mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 00:00:50 +00:00
feat: Add support for syncing self-signed certificates and manually u… (#13068)
* feat: Add support for syncing self-signed certificates and manually uploaded certificates to other nodes. * feat: Add support for syncing self-signed certificates and manually
This commit is contained in:
@@ -210,6 +210,27 @@ func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website SSL
|
||||
// @Summary Push ssl to nodes
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteSSLPush true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /websites/ssl/push [post]
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"推送证书到节点 [domain]","formatEN":"Push ssl to nodes [domain]"}
|
||||
func (b *BaseApi) PushWebsiteSSLToNode(c *gin.Context) {
|
||||
var req request.WebsiteSSLPush
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := websiteSSLService.PushToNode(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Website SSL
|
||||
// @Summary Upload ssl
|
||||
// @Accept json
|
||||
@@ -248,6 +269,8 @@ func (b *BaseApi) UploadSSLFile(c *gin.Context) {
|
||||
var req request.WebsiteSSLFileUpload
|
||||
|
||||
req.Description = c.PostForm("description")
|
||||
req.Nodes = c.PostForm("nodes")
|
||||
req.PushNode, _ = strconv.ParseBool(c.PostForm("pushNode"))
|
||||
sslID := c.PostForm("sslID")
|
||||
if sslID != "" {
|
||||
req.SSLID, _ = strconv.ParseUint(sslID, 10, 64)
|
||||
@@ -283,6 +306,8 @@ func (b *BaseApi) UploadSSLFile(c *gin.Context) {
|
||||
Certificate: string(certificateContent),
|
||||
Description: req.Description,
|
||||
SSLID: uint(req.SSLID),
|
||||
PushNode: req.PushNode,
|
||||
Nodes: req.Nodes,
|
||||
}
|
||||
|
||||
if err := websiteSSLService.Upload(uploadReq); err != nil {
|
||||
|
||||
@@ -126,6 +126,15 @@ type WebsiteSSLUpload struct {
|
||||
Type string `json:"type" validate:"required,oneof=paste local"`
|
||||
SSLID uint `json:"sslID"`
|
||||
Description string `json:"description"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
}
|
||||
|
||||
type WebsiteSSLPush struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type WebsiteCASearch struct {
|
||||
@@ -157,6 +166,8 @@ type WebsiteCAObtain struct {
|
||||
Description string `json:"description"`
|
||||
ExecShell bool `json:"execShell"`
|
||||
Shell string `json:"shell"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
}
|
||||
|
||||
type WebsiteCARenew struct {
|
||||
@@ -167,4 +178,6 @@ type WebsiteSSLFileUpload struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
SSLID uint64 `json:"sslID"`
|
||||
PushNode bool `json:"pushNode"`
|
||||
Nodes string `json:"nodes"`
|
||||
}
|
||||
|
||||
@@ -207,6 +207,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
|
||||
Description: req.Description,
|
||||
ExecShell: req.ExecShell,
|
||||
}
|
||||
setSSLPushConfig(websiteSSL, req.PushNode, req.Nodes)
|
||||
if req.ExecShell {
|
||||
websiteSSL.Shell = req.Shell
|
||||
}
|
||||
@@ -384,6 +385,11 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
|
||||
}
|
||||
}
|
||||
reloadSystemSSL(websiteSSL, logger)
|
||||
if websiteSSL.PushNode {
|
||||
if err = pushSSLToNode(websiteSSL, logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return websiteSSL, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@@ -104,6 +105,7 @@ type IWebsiteSSLService interface {
|
||||
Delete(ids []uint) error
|
||||
Update(update request.WebsiteSSLUpdate) error
|
||||
Upload(req request.WebsiteSSLUpload) error
|
||||
PushToNode(req request.WebsiteSSLPush) error
|
||||
ObtainSSL(apply request.WebsiteSSLApply) error
|
||||
AutoRenewSSL(id uint) error
|
||||
SyncForRestart() error
|
||||
@@ -214,10 +216,7 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
|
||||
}
|
||||
websiteSSL.Dir = create.Dir
|
||||
}
|
||||
if create.PushNode && global.IsMaster && len(create.Nodes) > 0 {
|
||||
websiteSSL.PushNode = true
|
||||
websiteSSL.Nodes = create.Nodes
|
||||
}
|
||||
setSSLPushConfig(&websiteSSL, create.PushNode, create.Nodes)
|
||||
|
||||
var domains []string
|
||||
if create.OtherDomains != "" {
|
||||
@@ -282,6 +281,47 @@ func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{
|
||||
logger.Println(i18n.GetMsgWithMap(msgKey, params))
|
||||
}
|
||||
|
||||
func normalizeSSLPushConfig(pushNode bool, nodes string) (bool, string) {
|
||||
nodes = strings.TrimSpace(nodes)
|
||||
if !pushNode || nodes == "" {
|
||||
return false, ""
|
||||
}
|
||||
return true, nodes
|
||||
}
|
||||
|
||||
func setSSLPushConfig(websiteSSL *model.WebsiteSSL, pushNode bool, nodes string) {
|
||||
pushNode, nodes = normalizeSSLPushConfig(pushNode, nodes)
|
||||
if !global.IsMaster || !xpack.MultiNodeProvider.IsXpack() {
|
||||
pushNode = false
|
||||
nodes = ""
|
||||
}
|
||||
websiteSSL.PushNode = pushNode
|
||||
websiteSSL.Nodes = nodes
|
||||
}
|
||||
|
||||
func pushSSLToNode(websiteSSL *model.WebsiteSSL, logger *log.Logger) error {
|
||||
printSSLLog(logger, "StartPushSSLToNode", nil)
|
||||
if err := xpack.MultiNodeProvider.PushSSLToNode(websiteSSL); err != nil {
|
||||
printSSLLog(logger, "PushSSLToNodeFailed", map[string]interface{}{"err": err.Error()})
|
||||
return err
|
||||
}
|
||||
printSSLLog(logger, "PushSSLToNodeSuccess", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func pushSSLToNodeWithNewLogger(websiteSSL *model.WebsiteSSL) error {
|
||||
if !websiteSSL.PushNode {
|
||||
return nil
|
||||
}
|
||||
logFile, logger := newWebsiteSSLLogger(websiteSSL, false)
|
||||
if logFile != nil {
|
||||
defer func() {
|
||||
_ = logFile.Close()
|
||||
}()
|
||||
}
|
||||
return pushSSLToNode(websiteSSL, logger)
|
||||
}
|
||||
|
||||
func newWebsiteSSLLogger(websiteSSL *model.WebsiteSSL, autoRenew bool) (*os.File, *log.Logger) {
|
||||
flags := os.O_CREATE | os.O_WRONLY | os.O_TRUNC
|
||||
if autoRenew {
|
||||
@@ -567,12 +607,9 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error {
|
||||
}
|
||||
reloadSystemSSL(websiteSSL, logger)
|
||||
if websiteSSL.PushNode {
|
||||
printSSLLog(logger, "StartPushSSLToNode", nil)
|
||||
if err = xpack.MultiNodeProvider.PushSSLToNode(websiteSSL); err != nil {
|
||||
printSSLLog(logger, "PushSSLToNodeFailed", map[string]interface{}{"err": err.Error()})
|
||||
if err = pushSSLToNode(websiteSSL, logger); err != nil {
|
||||
return
|
||||
}
|
||||
printSSLLog(logger, "PushSSLToNodeSuccess", nil)
|
||||
}
|
||||
}(logFile, logger)
|
||||
|
||||
@@ -740,13 +777,13 @@ func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error {
|
||||
} else {
|
||||
updateParams["shell"] = ""
|
||||
}
|
||||
if update.PushNode {
|
||||
updateParams["push_node"] = true
|
||||
updateParams["nodes"] = update.Nodes
|
||||
} else {
|
||||
updateParams["push_node"] = false
|
||||
updateParams["nodes"] = ""
|
||||
pushNode, nodes := normalizeSSLPushConfig(update.PushNode, update.Nodes)
|
||||
if !global.IsMaster || !xpack.MultiNodeProvider.IsXpack() {
|
||||
pushNode = false
|
||||
nodes = ""
|
||||
}
|
||||
updateParams["push_node"] = pushNode
|
||||
updateParams["nodes"] = nodes
|
||||
|
||||
if websiteSSL.Provider != constant.SelfSigned && websiteSSL.Provider != constant.Manual {
|
||||
acmeAccount, err := websiteAcmeRepo.GetFirst(repo.WithByID(update.AcmeAccountID))
|
||||
@@ -797,6 +834,7 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
|
||||
Description: req.Description,
|
||||
Status: constant.SSLReady,
|
||||
}
|
||||
setSSLPushConfig(websiteSSL, req.PushNode, req.Nodes)
|
||||
var err error
|
||||
if req.SSLID > 0 {
|
||||
websiteSSL, err = websiteSSLRepo.GetFirst(repo.WithByID(req.SSLID))
|
||||
@@ -804,6 +842,7 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
|
||||
return err
|
||||
}
|
||||
websiteSSL.Description = req.Description
|
||||
setSSLPushConfig(websiteSSL, req.PushNode, req.Nodes)
|
||||
}
|
||||
if req.Type == "local" {
|
||||
fileOp := files.NewFileOp()
|
||||
@@ -891,9 +930,69 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
|
||||
if err := UpdateSSLConfig(*websiteSSL); err != nil {
|
||||
return err
|
||||
}
|
||||
return websiteSSLRepo.Save(websiteSSL)
|
||||
if err := websiteSSLRepo.Save(websiteSSL); err != nil {
|
||||
return err
|
||||
}
|
||||
return pushSSLToNodeWithNewLogger(websiteSSL)
|
||||
}
|
||||
return websiteSSLRepo.Create(context.Background(), websiteSSL)
|
||||
if err := websiteSSLRepo.Create(context.Background(), websiteSSL); err != nil {
|
||||
return err
|
||||
}
|
||||
return pushSSLToNodeWithNewLogger(websiteSSL)
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) PushToNode(req request.WebsiteSSLPush) error {
|
||||
if !global.IsMaster {
|
||||
return errors.New("only master node can push SSL to nodes")
|
||||
}
|
||||
if !xpack.MultiNodeProvider.IsXpack() {
|
||||
return errors.New("SSL node push is an XPack feature")
|
||||
}
|
||||
pushNode, nodes := normalizeSSLPushConfig(req.PushNode, req.Nodes)
|
||||
if !pushNode {
|
||||
return errors.New("please select nodes to push SSL")
|
||||
}
|
||||
websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if websiteSSL.Provider == constant.FromMaster {
|
||||
return errors.New("SSL imported from master node can not be pushed")
|
||||
}
|
||||
if websiteSSL.Status != constant.SSLReady {
|
||||
return errors.New("only ready SSL can be pushed")
|
||||
}
|
||||
if task.CheckResourceTaskIsExecuting(task.TaskPush, task.TaskScopeWebsite, websiteSSL.ID) {
|
||||
return buserr.New("TaskIsExecuting")
|
||||
}
|
||||
if err := websiteSSLRepo.SaveByMap(websiteSSL, map[string]interface{}{
|
||||
"push_node": pushNode,
|
||||
"nodes": nodes,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
websiteSSL.PushNode = pushNode
|
||||
websiteSSL.Nodes = nodes
|
||||
|
||||
pushTask, err := task.NewTaskWithOps(websiteSSL.PrimaryDomain, task.TaskPush, task.TaskScopeWebsite, req.TaskID, websiteSSL.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pushTask.AddSubTask(i18n.GetMsgByKey("StartPushSSLToNode"), func(t *task.Task) error {
|
||||
t.Log(i18n.GetMsgByKey("StartPushSSLToNode"))
|
||||
if err := xpack.MultiNodeProvider.PushSSLToNode(websiteSSL); err != nil {
|
||||
t.Log(i18n.GetMsgWithMap("PushSSLToNodeFailed", map[string]interface{}{"err": err.Error()}))
|
||||
return err
|
||||
}
|
||||
t.Log(i18n.GetMsgByKey("PushSSLToNodeSuccess"))
|
||||
return nil
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := pushTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("push ssl to node failed, sslID: %d, err: %v", websiteSSL.ID, err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) DownloadFile(id uint) (*os.File, error) {
|
||||
|
||||
@@ -21,6 +21,7 @@ func (a *WebsiteSSLRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
groupRouter.GET("/website/:websiteId", baseApi.GetWebsiteSSLByWebsiteId)
|
||||
groupRouter.GET("/:id", baseApi.GetWebsiteSSLById)
|
||||
groupRouter.POST("/update", baseApi.UpdateWebsiteSSL)
|
||||
groupRouter.POST("/push", baseApi.PushWebsiteSSLToNode)
|
||||
groupRouter.POST("/upload", baseApi.UploadWebsiteSSL)
|
||||
groupRouter.POST("/obtain", baseApi.ApplyWebsiteSSL)
|
||||
groupRouter.POST("/download", baseApi.DownloadWebsiteSSL)
|
||||
|
||||
@@ -295,6 +295,15 @@ export namespace Website {
|
||||
keyType: string;
|
||||
pushDir: boolean;
|
||||
dir: string;
|
||||
pushNode?: boolean;
|
||||
nodes?: string;
|
||||
}
|
||||
|
||||
export interface SSLPush {
|
||||
id: number;
|
||||
pushNode: boolean;
|
||||
nodes: string;
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface AcmeAccount extends CommonModel {
|
||||
@@ -578,6 +587,9 @@ export namespace Website {
|
||||
certificatePath: string;
|
||||
type: string;
|
||||
sslID: number;
|
||||
description?: string;
|
||||
pushNode?: boolean;
|
||||
nodes?: string;
|
||||
}
|
||||
|
||||
export interface SSLObtain {
|
||||
@@ -620,6 +632,8 @@ export namespace Website {
|
||||
pushDir: boolean;
|
||||
dir: string;
|
||||
description: string;
|
||||
pushNode?: boolean;
|
||||
nodes?: string;
|
||||
}
|
||||
|
||||
export interface RenewSSLByCA {
|
||||
|
||||
@@ -148,6 +148,10 @@ export const updateSSL = (req: Website.SSLUpdate) => {
|
||||
return http.post<any>(`/websites/ssl/update`, req);
|
||||
};
|
||||
|
||||
export const pushSSLToNode = (req: Website.SSLPush) => {
|
||||
return http.post<any>(`/websites/ssl/push`, req);
|
||||
};
|
||||
|
||||
export const getDnsResolve = (req: Website.DNSResolveReq) => {
|
||||
return http.post<Website.DNSResolve[]>(`/websites/ssl/resolve`, req, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
@@ -62,6 +62,14 @@
|
||||
{{ $t('ssl.shellHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<PushToNode
|
||||
v-if="isMaster && isXpackOrEE"
|
||||
:push-node="obtain.pushNode"
|
||||
:nodes="obtain.pushNodes"
|
||||
type="ssl"
|
||||
@update:push-node="obtain.pushNode = $event"
|
||||
@update:nodes="obtain.pushNodes = $event"
|
||||
/>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -84,8 +92,20 @@ import i18n from '@/lang';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { ref } from 'vue';
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import { KeyTypes } from '@/global/mimetype';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isMaster, isXpackOrEE } = useGlobalStore();
|
||||
|
||||
const PushToNode = defineAsyncComponent(async () => {
|
||||
const modules = import.meta.glob('@/xpack/views/ssl/index.vue');
|
||||
const loader = modules['/src/xpack/views/ssl/index.vue'];
|
||||
if (loader) {
|
||||
return ((await loader()) as any).default;
|
||||
}
|
||||
return { template: '<div></div>' };
|
||||
});
|
||||
|
||||
const open = ref(false);
|
||||
const fileRef = ref();
|
||||
@@ -100,6 +120,7 @@ const rules = ref({
|
||||
time: [Rules.integerNumber, checkNumberRange(1, 10000)],
|
||||
shell: [Rules.requiredInput],
|
||||
description: [checkMaxLength(128)],
|
||||
pushNodes: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
const initData = () => ({
|
||||
@@ -114,6 +135,9 @@ const initData = () => ({
|
||||
description: '',
|
||||
execShell: false,
|
||||
shell: '',
|
||||
pushNode: false,
|
||||
pushNodes: [] as string[],
|
||||
nodes: '',
|
||||
});
|
||||
const obtain = ref(initData());
|
||||
|
||||
@@ -144,6 +168,7 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
obtain.value.nodes = obtain.value.pushNode ? obtain.value.pushNodes.join(',') : '';
|
||||
|
||||
obtainSSLByCA(obtain.value)
|
||||
.then(() => {
|
||||
|
||||
@@ -182,13 +182,47 @@
|
||||
<Log ref="logRef" @close="search()" :heightDiff="220" />
|
||||
<CA ref="caRef" @close="search()" />
|
||||
<Obtain ref="obtainRef" @close="search()" @submit="openLog" />
|
||||
<DrawerPro v-model="pushOpen" :header="$t('commons.button.sync')" size="large" @close="handlePushClose">
|
||||
<el-form
|
||||
ref="pushFormRef"
|
||||
label-position="top"
|
||||
:model="pushForm"
|
||||
:rules="pushRules"
|
||||
v-loading="pushLoading"
|
||||
>
|
||||
<PushToNode
|
||||
v-if="isMaster && isXpackOrEE"
|
||||
:push-node="pushForm.pushNode"
|
||||
:nodes="pushForm.pushNodes"
|
||||
type="ssl"
|
||||
@update:push-node="pushForm.pushNode = $event"
|
||||
@update:nodes="pushForm.pushNodes = $event"
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handlePushClose" :disabled="pushLoading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-permission="'website_cert_manage'"
|
||||
type="primary"
|
||||
@click="submitPush"
|
||||
:disabled="pushLoading"
|
||||
>
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
<TaskLog ref="taskLogRef" @close="search()" />
|
||||
</LayoutContent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { deleteSSL, downloadFile, searchSSL, updateSSL } from '@/api/modules/website';
|
||||
import { defineAsyncComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { deleteSSL, downloadFile, pushSSLToNode, searchSSL, updateSSL } from '@/api/modules/website';
|
||||
import DnsAccount from './dns-account/index.vue';
|
||||
import AcmeAccount from './acme-account/index.vue';
|
||||
import CA from './ca/index.vue';
|
||||
@@ -206,9 +240,23 @@ import Obtain from './obtain/index.vue';
|
||||
import MsgInfo from '@/components/msg-info/index.vue';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { useOperateNodeContext } from '@/composables/useOperateNodeContext';
|
||||
import TaskLog from '@/components/log/task/index.vue';
|
||||
import { newUUID } from '@/utils/id';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { FormInstance } from 'element-plus';
|
||||
|
||||
const { currentNode, isMobile } = useGlobalStore();
|
||||
const { currentNode, isMobile, isMaster, isXpackOrEE } = useGlobalStore();
|
||||
useOperateNodeContext(currentNode);
|
||||
|
||||
const PushToNode = defineAsyncComponent(async () => {
|
||||
const modules = import.meta.glob('@/xpack/views/ssl/index.vue');
|
||||
const loader = modules['/src/xpack/views/ssl/index.vue'];
|
||||
if (loader) {
|
||||
return ((await loader()) as any).default;
|
||||
}
|
||||
return { template: '<div></div>' };
|
||||
});
|
||||
|
||||
const paginationConfig = reactive({
|
||||
cacheSizeKey: 'ssl-page-size',
|
||||
currentPage: 1,
|
||||
@@ -225,8 +273,12 @@ const opRef = ref();
|
||||
const sslUploadRef = ref();
|
||||
const applyRef = ref();
|
||||
const logRef = ref();
|
||||
const taskLogRef = ref();
|
||||
const caRef = ref();
|
||||
const obtainRef = ref();
|
||||
const pushFormRef = ref<FormInstance>();
|
||||
const pushOpen = ref(false);
|
||||
const pushLoading = ref(false);
|
||||
let selects = ref<any>([]);
|
||||
const columns = ref([]);
|
||||
const req = reactive({
|
||||
@@ -234,6 +286,14 @@ const req = reactive({
|
||||
orderBy: 'updated_at',
|
||||
order: 'descending',
|
||||
});
|
||||
const pushForm = ref({
|
||||
id: 0,
|
||||
pushNode: true,
|
||||
pushNodes: [] as string[],
|
||||
});
|
||||
const pushRules = ref({
|
||||
pushNodes: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
const routerButton = [
|
||||
{
|
||||
@@ -292,6 +352,19 @@ const buttons = [
|
||||
return row.provider != 'manual';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.sync'),
|
||||
permission: true,
|
||||
disabled: function (row: Website.SSLDTO) {
|
||||
return row.status !== 'ready';
|
||||
},
|
||||
click: function (row: Website.SSLDTO) {
|
||||
openPush(row);
|
||||
},
|
||||
show: function (row: Website.SSLDTO) {
|
||||
return isMaster.value && isXpackOrEE.value && row.provider !== 'fromMaster';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.download'),
|
||||
click: function (row: Website.SSLDTO) {
|
||||
@@ -396,6 +469,58 @@ const openSSLLog = (row: Website.SSL) => {
|
||||
logRef.value.acceptParams({ id: row.id, type: 'ssl', tail: row.status === 'applying' });
|
||||
};
|
||||
|
||||
const parsePushNodes = (nodes: string) => {
|
||||
return nodes
|
||||
? nodes
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item !== '')
|
||||
: [];
|
||||
};
|
||||
|
||||
const openPush = (row: Website.SSLDTO) => {
|
||||
pushForm.value = {
|
||||
id: row.id,
|
||||
pushNode: true,
|
||||
pushNodes: parsePushNodes(row.nodes),
|
||||
};
|
||||
pushOpen.value = true;
|
||||
};
|
||||
|
||||
const handlePushClose = () => {
|
||||
pushOpen.value = false;
|
||||
pushFormRef.value?.resetFields();
|
||||
pushForm.value = {
|
||||
id: 0,
|
||||
pushNode: true,
|
||||
pushNodes: [],
|
||||
};
|
||||
};
|
||||
|
||||
const submitPush = async () => {
|
||||
if (!pushForm.value.pushNode || pushForm.value.pushNodes.length === 0) {
|
||||
MsgError(i18n.global.t('commons.rule.requiredSelect'));
|
||||
return;
|
||||
}
|
||||
await pushFormRef.value?.validate();
|
||||
const taskID = newUUID();
|
||||
pushLoading.value = true;
|
||||
pushSSLToNode({
|
||||
id: pushForm.value.id,
|
||||
pushNode: pushForm.value.pushNode,
|
||||
nodes: pushForm.value.pushNodes.join(','),
|
||||
taskID,
|
||||
})
|
||||
.then(() => {
|
||||
handlePushClose();
|
||||
taskLogRef.value.openWithTaskID(taskID);
|
||||
search();
|
||||
})
|
||||
.finally(() => {
|
||||
pushLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const openCA = () => {
|
||||
caRef.value.acceptParams();
|
||||
};
|
||||
|
||||
@@ -69,6 +69,14 @@
|
||||
<el-form-item :label="$t('website.remark')" prop="description">
|
||||
<el-input v-model="ssl.description"></el-input>
|
||||
</el-form-item>
|
||||
<PushToNode
|
||||
v-if="isMaster && isXpackOrEE"
|
||||
:push-node="ssl.pushNode"
|
||||
:nodes="ssl.pushNodes"
|
||||
type="ssl"
|
||||
@update:push-node="ssl.pushNode = $event"
|
||||
@update:nodes="ssl.pushNodes = $event"
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -89,9 +97,21 @@ import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { ref } from 'vue';
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
|
||||
const { isMaster, isXpackOrEE } = useGlobalStore();
|
||||
|
||||
const PushToNode = defineAsyncComponent(async () => {
|
||||
const modules = import.meta.glob('@/xpack/views/ssl/index.vue');
|
||||
const loader = modules['/src/xpack/views/ssl/index.vue'];
|
||||
if (loader) {
|
||||
return ((await loader()) as any).default;
|
||||
}
|
||||
return { template: '<div></div>' };
|
||||
});
|
||||
|
||||
const open = ref(false);
|
||||
const keyFileRef = ref();
|
||||
@@ -111,6 +131,7 @@ const rules = ref({
|
||||
type: [Rules.requiredSelect],
|
||||
certificateFile: [Rules.requiredInput],
|
||||
privateKeyFile: [Rules.requiredInput],
|
||||
pushNodes: [Rules.requiredSelect],
|
||||
});
|
||||
const initData = () => ({
|
||||
privateKey: '',
|
||||
@@ -120,6 +141,9 @@ const initData = () => ({
|
||||
type: 'paste',
|
||||
sslID: 0,
|
||||
description: '',
|
||||
pushNode: false,
|
||||
pushNodes: [] as string[],
|
||||
nodes: '',
|
||||
privateKeyFile: null as File | null,
|
||||
certificateFile: null as File | null,
|
||||
});
|
||||
@@ -155,6 +179,13 @@ const acceptParams = (websiteSSL?: Website.SSLDTO) => {
|
||||
ssl.value.description = websiteSSL.description;
|
||||
ssl.value.privateKeyPath = websiteSSL.privateKeyPath;
|
||||
ssl.value.certificatePath = websiteSSL.certPath;
|
||||
ssl.value.pushNode = websiteSSL.pushNode;
|
||||
ssl.value.pushNodes = websiteSSL.nodes
|
||||
? websiteSSL.nodes
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item !== '')
|
||||
: [];
|
||||
if (ssl.value.certificatePath != '' && ssl.value.privateKeyPath != '') {
|
||||
ssl.value.type = 'local';
|
||||
}
|
||||
@@ -174,11 +205,14 @@ const submit = async () => {
|
||||
try {
|
||||
await sslForm.value?.validate();
|
||||
loading.value = true;
|
||||
ssl.value.nodes = ssl.value.pushNode ? ssl.value.pushNodes.join(',') : '';
|
||||
if (ssl.value.type === 'upload') {
|
||||
const formData = new FormData();
|
||||
formData.append('type', ssl.value.type);
|
||||
formData.append('description', ssl.value.description);
|
||||
formData.append('sslID', ssl.value.sslID.toString());
|
||||
formData.append('pushNode', String(ssl.value.pushNode));
|
||||
formData.append('nodes', ssl.value.nodes);
|
||||
|
||||
if (ssl.value.privateKeyFile) {
|
||||
formData.append('privateKeyFile', ssl.value.privateKeyFile);
|
||||
|
||||
Reference in New Issue
Block a user