feat: 修改菜单位置 证书管理放在二级菜单

This commit is contained in:
zhengkunwang223 2022-11-17 17:39:33 +08:00 committed by zhengkunwang223
parent 681a0c9106
commit c7e0e3320a
37 changed files with 495 additions and 295 deletions

View File

@ -38,6 +38,19 @@ func (b *BaseApi) CreateWebsiteSSL(c *gin.Context) {
helper.SuccessWithData(c, res)
}
func (b *BaseApi) RenewWebsiteSSL(c *gin.Context) {
var req dto.WebsiteSSLRenew
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteSSLService.Renew(req.SSLID); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) ApplyWebsiteSSL(c *gin.Context) {
var req dto.WebsiteSSLApply
if err := c.ShouldBindJSON(&req); err != nil {
@ -73,10 +86,24 @@ func (b *BaseApi) DeleteWebsiteSSL(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteSSLService.Delete(id); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) GetWebsiteSSL(c *gin.Context) {
websiteId, err := helper.GetIntParamByKey(c, "websiteId")
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, websiteSSL)
}

View File

@ -10,15 +10,15 @@ type SSLProvider string
const (
DNSAccount = "dnsAccount"
DnsCommon = "dnsCommon"
DnsManual = "dnsManual"
Http = "http"
)
type WebsiteSSLCreate struct {
Domains []string `json:"domains" validate:"required"`
PrimaryDomain string `json:"primaryDomain" validate:"required"`
OtherDomains string `json:"otherDomains"`
Provider SSLProvider `json:"provider" validate:"required"`
WebsiteID uint `json:"websiteId" validate:"required"`
AcmeAccountID uint `json:"acmeAccountId"`
AcmeAccountID uint `json:"acmeAccountId" validate:"required"`
DnsAccountID uint `json:"dnsAccountId"`
}
@ -37,3 +37,7 @@ type WebsiteDNSRes struct {
Value string `json:"value"`
Type string `json:"type"`
}
type WebsiteSSLRenew struct {
SSLID uint `json:"SSLId" validate:"required"`
}

View File

@ -4,16 +4,18 @@ import "time"
type WebSiteSSL struct {
BaseModel
Alias string `gorm:"type:varchar(64);not null" json:"alias"`
PrivateKey string `gorm:"type:longtext;not null" json:"privateKey"`
Pem string `gorm:"type:longtext;not null" json:"pem"`
Domain string `gorm:"type:varchar(256);not null" json:"domain"`
CertURL string `gorm:"type:varchar(256);not null" json:"certURL"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
IssuerName string `gorm:"type:varchar(64);not null" json:"issuerName"`
DnsAccountID uint `gorm:"type:integer;not null" json:"dnsAccountId"`
ExpireDate time.Time `json:"expireDate"`
StartDate time.Time `json:"startDate"`
PrimaryDomain string `gorm:"type:varchar(256);not null" json:"primaryDomain"`
PrivateKey string `gorm:"type:longtext;not null" json:"privateKey"`
Pem string `gorm:"type:longtext;not null" json:"pem"`
Domains string `gorm:"type:varchar(256);not null" json:"domains"`
CertURL string `gorm:"type:varchar(256);not null" json:"certURL"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
Provider string `gorm:"type:varchar(64);not null" json:"provider"`
Organization string `gorm:"type:varchar(64);not null" json:"organization"`
DnsAccountID uint `gorm:"type:integer;not null" json:"dnsAccountId"`
AcmeAccountID uint `gorm:"type:integer;not null" json:"acmeAccountId"`
ExpireDate time.Time `json:"expireDate"`
StartDate time.Time `json:"startDate"`
}
func (w WebSiteSSL) TableName() string {

View File

@ -3,6 +3,7 @@ package service
import (
"context"
"crypto/x509"
"encoding/pem"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/utils/ssl"
@ -31,48 +32,59 @@ func (w WebSiteSSLService) Create(create dto.WebsiteSSLCreate) (dto.WebsiteSSLCr
if err != nil {
return res, err
}
dnsAccount, err := websiteDnsRepo.GetFirst(commonRepo.WithByID(create.DnsAccountID))
if err != nil {
return res, err
}
client, err := ssl.NewPrivateKeyClient(acmeAccount.Email, acmeAccount.PrivateKey)
if err != nil {
return res, err
}
if create.Provider == dto.Http {
} else {
switch create.Provider {
case dto.DNSAccount:
dnsAccount, err := websiteDnsRepo.GetFirst(commonRepo.WithByID(create.DnsAccountID))
if err != nil {
return res, err
}
if err := client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil {
return res, err
}
case dto.Http:
case dto.DnsManual:
}
resource, err := client.GetSSL(create.Domains)
domains := []string{create.PrimaryDomain}
otherDomainArray := strings.Split(create.OtherDomains, "\n")
if create.OtherDomains != "" {
domains = append(otherDomainArray, domains...)
}
resource, err := client.ObtainSSL(domains)
if err != nil {
return res, err
}
var websiteSSL model.WebSiteSSL
//TODO 判断同一个账号下的证书
websiteSSL.Alias = create.Domains[0]
websiteSSL.Domain = strings.Join(create.Domains, ",")
websiteSSL.DnsAccountID = create.DnsAccountID
websiteSSL.AcmeAccountID = acmeAccount.ID
websiteSSL.Provider = string(create.Provider)
websiteSSL.Domains = strings.Join(otherDomainArray, ",")
websiteSSL.PrimaryDomain = create.PrimaryDomain
websiteSSL.PrivateKey = string(resource.PrivateKey)
websiteSSL.Pem = string(resource.Certificate)
websiteSSL.CertURL = resource.CertURL
cert, err := x509.ParseCertificate([]byte(websiteSSL.Pem))
certBlock, _ := pem.Decode(resource.Certificate)
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return dto.WebsiteSSLCreate{}, err
}
websiteSSL.ExpireDate = cert.NotAfter
websiteSSL.StartDate = cert.NotBefore
websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.IssuerName = cert.Issuer.Organization[0]
websiteSSL.Organization = cert.Issuer.Organization[0]
if err := createPemFile(websiteSSL); err != nil {
return dto.WebsiteSSLCreate{}, err
}
//if err := createPemFile(websiteSSL); err != nil {
// return dto.WebsiteSSLCreate{}, err
//}
if err := websiteSSLRepo.Create(context.TODO(), &websiteSSL); err != nil {
return res, err
@ -81,6 +93,55 @@ func (w WebSiteSSLService) Create(create dto.WebsiteSSLCreate) (dto.WebsiteSSLCr
return create, nil
}
func (w WebSiteSSLService) Renew(sslId uint) error {
websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(sslId))
if err != nil {
return err
}
acmeAccount, err := websiteAcmeRepo.GetFirst(commonRepo.WithByID(websiteSSL.AcmeAccountID))
if err != nil {
return err
}
client, err := ssl.NewPrivateKeyClient(acmeAccount.Email, acmeAccount.PrivateKey)
if err != nil {
return err
}
switch websiteSSL.Provider {
case dto.DNSAccount:
dnsAccount, err := websiteDnsRepo.GetFirst(commonRepo.WithByID(websiteSSL.DnsAccountID))
if err != nil {
return err
}
if err := client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization); err != nil {
return err
}
case dto.Http:
case dto.DnsManual:
}
resource, err := client.RenewSSL(websiteSSL.CertURL)
if err != nil {
return err
}
websiteSSL.PrivateKey = string(resource.PrivateKey)
websiteSSL.Pem = string(resource.Certificate)
websiteSSL.CertURL = resource.CertURL
certBlock, _ := pem.Decode(resource.Certificate)
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return err
}
websiteSSL.ExpireDate = cert.NotAfter
websiteSSL.StartDate = cert.NotBefore
websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.Organization = cert.Issuer.Organization[0]
return websiteSSLRepo.Save(websiteSSL)
}
func (w WebSiteSSLService) Apply(apply dto.WebsiteSSLApply) (dto.WebsiteSSLApply, error) {
websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(apply.SSLID))
if err != nil {
@ -96,10 +157,10 @@ func (w WebSiteSSLService) Apply(apply dto.WebsiteSSLApply) (dto.WebsiteSSLApply
nginxParams := getNginxParamsFromStaticFile(dto.SSL)
for i, param := range nginxParams {
if param.Name == "ssl_certificate" {
nginxParams[i].Params = []string{path.Join("/etc/nginx/ssl", websiteSSL.Alias, "fullchain.pem")}
nginxParams[i].Params = []string{path.Join("/etc/nginx/ssl", websiteSSL.PrimaryDomain, "fullchain.pem")}
}
if param.Name == "ssl_certificate_key" {
nginxParams[i].Params = []string{path.Join("/etc/nginx/ssl", websiteSSL.Alias, "privkey.pem")}
nginxParams[i].Params = []string{path.Join("/etc/nginx/ssl", websiteSSL.PrimaryDomain, "privkey.pem")}
}
}
if err := updateNginxConfig(website, nginxParams, dto.SSL); err != nil {
@ -134,6 +195,20 @@ func (w WebSiteSSLService) GetDNSResolve(req dto.WebsiteDNSReq) (dto.WebsiteDNSR
return res, nil
}
func (w WebSiteSSLService) GetWebsiteSSL(websiteId uint) (dto.WebsiteSSLDTO, error) {
var res dto.WebsiteSSLDTO
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteId))
if err != nil {
return res, err
}
websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebSiteSSLID))
if err != nil {
return res, err
}
res.WebSiteSSL = websiteSSL
return res, nil
}
func (w WebSiteSSLService) Delete(id uint) error {
return websiteSSLRepo.DeleteBy(commonRepo.WithByID(id))
}

View File

@ -318,7 +318,7 @@ func createPemFile(websiteSSL model.WebSiteSSL) error {
return err
}
configDir := path.Join(constant.AppInstallDir, "nginx", nginxInstall.Name, "ssl", websiteSSL.Alias)
configDir := path.Join(constant.AppInstallDir, "nginx", nginxInstall.Name, "ssl", websiteSSL.PrimaryDomain)
fileOp := files.NewFileOp()
if !fileOp.Stat(configDir) {

View File

@ -16,9 +16,11 @@ func (a *WebsiteSSLRouter) InitWebsiteSSLRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
groupRouter.POST("/search", baseApi.PageWebsiteSSL)
groupRouter.POST("/renew", baseApi.RenewWebsiteSSL)
groupRouter.POST("", baseApi.CreateWebsiteSSL)
groupRouter.POST("/apply", baseApi.ApplyWebsiteSSL)
groupRouter.POST("/resolve", baseApi.GetDNSResolve)
groupRouter.POST("/:id", baseApi.DeleteWebsiteSSL)
groupRouter.DELETE("/:id", baseApi.DeleteWebsiteSSL)
groupRouter.GET("/:websiteId", baseApi.GetWebsiteSSL)
}
}

