mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 16:10:59 +08:00
feat: 支持上传本地证书
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type Cert struct {
|
||||
@@ -27,9 +28,10 @@ type Cert struct {
|
||||
}
|
||||
|
||||
type CertRepo interface {
|
||||
List(page, limit uint) ([]*Cert, int64, error)
|
||||
List(page, limit uint) ([]*types.CertList, int64, error)
|
||||
Get(id uint) (*Cert, error)
|
||||
GetByWebsite(WebsiteID uint) (*Cert, error)
|
||||
Upload(req *request.CertUpload) (*Cert, error)
|
||||
Create(req *request.CertCreate) (*Cert, error)
|
||||
Update(req *request.CertUpdate) error
|
||||
Delete(id uint) error
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,9 +12,11 @@ import (
|
||||
"github.com/TheTNB/panel/internal/biz"
|
||||
"github.com/TheTNB/panel/internal/http/request"
|
||||
"github.com/TheTNB/panel/pkg/acme"
|
||||
pkgcert "github.com/TheTNB/panel/pkg/cert"
|
||||
"github.com/TheTNB/panel/pkg/io"
|
||||
"github.com/TheTNB/panel/pkg/shell"
|
||||
"github.com/TheTNB/panel/pkg/systemctl"
|
||||
"github.com/TheTNB/panel/pkg/types"
|
||||
)
|
||||
|
||||
type certRepo struct {
|
||||
@@ -24,11 +27,37 @@ func NewCertRepo() biz.CertRepo {
|
||||
return &certRepo{}
|
||||
}
|
||||
|
||||
func (r *certRepo) List(page, limit uint) ([]*biz.Cert, int64, error) {
|
||||
func (r *certRepo) List(page, limit uint) ([]*types.CertList, int64, error) {
|
||||
var certs []*biz.Cert
|
||||
var total int64
|
||||
err := app.Orm.Model(&biz.Cert{}).Preload("Website").Preload("Account").Preload("DNS").Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&certs).Error
|
||||
return certs, total, err
|
||||
|
||||
var list []*types.CertList
|
||||
for cert := range slices.Values(certs) {
|
||||
item := &types.CertList{
|
||||
ID: cert.ID,
|
||||
AccountID: cert.AccountID,
|
||||
WebsiteID: cert.WebsiteID,
|
||||
DNSID: cert.DNSID,
|
||||
Type: cert.Type,
|
||||
Domains: cert.Domains,
|
||||
AutoRenew: cert.AutoRenew,
|
||||
Cert: cert.Cert,
|
||||
Key: cert.Key,
|
||||
CreatedAt: cert.CreatedAt,
|
||||
UpdatedAt: cert.UpdatedAt,
|
||||
}
|
||||
if decode, err := pkgcert.ParseCert(cert.Cert); err == nil {
|
||||
item.NotBefore = decode.NotBefore
|
||||
item.NotAfter = decode.NotAfter
|
||||
item.Issuer = decode.Issuer.CommonName
|
||||
item.OCSPServer = decode.OCSPServer
|
||||
item.DNSNames = decode.DNSNames
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
return list, total, err
|
||||
}
|
||||
|
||||
func (r *certRepo) Get(id uint) (*biz.Cert, error) {
|
||||
@@ -43,6 +72,25 @@ func (r *certRepo) GetByWebsite(WebsiteID uint) (*biz.Cert, error) {
|
||||
return cert, err
|
||||
}
|
||||
|
||||
func (r *certRepo) Upload(req *request.CertUpload) (*biz.Cert, error) {
|
||||
info, err := pkgcert.ParseCert(req.Cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert := &biz.Cert{
|
||||
Type: "upload",
|
||||
Domains: info.DNSNames,
|
||||
Cert: req.Cert,
|
||||
Key: req.Key,
|
||||
}
|
||||
if err = app.Orm.Create(cert).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (r *certRepo) Create(req *request.CertCreate) (*biz.Cert, error) {
|
||||
cert := &biz.Cert{
|
||||
AccountID: req.AccountID,
|
||||
@@ -59,12 +107,22 @@ func (r *certRepo) Create(req *request.CertCreate) (*biz.Cert, error) {
|
||||
}
|
||||
|
||||
func (r *certRepo) Update(req *request.CertUpdate) error {
|
||||
info, err := pkgcert.ParseCert(req.Cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Type == "upload" {
|
||||
req.Domains = info.DNSNames
|
||||
}
|
||||
|
||||
return app.Orm.Model(&biz.Cert{}).Where("id = ?", req.ID).Select("*").Updates(&biz.Cert{
|
||||
ID: req.ID,
|
||||
AccountID: req.AccountID,
|
||||
WebsiteID: req.WebsiteID,
|
||||
DNSID: req.DNSID,
|
||||
Type: req.Type,
|
||||
Cert: req.Cert,
|
||||
Key: req.Key,
|
||||
Domains: req.Domains,
|
||||
AutoRenew: req.AutoRenew,
|
||||
}).Error
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cast"
|
||||
@@ -137,8 +138,8 @@ func (r *websiteRepo) Get(id uint) (*types.WebsiteSetting, error) {
|
||||
setting.SSLCertificateKey = key
|
||||
// 解析证书信息
|
||||
if decode, err := cert.ParseCert(crt); err == nil {
|
||||
setting.SSLNotBefore = decode.NotBefore.Format("2006-01-02 15:04:05")
|
||||
setting.SSLNotAfter = decode.NotAfter.Format("2006-01-02 15:04:05")
|
||||
setting.SSLNotBefore = decode.NotBefore.Format(time.DateTime)
|
||||
setting.SSLNotAfter = decode.NotAfter.Format(time.DateTime)
|
||||
setting.SSLIssuer = decode.Issuer.CommonName
|
||||
setting.SSLOCSPServer = decode.OCSPServer
|
||||
setting.SSLDNSNames = decode.DNSNames
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package request
|
||||
|
||||
type CertUpload struct {
|
||||
Cert string `form:"cert" json:"cert" validate:"required"`
|
||||
Key string `form:"key" json:"key" validate:"required"`
|
||||
}
|
||||
|
||||
type CertCreate struct {
|
||||
Type string `form:"type" json:"type" validate:"required,oneof=P256 P384 2048 3072 4096"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
|
||||
@@ -11,8 +16,10 @@ type CertCreate struct {
|
||||
|
||||
type CertUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required,exists=certs id"`
|
||||
Type string `form:"type" json:"type" validate:"required,oneof=P256 P384 2048 3072 4096"`
|
||||
Type string `form:"type" json:"type" validate:"required,oneof=upload P256 P384 2048 3072 4096"`
|
||||
Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"`
|
||||
Cert string `form:"cert" json:"cert" validate:"required"`
|
||||
Key string `form:"key" json:"key" validate:"required"`
|
||||
AutoRenew bool `form:"auto_renew" json:"auto_renew"`
|
||||
AccountID uint `form:"account_id" json:"account_id"`
|
||||
DNSID uint `form:"dns_id" json:"dns_id"`
|
||||
|
||||
@@ -34,7 +34,7 @@ func (r *CertRenew) Run() {
|
||||
}
|
||||
|
||||
for _, cert := range certs {
|
||||
if !cert.AutoRenew {
|
||||
if cert.Type == "upload" || !cert.AutoRenew {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ func (r *Monitoring) Run() {
|
||||
if day <= 0 || app.Status != app.StatusNormal {
|
||||
return
|
||||
}
|
||||
if err = app.Orm.Where("created_at < ?", time.Now().AddDate(0, 0, -day).Format("2006-01-02 15:04:05")).Delete(&biz.Monitor{}).Error; err != nil {
|
||||
if err = app.Orm.Where("created_at < ?", time.Now().AddDate(0, 0, -day).Format(time.DateTime)).Delete(&biz.Monitor{}).Error; err != nil {
|
||||
app.Logger.Error("删除过期系统监控失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ func Http(r chi.Router) {
|
||||
r.Route("/cert", func(r chi.Router) {
|
||||
r.Get("/", cert.List)
|
||||
r.Post("/", cert.Create)
|
||||
r.Post("/upload", cert.Upload)
|
||||
r.Put("/{id}", cert.Update)
|
||||
r.Get("/{id}", cert.Get)
|
||||
r.Delete("/{id}", cert.Delete)
|
||||
|
||||
@@ -115,6 +115,22 @@ func (s *CertService) List(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *CertService) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.CertUpload](r)
|
||||
if err != nil {
|
||||
Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cert, err := s.certRepo.Upload(req)
|
||||
if err != nil {
|
||||
Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Success(w, cert)
|
||||
}
|
||||
|
||||
func (s *CertService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := Bind[request.CertCreate](r)
|
||||
if err != nil {
|
||||
|
||||
@@ -168,7 +168,7 @@ func (s *CliService) UserList(ctx context.Context, cmd *cli.Command) error {
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
fmt.Printf("ID: %d, 用户名: %s, 邮箱: %s, 创建日期: %s\n", user.ID, user.Username, user.Email, user.CreatedAt.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("ID: %d, 用户名: %s, 邮箱: %s, 创建日期: %s\n", user.ID, user.Username, user.Email, user.CreatedAt.Format(time.DateTime))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -46,13 +46,13 @@ func Now(address ...string) (time.Time, error) {
|
||||
return now, nil
|
||||
}
|
||||
|
||||
func UpdateSystemTime(time time.Time) error {
|
||||
_, err := shell.Execf(`date -s '%s'`, time.Format("2006-01-02 15:04:05"))
|
||||
func UpdateSystemTime(t time.Time) error {
|
||||
_, err := shell.Execf(`date -s '%s'`, t.Format(time.DateTime))
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateSystemTimeZone(timezone string) error {
|
||||
_, err := shell.Execf(`timedatectl set-timezone '%s'`, timezone)
|
||||
func UpdateSystemTimeZone(tz string) error {
|
||||
_, err := shell.Execf(`timedatectl set-timezone '%s'`, tz)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
22
pkg/types/cert.go
Normal file
22
pkg/types/cert.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
type CertList struct {
|
||||
ID uint `json:"id"`
|
||||
AccountID uint `json:"account_id"`
|
||||
WebsiteID uint `json:"website_id"`
|
||||
DNSID uint `json:"dns_id"`
|
||||
Type string `json:"type"`
|
||||
Domains []string `json:"domains"`
|
||||
AutoRenew bool `json:"auto_renew"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
NotBefore time.Time `json:"not_before"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
Issuer string `json:"issuer"`
|
||||
OCSPServer []string `json:"ocsp_server"`
|
||||
DNSNames []string `json:"dns_names"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -38,6 +38,8 @@ export default {
|
||||
request.get('/cert/cert', { params: { page, limit } }),
|
||||
// 证书详情
|
||||
certInfo: (id: number): Promise<AxiosResponse<any>> => request.get(`/cert/cert/${id}`),
|
||||
// 证书上传
|
||||
certUpload: (data: any): Promise<AxiosResponse<any>> => request.post('/cert/cert/upload', data),
|
||||
// 证书添加
|
||||
certCreate: (data: any): Promise<AxiosResponse<any>> => request.post('/cert/cert', data),
|
||||
// 证书更新
|
||||
|
||||
@@ -8,7 +8,10 @@ body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #f2f2f2;
|
||||
font-family: -apple-system, "Noto Sans", "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif;;
|
||||
font-family: -apple-system, 'Noto Sans', 'Helvetica Neue', Helvetica, 'Nimbus Sans L', Arial,
|
||||
'Liberation Sans', 'PingFang SC', 'Hiragino Sans GB', 'Noto Sans CJK SC', 'Noto Sans SC',
|
||||
'Source Han Sans SC', 'Source Han Sans CN', 'Microsoft YaHei', 'Wenquanyi Micro Hei',
|
||||
'WenQuanYi Zen Hei', 'ST Heiti', SimHei, 'WenQuanYi Zen Hei Sharp', sans-serif;
|
||||
}
|
||||
|
||||
#app {
|
||||
|
||||
@@ -4,37 +4,39 @@ import type { MessageReactive } from 'naive-ui'
|
||||
import { NButton, NDataTable, NFlex, NPopconfirm, NSpace, NSwitch, NTable, NTag } from 'naive-ui'
|
||||
|
||||
import cert from '@/api/panel/cert'
|
||||
import { formatDateTime } from '@/utils'
|
||||
import type { Cert } from '@/views/cert/types'
|
||||
|
||||
const props = defineProps({
|
||||
algorithms: Array<any>,
|
||||
websites: Array<any>,
|
||||
accounts: Array<any>,
|
||||
dns: Array<any>,
|
||||
caProviders: Array<any>
|
||||
dns: Array<any>
|
||||
})
|
||||
|
||||
const { algorithms, websites, accounts, dns, caProviders } = toRefs(props)
|
||||
const { algorithms, websites, accounts, dns } = toRefs(props)
|
||||
|
||||
let messageReactive: MessageReactive | null = null
|
||||
|
||||
const updateCertModel = ref<any>({
|
||||
const updateModel = ref<any>({
|
||||
domains: [],
|
||||
type: 'P256',
|
||||
dns_id: null,
|
||||
account_id: null,
|
||||
website_id: null,
|
||||
auto_renew: true
|
||||
})
|
||||
const updateCertModal = ref(false)
|
||||
const updateCert = ref<any>()
|
||||
const showModal = ref(false)
|
||||
const showCertModel = ref<any>({
|
||||
auto_renew: true,
|
||||
cert: '',
|
||||
key: ''
|
||||
})
|
||||
const deployCertModal = ref(false)
|
||||
const deployCertModel = ref<any>({
|
||||
const updateModal = ref(false)
|
||||
const updateCert = ref<any>()
|
||||
const showModal = ref(false)
|
||||
const showModel = ref<any>({
|
||||
cert: '',
|
||||
key: ''
|
||||
})
|
||||
const deployModal = ref(false)
|
||||
const deployModel = ref<any>({
|
||||
id: null,
|
||||
websites: []
|
||||
})
|
||||
@@ -43,7 +45,7 @@ const columns: any = [
|
||||
{
|
||||
title: '域名',
|
||||
key: 'domains',
|
||||
minWidth: 200,
|
||||
minWidth: 150,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any) {
|
||||
@@ -53,7 +55,12 @@ const columns: any = [
|
||||
type: row.status == 'active' ? 'success' : 'error'
|
||||
},
|
||||
{
|
||||
default: () => row.domains.join(', ')
|
||||
default: () => {
|
||||
if (row.domains == null || row.domains.length == 0) {
|
||||
return '无'
|
||||
}
|
||||
return row.domains.join(', ')
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -83,7 +90,7 @@ const columns: any = [
|
||||
case '4096':
|
||||
return 'RSA 4096'
|
||||
default:
|
||||
return '未知'
|
||||
return '上传'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +100,7 @@ const columns: any = [
|
||||
{
|
||||
title: '关联账号',
|
||||
key: 'account_id',
|
||||
minWidth: 400,
|
||||
minWidth: 200,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any) {
|
||||
@@ -102,51 +109,44 @@ const columns: any = [
|
||||
}
|
||||
return h(NFlex, null, {
|
||||
default: () => [
|
||||
h(NTag, null, { default: () => (row.account?.email == null ? '无' : row.account.email) }),
|
||||
h(NTag, null, {
|
||||
default: () =>
|
||||
caProviders?.value?.find((item: any) => item.value === row.account?.ca)?.label
|
||||
row.account_id == 0
|
||||
? '无'
|
||||
: accounts?.value?.find((item: any) => item.value === row.account_id)?.label
|
||||
})
|
||||
]
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '关联网站',
|
||||
key: 'website_id',
|
||||
minWidth: 150,
|
||||
title: '颁发者',
|
||||
key: 'issuer',
|
||||
minWidth: 100,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: row.website == null ? 'error' : 'success',
|
||||
type: 'info',
|
||||
bordered: false
|
||||
},
|
||||
{
|
||||
default: () => (row.website?.name == null ? '无' : row.website.name)
|
||||
default: () => {
|
||||
return row.issuer == '' ? '无' : row.issuer
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '关联DNS',
|
||||
key: 'dns_id',
|
||||
width: 150,
|
||||
title: '过期时间',
|
||||
key: 'not_after',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: row.dns == null ? 'error' : 'success',
|
||||
bordered: false
|
||||
},
|
||||
{
|
||||
default: () => (row.dns?.name == null ? '无' : row.dns.name)
|
||||
}
|
||||
)
|
||||
return formatDateTime(row.not_after)
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -258,11 +258,11 @@ const columns: any = [
|
||||
size: 'small',
|
||||
type: 'info',
|
||||
onClick: () => {
|
||||
deployCertModel.value.id = row.id
|
||||
deployModel.value.id = row.id
|
||||
if (row.website_id != 0) {
|
||||
deployCertModel.value.websites.push(row.website_id)
|
||||
deployModel.value.websites.push(row.website_id)
|
||||
}
|
||||
deployCertModal.value = true
|
||||
deployModal.value = true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -270,7 +270,7 @@ const columns: any = [
|
||||
}
|
||||
)
|
||||
: null,
|
||||
row.cert != '' && row.key != ''
|
||||
row.cert != '' && row.key != '' && row.type != 'upload'
|
||||
? h(
|
||||
NButton,
|
||||
{
|
||||
@@ -300,8 +300,8 @@ const columns: any = [
|
||||
type: 'tertiary',
|
||||
style: 'margin-left: 15px;',
|
||||
onClick: () => {
|
||||
showCertModel.value.cert = row.cert
|
||||
showCertModel.value.key = row.key
|
||||
showModel.value.cert = row.cert
|
||||
showModel.value.key = row.key
|
||||
showModal.value = true
|
||||
}
|
||||
},
|
||||
@@ -318,13 +318,15 @@ const columns: any = [
|
||||
style: 'margin-left: 15px;',
|
||||
onClick: () => {
|
||||
updateCert.value = row.id
|
||||
updateCertModel.value.domains = row.domains
|
||||
updateCertModel.value.type = row.type
|
||||
updateCertModel.value.dns_id = row.dns_id == 0 ? null : row.dns_id
|
||||
updateCertModel.value.account_id = row.account_id == 0 ? null : row.account_id
|
||||
updateCertModel.value.website_id = row.website_id == 0 ? null : row.website_id
|
||||
updateCertModel.value.auto_renew = row.auto_renew
|
||||
updateCertModal.value = true
|
||||
updateModel.value.domains = row.domains
|
||||
updateModel.value.type = row.type
|
||||
updateModel.value.dns_id = row.dns_id == 0 ? null : row.dns_id
|
||||
updateModel.value.account_id = row.account_id == 0 ? null : row.account_id
|
||||
updateModel.value.website_id = row.website_id == 0 ? null : row.website_id
|
||||
updateModel.value.auto_renew = row.auto_renew
|
||||
updateModel.value.cert = row.cert
|
||||
updateModel.value.key = row.key
|
||||
updateModal.value = true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -395,31 +397,33 @@ const getCertList = async (page: number, limit: number) => {
|
||||
}
|
||||
|
||||
const handleUpdateCert = async () => {
|
||||
await cert.certUpdate(updateCert.value, updateCertModel.value)
|
||||
await cert.certUpdate(updateCert.value, updateModel.value)
|
||||
window.$message.success('更新成功')
|
||||
updateCertModal.value = false
|
||||
updateModal.value = false
|
||||
onPageChange(1)
|
||||
updateCertModel.value.domains = []
|
||||
updateCertModel.value.type = 'P256'
|
||||
updateCertModel.value.dns_id = null
|
||||
updateCertModel.value.account_id = null
|
||||
updateCertModel.value.website_id = null
|
||||
updateCertModel.value.auto_renew = true
|
||||
updateModel.value.domains = []
|
||||
updateModel.value.type = 'P256'
|
||||
updateModel.value.dns_id = null
|
||||
updateModel.value.account_id = null
|
||||
updateModel.value.website_id = null
|
||||
updateModel.value.auto_renew = true
|
||||
updateModel.value.cert = ''
|
||||
updateModel.value.key = ''
|
||||
}
|
||||
|
||||
const handleDeployCert = async () => {
|
||||
for (const website of deployCertModel.value.websites) {
|
||||
await cert.deploy(deployCertModel.value.id, website)
|
||||
for (const website of deployModel.value.websites) {
|
||||
await cert.deploy(deployModel.value.id, website)
|
||||
}
|
||||
window.$message.success('部署成功')
|
||||
deployCertModal.value = false
|
||||
deployCertModel.value.id = null
|
||||
deployCertModel.value.websites = []
|
||||
deployModal.value = false
|
||||
deployModel.value.id = null
|
||||
deployModel.value.websites = []
|
||||
}
|
||||
|
||||
const handleShowModalClose = () => {
|
||||
showCertModel.value.cert = ''
|
||||
showCertModel.value.key = ''
|
||||
showModel.value.cert = ''
|
||||
showModel.value.key = ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -450,7 +454,7 @@ onUnmounted(() => {
|
||||
/>
|
||||
</n-space>
|
||||
<n-modal
|
||||
v-model:show="updateCertModal"
|
||||
v-model:show="updateModal"
|
||||
preset="card"
|
||||
title="修改证书"
|
||||
style="width: 60vw"
|
||||
@@ -463,18 +467,18 @@ onUnmounted(() => {
|
||||
可以通过选择网站 / DNS 中的任意一项来自动签发和部署证书,也可以手动输入域名并设置 DNS
|
||||
解析来签发证书
|
||||
</n-alert>
|
||||
<n-form :model="updateCertModel">
|
||||
<n-form-item label="域名">
|
||||
<n-form :model="updateModel">
|
||||
<n-form-item v-if="updateModel.type != 'upload'" path="domains" label="域名">
|
||||
<n-dynamic-input
|
||||
v-model:value="updateCertModel.domains"
|
||||
v-model:value="updateModel.domains"
|
||||
placeholder="example.com"
|
||||
:min="1"
|
||||
show-sort-button
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="type" label="密钥类型">
|
||||
<n-form-item v-if="updateModel.type != 'upload'" path="type" label="密钥类型">
|
||||
<n-select
|
||||
v-model:value="updateCertModel.type"
|
||||
v-model:value="updateModel.type"
|
||||
placeholder="选择密钥类型"
|
||||
clearable
|
||||
:options="algorithms"
|
||||
@@ -482,34 +486,48 @@ onUnmounted(() => {
|
||||
</n-form-item>
|
||||
<n-form-item path="website_id" label="网站">
|
||||
<n-select
|
||||
v-model:value="updateCertModel.website_id"
|
||||
v-model:value="updateModel.website_id"
|
||||
placeholder="选择用于部署证书的网站"
|
||||
clearable
|
||||
:options="websites"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="account_id" label="账号">
|
||||
<n-form-item v-if="updateModel.type != 'upload'" path="account_id" label="账号">
|
||||
<n-select
|
||||
v-model:value="updateCertModel.account_id"
|
||||
v-model:value="updateModel.account_id"
|
||||
placeholder="选择用于签发证书的账号"
|
||||
clearable
|
||||
:options="accounts"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="account_id" label="DNS">
|
||||
<n-form-item v-if="updateModel.type != 'upload'" path="account_id" label="DNS">
|
||||
<n-select
|
||||
v-model:value="updateCertModel.dns_id"
|
||||
v-model:value="updateModel.dns_id"
|
||||
placeholder="选择用于签发证书的DNS"
|
||||
clearable
|
||||
:options="dns"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="updateModel.type == 'upload'" path="cert" label="证书">
|
||||
<n-input
|
||||
v-model:value="updateModel.cert"
|
||||
type="textarea"
|
||||
placeholder="输入 PEM 证书文件的内容"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="updateModel.type == 'upload'" path="key" label="私钥">
|
||||
<n-input
|
||||
v-model:value="updateModel.key"
|
||||
type="textarea"
|
||||
placeholder="输入 KEY 私钥文件的内容"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleUpdateCert">提交</n-button>
|
||||
</n-space>
|
||||
</n-modal>
|
||||
<n-modal
|
||||
v-model:show="deployCertModal"
|
||||
v-model:show="deployModal"
|
||||
preset="card"
|
||||
title="部署证书"
|
||||
style="width: 60vw"
|
||||
@@ -518,10 +536,10 @@ onUnmounted(() => {
|
||||
:segmented="false"
|
||||
>
|
||||
<n-space vertical>
|
||||
<n-form :model="deployCertModel">
|
||||
<n-form :model="deployModel">
|
||||
<n-form-item path="website_id" label="网站">
|
||||
<n-select
|
||||
v-model:value="deployCertModel.websites"
|
||||
v-model:value="deployModel.websites"
|
||||
placeholder="选择需要部署证书的网站"
|
||||
clearable
|
||||
multiple
|
||||
@@ -545,7 +563,7 @@ onUnmounted(() => {
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane name="cert" tab="证书">
|
||||
<Editor
|
||||
v-model:value="showCertModel.cert"
|
||||
v-model:value="showModel.cert"
|
||||
theme="vs-dark"
|
||||
height="60vh"
|
||||
mt-8
|
||||
@@ -557,7 +575,7 @@ onUnmounted(() => {
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="key" tab="密钥">
|
||||
<Editor
|
||||
v-model:value="showCertModel.key"
|
||||
v-model:value="showModel.key"
|
||||
theme="vs-dark"
|
||||
height="60vh"
|
||||
mt-8
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import UploadCertModal from '@/views/cert/UploadCertModal.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'cert-index'
|
||||
})
|
||||
@@ -17,8 +19,9 @@ import DnsView from '@/views/cert/DnsView.vue'
|
||||
|
||||
const currentTab = ref('cert')
|
||||
|
||||
const createDNS = ref(false)
|
||||
const uploadCert = ref(false)
|
||||
const createCert = ref(false)
|
||||
const createDNS = ref(false)
|
||||
const createAccount = ref(false)
|
||||
|
||||
const algorithms = ref<any>([])
|
||||
@@ -83,28 +86,28 @@ onUnmounted(() => {
|
||||
<template>
|
||||
<common-page show-footer>
|
||||
<template #action>
|
||||
<n-button v-if="currentTab == 'cert'" type="primary" @click="createCert = true">
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
创建证书
|
||||
</n-button>
|
||||
<n-button v-if="currentTab == 'user'" type="primary" @click="createAccount = true">
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
创建账号
|
||||
</n-button>
|
||||
<n-button v-if="currentTab == 'dns'" type="primary" @click="createDNS = true">
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
创建 DNS
|
||||
</n-button>
|
||||
<n-flex>
|
||||
<n-button v-if="currentTab == 'cert'" type="success" @click="uploadCert = true">
|
||||
<TheIcon :size="18" icon="material-symbols:upload" />
|
||||
上传证书
|
||||
</n-button>
|
||||
<n-button v-if="currentTab == 'cert'" type="primary" @click="createCert = true">
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
创建证书
|
||||
</n-button>
|
||||
<n-button v-if="currentTab == 'user'" type="primary" @click="createAccount = true">
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
创建账号
|
||||
</n-button>
|
||||
<n-button v-if="currentTab == 'dns'" type="primary" @click="createDNS = true">
|
||||
<TheIcon :size="18" icon="material-symbols:add" />
|
||||
创建 DNS
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</template>
|
||||
<n-tabs v-model:value="currentTab" type="line" animated>
|
||||
<n-tab-pane name="cert" tab="证书列表">
|
||||
<cert-view
|
||||
:accounts="accounts"
|
||||
:algorithms="algorithms"
|
||||
:websites="websites"
|
||||
:dns="dns"
|
||||
:ca-providers="caProviders"
|
||||
/>
|
||||
<cert-view :accounts="accounts" :algorithms="algorithms" :websites="websites" :dns="dns" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="user" tab="账号列表">
|
||||
<account-view :ca-providers="caProviders" :algorithms="algorithms" />
|
||||
@@ -114,6 +117,7 @@ onUnmounted(() => {
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</common-page>
|
||||
<upload-cert-modal v-model:show="uploadCert" />
|
||||
<create-cert-modal
|
||||
v-model:show="createCert"
|
||||
:accounts="accounts"
|
||||
|
||||
55
web/src/views/cert/UploadCertModal.vue
Normal file
55
web/src/views/cert/UploadCertModal.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import cert from '@/api/panel/cert'
|
||||
import { NButton, NSpace } from 'naive-ui'
|
||||
|
||||
const show = defineModel<boolean>('show', { type: Boolean, required: true })
|
||||
|
||||
const model = ref<any>({
|
||||
cert: '',
|
||||
key: ''
|
||||
})
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await cert.certUpload(model.value)
|
||||
show.value = false
|
||||
window.$message.success('创建成功')
|
||||
model.value.cert = ''
|
||||
model.value.key = ''
|
||||
window.$bus.emit('cert:refresh-cert')
|
||||
window.$bus.emit('cert:refresh-async')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="show"
|
||||
preset="card"
|
||||
title="上传证书"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
>
|
||||
<n-space vertical>
|
||||
<n-form :model="model">
|
||||
<n-form-item label="证书">
|
||||
<n-input
|
||||
v-model:value="model.cert"
|
||||
type="textarea"
|
||||
placeholder="输入 PEM 证书文件的内容"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="私钥">
|
||||
<n-input
|
||||
v-model:value="model.key"
|
||||
type="textarea"
|
||||
placeholder="输入 KEY 私钥文件的内容"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleSubmit">提交</n-button>
|
||||
</n-space>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
Reference in New Issue
Block a user