View File

@ -51,7 +51,7 @@ type server interface {
func initServer(address string, router *gin.Engine) server {
s := endless.NewServer(address, router)
s.ReadHeaderTimeout = 20 * time.Second
s.WriteTimeout = 20 * time.Second
s.WriteTimeout = 60 * time.Second
s.MaxHeaderBytes = 1 << 20
return s
}

View File

@ -1,27 +1,27 @@
-----BEGIN privateKey-----
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
-----END privateKey-----

View File

@ -85,9 +85,8 @@ func NewPrivateKeyClient(email string, privateKey string) (*AcmeClient, error) {
func newConfig(user *AcmeUser) *lego.Config {
config := lego.NewConfig(user)
config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
config.UserAgent = "acm_go/0.0.1"
config.Certificate.KeyType = certcrypto.RSA2048
return config
}

View File

@ -99,12 +99,12 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string) error {
}
}
return c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(6*time.Minute))
return c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(3*time.Minute))
}
func (c *AcmeClient) UseManualDns(domains []string) (*Resolve, error) {
p := &manualDnsProvider{}
if err := c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(6*time.Minute)); err != nil {
if err := c.Client.Challenge.SetDNS01Provider(p, dns01.AddDNSTimeout(3*time.Minute)); err != nil {
return nil, nil
}
@ -124,7 +124,7 @@ func (c *AcmeClient) UseHTTP() {
}
func (c *AcmeClient) GetSSL(domains []string) (certificate.Resource, error) {
func (c *AcmeClient) ObtainSSL(domains []string) (certificate.Resource, error) {
request := certificate.ObtainRequest{
Domains: domains,
@ -139,6 +139,20 @@ func (c *AcmeClient) GetSSL(domains []string) (certificate.Resource, error) {
return *certificates, nil
}
func (c *AcmeClient) RenewSSL(certUrl string) (certificate.Resource, error) {
certificates, err := c.Client.Certificate.Get(certUrl, true)
if err != nil {
return certificate.Resource{}, err
}
certificates, err = c.Client.Certificate.Renew(*certificates, true, true, "")
if err != nil {
return certificate.Resource{}, err
}
return *certificates, nil
}
type Resolve struct {
Key string
Value string

View File

@ -118,9 +118,10 @@ export namespace WebSite {
}
export interface SSL extends CommonModel {
primaryDomain: string;
privateKey: string;
pem: string;
domain: string;
otherDomains: string;
certURL: string;
type: string;
issuerName: string;
@ -129,9 +130,9 @@ export namespace WebSite {
}
export interface SSLCreate {
domains: string[];
primaryDomain: string;
otherDomains: string;
provider: string;
websiteId: number;
acmeAccountId: number;
dnsAccountId: number;
}
@ -141,6 +142,10 @@ export namespace WebSite {
SSLId: number;
}
export interface SSLRenew {
SSLId: number;
}
export interface AcmeAccount extends CommonModel {
email: string;
url: string;

View File

@ -98,10 +98,18 @@ export const DeleteSSL = (id: number) => {
return http.delete<any>(`/websites/ssl/${id}`);
};
export const GetWebsiteSSL = (websiteId: number) => {
return http.get<WebSite.SSL>(`/websites/ssl/${websiteId}`);
};
export const ApplySSL = (req: WebSite.SSLApply) => {
return http.post<WebSite.SSLApply>(`/websites/ssl/apply`, req);
};
export const RenewSSL = (req: WebSite.SSLRenew) => {
return http.post<any>(`/websites/ssl/renew`, req);
};
export const GetDnsResolve = (req: WebSite.DNSResolveReq) => {
return http.post<WebSite.DNSResolve>(`/websites/ssl/resolve`, req);
};

View File

@ -37,6 +37,7 @@ export const useDeleteData = <P = any, R = any>(
})
.finally(() => {
loading = false;
});
})
.catch(() => {});
});
};

View File

@ -131,6 +131,7 @@ export default {
project: '项目',
config: '配置',
firewall: '防火墙',
ssl: '证书',
database: '数据库',
container: '容器',
cronjob: '计划任务',
@ -704,7 +705,7 @@ export default {
manual: '手动解析',
key: '密钥',
check: '查看',
accountManage: '账户管理',
acmeAccountManage: 'Acme 账户管理',
email: '邮箱',
addAccount: '新增账户',
acmeAccount: 'Acme 账户',
@ -714,5 +715,10 @@ export default {
brand: '品牌',
deploySSL: '部署',
deploySSLHelper: '确定部署证书',
ssl: '证书',
dnsAccountManage: 'DNS 账户管理',
renewSSL: '续签',
renewHelper: '确定续签证书',
renewSuccess: '续签证书',
},
};

View File

@ -13,21 +13,29 @@ const webSiteRouter = {
{
path: '/websites',
name: 'Website',
component: () => import('@/views/website/project/index.vue'),
component: () => import('@/views/website/website/index.vue'),
meta: {
title: 'menu.project',
title: 'menu.website',
},
},
{
path: '/websites/:id/config/:tab',
name: 'WebsiteConfig',
component: () => import('@/views/website/project/config/index.vue'),
component: () => import('@/views/website/website/config/index.vue'),
hidden: true,
props: true,
meta: {
activeMenu: '/websites',
},
},
{
path: '/websites/ssl',
name: 'SSL',
component: () => import('@/views/website/ssl/index.vue'),
meta: {
title: 'menu.ssl',
},
},
{
path: '/websites/nginx',
name: 'Config',
@ -36,14 +44,6 @@ const webSiteRouter = {
title: 'menu.config',
},
},
{
path: '/websites/firewall',
name: 'Firewall',
component: () => import('@/views/website/project/index.vue'),
meta: {
title: 'menu.firewall',
},
},
],
};

View File

@ -1,7 +0,0 @@
<template>
<LayoutContent></LayoutContent>
</template>
<script lang="ts" setup>
import LayoutContent from '@/layout/layout-content.vue';
</script>

View File

@ -1,5 +0,0 @@
<template>
<div></div>
</template>
<script lang="ts" setup></script>

View File

@ -1,33 +0,0 @@
<template>
<el-tabs tab-position="left" type="border-card" v-model="index">
<el-tab-pane :label="$t('website.currentSSL')">
<Current :id="id" v-if="index == '0'"></Current>
</el-tab-pane>
<el-tab-pane :label="$t('website.dnsAccount')">
<Account :id="id" v-if="index == '1'"></Account>
</el-tab-pane>
<el-tab-pane :label="$t('website.applySSL')">
<SSL :id="id" v-if="index == '2'"></SSL>
</el-tab-pane>
</el-tabs>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import Current from './current/index.vue';
import Account from './account/index.vue';
import SSL from './ssl/index.vue';
const props = defineProps({
id: {
type: Number,
default: 0,
},
});
const id = computed(() => {
return props.id;
});
let index = ref('0');
</script>

View File

@ -1,120 +0,0 @@
<template>
<div>
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()">
<template #toolbar>
<el-button type="primary" plain @click="openSSL()">{{ $t('commons.button.create') }}</el-button>
<el-button type="primary" plain @click="openAccount()">{{ $t('website.accountManage') }}</el-button>
</template>
<el-table-column :label="$t('website.domain')" fix show-overflow-tooltip prop="domain"></el-table-column>
<el-table-column :label="$t('website.brand')" fix show-overflow-tooltip prop="type"></el-table-column>
<el-table-column
prop="expireDate"
:label="$t('website.expireDate')"
:formatter="dateFromat"
show-overflow-tooltip
/>
<fu-table-operations
:ellipsis="1"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
<Account ref="accountRef"></Account>
<Create :id="id" ref="sslCreateRef"></Create>
</div>
</template>
<script lang="ts" setup>
import ComplexTable from '@/components/complex-table/index.vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { ApplySSL, DeleteSSL, SearchSSL } from '@/api/modules/website';
import Account from './account/index.vue';
import Create from './create/index.vue';
import { dateFromat } from '@/utils/util';
import i18n from '@/lang';
import { WebSite } from '@/api/interface/website';
import { useDeleteData } from '@/hooks/use-delete-data';
const props = defineProps({
id: {
type: Number,
default: 0,
},
});
const id = computed(() => {
return props.id;
});
const paginationConfig = reactive({
currentPage: 1,
pageSize: 20,
total: 0,
});
const accountRef = ref();
const sslCreateRef = ref();
let data = ref();
let loading = ref(false);
const buttons = [
{
label: i18n.global.t('website.deploySSL'),
click: function (row: WebSite.WebSite) {
applySSL(row.id);
},
},
{
label: i18n.global.t('app.delete'),
click: function (row: WebSite.WebSite) {
deleteSSL(row.id);
},
},
];
const search = () => {
const req = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
};
loading.value = true;
SearchSSL(req)
.then((res) => {
data.value = res.data.items || [];
paginationConfig.total = res.data.total;
})
.finally(() => {
loading.value = false;
});
};
const openAccount = () => {
accountRef.value.acceptParams();
};
const openSSL = () => {
sslCreateRef.value.acceptParams();
};
const deleteSSL = async (id: number) => {
loading.value = true;
await useDeleteData(DeleteSSL, id, 'commons.msg.delete', false);
loading.value = false;
search();
};
const applySSL = async (sslId: number) => {
loading.value = true;
const apply = {
websiteId: Number(id.value),
SSLId: sslId,
};
await useDeleteData(ApplySSL, apply, 'website.deploySSLHelper', false);
loading.value = false;
search();
};
onMounted(() => {
search();
});
</script>

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="open" :title="$t('website.accountManage')">
<el-dialog v-model="open" :title="$t('website.acmeAccountManage')">
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()" v-loading="loading">
<template #toolbar>
<el-button type="primary" plain @click="openCreate">{{ $t('commons.button.create') }}</el-button>
@ -66,9 +66,7 @@ const openCreate = () => {
};
const deleteAccount = async (id: number) => {
loading.value = true;
await useDeleteData(DeleteAcmeAccount, id, 'commons.msg.delete', false);
loading.value = false;
await useDeleteData(DeleteAcmeAccount, id, 'commons.msg.delete', loading.value);
search();
};

View File

@ -4,10 +4,16 @@
ref="sslForm"
label-position="right"
:model="ssl"
label-width="150px"
label-width="125px"
:rules="rules"
v-loading="loading"
>
<el-form-item :label="$t('website.primaryDomain')" prop="primaryDomain">
<el-input v-model="ssl.primaryDomain"></el-input>
</el-form-item>
<el-form-item :label="$t('website.otherDomains')" prop="otherDomains">
<el-input type="textarea" :autosize="{ minRows: 2, maxRows: 6 }" v-model="ssl.otherDomains"></el-input>
</el-form-item>
<el-form-item :label="$t('website.acmeAccount')" prop="acmeAccountId">
<el-select v-model="ssl.acmeAccountId">
<el-option
@ -21,7 +27,7 @@
<el-form-item :label="$t('website.provider')" prop="provider">
<el-radio-group v-model="ssl.provider">
<el-radio label="dnsAccount">{{ $t('website.dnsAccount') }}</el-radio>
<el-radio label="dnsCommon">{{ $t('website.dnsCommon') }}</el-radio>
<el-radio label="dnsManual">{{ $t('website.dnsCommon') }}</el-radio>
<el-radio label="http">HTTP</el-radio>
</el-radio-group>
</el-form-item>
@ -35,18 +41,18 @@
></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('website.domain')" prop="domains">
<!-- <el-form-item :label="$t('website.domain')" prop="domains">
<el-checkbox-group v-model="ssl.domains">
<el-checkbox v-for="domain in domains" :key="domain.domain" :label="domain.domain"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item>
</el-form-item> -->
<!-- <el-form-item>
<div>
<span>解析域名: {{ dnsResolve.key }}</span>
<span>记录值: {{ dnsResolve.value }}</span>
<span>类型: {{ dnsResolve.type }}</span>
</div>
</el-form-item>
</el-form-item> -->
</el-form>
<template #footer>
<span class="dialog-footer">
@ -61,7 +67,7 @@
<script lang="ts" setup>
import { WebSite } from '@/api/interface/website';
import { CreateSSL, GetDnsResolve, GetWebsite, SearchAcmeAccount, SearchDnsAccount } from '@/api/modules/website';
import { CreateSSL, SearchAcmeAccount, SearchDnsAccount } from '@/api/modules/website';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { ElMessage, FormInstance } from 'element-plus';
@ -90,26 +96,27 @@ let acmeReq = reactive({
});
let dnsAccounts = ref<WebSite.DnsAccount[]>();
let acmeAccounts = ref<WebSite.AcmeAccount[]>();
let domains = ref<WebSite.Domain[]>([]);
// let domains = ref<WebSite.Domain[]>([]);
let sslForm = ref<FormInstance>();
let rules = ref({
primaryDomain: [Rules.requiredInput],
acmeAccountId: [Rules.requiredSelectBusiness],
dnsAccountId: [Rules.requiredSelectBusiness],
provider: [Rules.requiredInput],
domains: [Rules.requiredSelect],
});
let ssl = ref({
domains: [],
primaryDomain: '',
otherDomains: '',
provider: 'dnsAccount',
websiteId: 0,
acmeAccountId: 0,
dnsAccountId: 0,
});
let dnsResolve = ref<WebSite.DNSResolve>({
key: '',
value: '',
type: '',
});
// let dnsResolve = ref<WebSite.DNSResolve>({
// key: '',
// value: '',
// type: '',
// });
let hasResolve = ref(false);
const em = defineEmits(['close']);
@ -121,7 +128,8 @@ const handleClose = () => {
const resetForm = () => {
sslForm.value?.resetFields();
ssl.value = {
domains: [],
primaryDomain: '',
otherDomains: '',
provider: 'dnsAccount',
websiteId: 0,
acmeAccountId: 0,
@ -132,7 +140,7 @@ const resetForm = () => {
const acceptParams = () => {
resetForm();
ssl.value.websiteId = Number(id.value);
getWebsite(id.value);
// getWebsite(id.value);
getAcmeAccounts();
getDnsAccounts();
open.value = true;
@ -154,32 +162,36 @@ const getDnsAccounts = async () => {
}
};
const getWebsite = async (id: number) => {
domains.value = (await GetWebsite(id)).data.domains || [];
};
// const getWebsite = async (id: number) => {
// domains.value = (await GetWebsite(id)).data.domains || [];
// };
const getDnsResolve = async (acmeAccountId: number, domains: string[]) => {
hasResolve.value = false;
const res = await GetDnsResolve({ acmeAccountId: acmeAccountId, domains: domains });
if (res.data) {
dnsResolve.value = res.data;
hasResolve.value = true;
}
};
// const getDnsResolve = async (acmeAccountId: number, domains: string[]) => {
// hasResolve.value = false;
// const res = await GetDnsResolve({ acmeAccountId: acmeAccountId, domains: domains });
// if (res.data) {
// dnsResolve.value = res.data;
// hasResolve.value = true;
// }
// };
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate(async (valid) => {
await formEl.validate((valid) => {
if (!valid) {
return;
}
if (ssl.value.provider != 'dnsCommon' || hasResolve.value) {
if (ssl.value.provider != 'dnsManual' || hasResolve.value) {
loading.value = true;
await CreateSSL(ssl.value);
ElMessage.success(i18n.global.t('commons.msg.createSuccess'));
loading.value = false;
CreateSSL(ssl.value)
.then(() => {
ElMessage.success(i18n.global.t('commons.msg.createSuccess'));
})
.finally(() => {
loading.value = false;
});
} else {
getDnsResolve(ssl.value.acmeAccountId, ssl.value.domains);
// getDnsResolve(ssl.value.acmeAccountId, ssl.value.domains);
}
});
};

View File

@ -14,7 +14,7 @@
></el-option>
</el-select>
</el-form-item>
<div v-if="account.type === 'Aliyun'">
<div v-if="account.type === 'AliYun'">
<el-form-item label="AccessKey" prop="authorization.accessKey">
<el-input v-model="account.authorization['accessKey']"></el-input>
</el-form-item>

View File

@ -1,5 +1,5 @@
<template>
<div>
<el-dialog v-model="open" :title="$t('website.dnsAccountManage')">
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()">
<template #toolbar>
<el-button type="primary" plain @click="openCreate">{{ $t('commons.button.create') }}</el-button>
@ -20,7 +20,7 @@
/>
</ComplexTable>
<Create ref="createRef" @close="search()"></Create>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
@ -40,6 +40,7 @@ const paginationConfig = reactive({
let data = ref<WebSite.DnsAccount[]>();
let createRef = ref();
let loading = ref(false);
let open = ref(false);
const buttons = [
{
@ -50,6 +51,11 @@ const buttons = [
},
];
const acceptParams = () => {
search();
open.value = true;
};
const search = () => {
const req = {
page: paginationConfig.currentPage,
@ -79,4 +85,8 @@ const deleteAccount = async (id: number) => {
onMounted(() => {
search();
});
defineExpose({
acceptParams,
});
</script>

View File

@ -0,0 +1,151 @@
<template>
<LayoutContent :header="$t('website.ssl')">
<ComplexTable :data="data" :pagination-config="paginationConfig" @search="search()">
<template #toolbar>
<el-button type="primary" plain @click="openSSL()">{{ $t('commons.button.create') }}</el-button>
<el-button type="primary" plain @click="openAcmeAccount()">
{{ $t('website.acmeAccountManage') }}
</el-button>
<el-button type="primary" plain @click="openDnsAccount()">
{{ $t('website.dnsAccountManage') }}
</el-button>
</template>
<el-table-column
:label="$t('website.domain')"
fix
show-overflow-tooltip
prop="primaryDomain"
></el-table-column>
<el-table-column
:label="$t('website.otherDomains')"
fix
show-overflow-tooltip
prop="domains"
></el-table-column>
<el-table-column :label="$t('website.brand')" fix show-overflow-tooltip prop="type"></el-table-column>
<el-table-column
prop="expireDate"
:label="$t('website.expireDate')"
:formatter="dateFromat"
show-overflow-tooltip
/>
<fu-table-operations
:ellipsis="1"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
<DnsAccount ref="dnsAccountRef"></DnsAccount>
<AcmeAccount ref="acmeAccountRef"></AcmeAccount>
<Create ref="sslCreateRef" @close="search()"></Create>
<Renew ref="renewRef" @close="search()"></Renew>
</LayoutContent>
</template>
<script lang="ts" setup>
import LayoutContent from '@/layout/layout-content.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { onMounted, reactive, ref } from 'vue';
import { DeleteSSL, SearchSSL } from '@/api/modules/website';
import DnsAccount from './dns-account/index.vue';
import AcmeAccount from './acme-account/index.vue';
import Renew from './renew/index.vue';
import Create from './create/index.vue';
import { dateFromat } from '@/utils/util';
import i18n from '@/lang';
import { WebSite } from '@/api/interface/website';
import { useDeleteData } from '@/hooks/use-delete-data';
const paginationConfig = reactive({
currentPage: 1,
pageSize: 20,
total: 0,
});
const acmeAccountRef = ref();
const dnsAccountRef = ref();
const sslCreateRef = ref();
const renewRef = ref();
let data = ref();
let loading = ref(false);
const buttons = [
{
label: i18n.global.t('website.renewSSL'),
click: function (row: WebSite.WebSite) {
openRenewSSL(row.id);
},
},
// {
// label: i18n.global.t('website.deploySSL'),
// click: function (row: WebSite.WebSite) {
// applySSL(row.id);
// },
// },
{
label: i18n.global.t('app.delete'),
click: function (row: WebSite.WebSite) {
deleteSSL(row.id);
},
},
];
const search = () => {
const req = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
};
loading.value = true;
SearchSSL(req)
.then((res) => {
data.value = res.data.items || [];
paginationConfig.total = res.data.total;
})
.finally(() => {
loading.value = false;
});
};
const openAcmeAccount = () => {
acmeAccountRef.value.acceptParams();
};
const openDnsAccount = () => {
dnsAccountRef.value.acceptParams();
};
const openSSL = () => {
sslCreateRef.value.acceptParams();
};
const openRenewSSL = (id: number) => {
renewRef.value.acceptParams(id);
};
const deleteSSL = async (id: number) => {
loading.value = true;
await useDeleteData(DeleteSSL, id, 'commons.msg.delete', false);
loading.value = false;
search();
};
// const renewSSL = async (id: number) => {
// loading.value = true;
// await useDeleteData(RenewSSL, { SSLId: id }, 'website.renewHelper', false);
// loading.value = false;
// search();
// };
// const applySSL = async (sslId: number) => {
// loading.value = true;
// const apply = {
// websiteId: 0,
// SSLId: sslId,
// };
// await useDeleteData(ApplySSL, apply, 'website.deploySSLHelper', false);
// loading.value = false;
// search();
// };
onMounted(() => {
search();
});
</script>

View File

@ -0,0 +1,55 @@
<template>
<el-dialog v-model="open" :title="$t('website.renewSSL')" width="30%" :before-close="handleClose">
<div style="text-align: center">
<span>是否确定续签?</span>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit()" :disabled="loading" :loading="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { RenewSSL } from '@/api/modules/website';
import i18n from '@/lang';
import { ElMessage } from 'element-plus';
import { reactive, ref } from 'vue';
let open = ref(false);
let loading = ref(false);
let renewReq = reactive({
SSLId: 0,
});
const em = defineEmits(['close']);
const handleClose = () => {
open.value = false;
em('close', false);
};
const acceptParams = async (id: number) => {
renewReq.SSLId = id;
open.value = true;
};
const submit = () => {
loading.value = true;
RenewSSL(renewReq)
.then(() => {
handleClose();
ElMessage.success(i18n.global.t('commons.msg.renewSuccess'));
})
.finally(() => {
loading.value = false;
});
};
defineExpose({
acceptParams,
});
</script>

View File

@ -4,9 +4,6 @@
<el-tab-pane label="基本" name="basic">
<Basic :id="id" v-if="index === 'basic'"></Basic>
</el-tab-pane>
<el-tab-pane label="证书" name="ssl">
<SSL :id="id" v-if="index === 'ssl'"></SSL>
</el-tab-pane>
<el-tab-pane label="安全">反代</el-tab-pane>
<el-tab-pane label="备份">反代</el-tab-pane>
<el-tab-pane label="源文">反代</el-tab-pane>
@ -18,7 +15,6 @@
import LayoutContent from '@/layout/layout-content.vue';
import { onMounted, ref } from 'vue';
import Basic from './basic/index.vue';
import SSL from './ssl/index.vue';
import router from '@/routers';
const props = defineProps({

View File

@ -68,7 +68,7 @@ const search = async () => {
};
const openConfig = (id: number) => {
router.push({ name: 'WebsiteConfig', params: { id: id, tab: 'ssl' } });
router.push({ name: 'WebsiteConfig', params: { id: id, tab: 'basic' } });
};
const buttons = [