mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-14 01:34:47 +08:00
feat: 证书增加下载功能 (#3090)
Refs https://github.com/1Panel-dev/1Panel/issues/2767
This commit is contained in:
parent
59c216c184
commit
86bc75ff28
@ -1,7 +1,10 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@ -213,3 +216,32 @@ func (b *BaseApi) UploadWebsiteSSL(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
// @Tags Website SSL
|
||||
// @Summary Download SSL file
|
||||
// @Description 下载证书文件
|
||||
// @Accept json
|
||||
// @Param request body request.WebsiteResourceReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /websites/ssl/download [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":"download ssl file [domain]"}
|
||||
func (b *BaseApi) DownloadWebsiteSSL(c *gin.Context) {
|
||||
var req request.WebsiteResourceReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
file, err := websiteSSLService.DownloadFile(req.ID)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
c.Header("Content-Length", strconv.FormatInt(info.Size(), 10))
|
||||
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name()))
|
||||
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
@ -41,6 +42,7 @@ type IWebsiteSSLService interface {
|
||||
Upload(req request.WebsiteSSLUpload) error
|
||||
ObtainSSL(apply request.WebsiteSSLApply) error
|
||||
SyncForRestart() error
|
||||
DownloadFile(id uint) (*os.File, error)
|
||||
}
|
||||
|
||||
func NewIWebsiteSSLService() IWebsiteSSLService {
|
||||
@ -425,6 +427,34 @@ func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error {
|
||||
return websiteSSLRepo.Create(context.Background(), websiteSSL)
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) DownloadFile(id uint) (*os.File, error) {
|
||||
websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
dir := path.Join(global.CONF.System.BaseDir, "1panel/tmp/ssl", websiteSSL.PrimaryDomain)
|
||||
if fileOp.Stat(dir) {
|
||||
if err = fileOp.DeleteDir(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = fileOp.CreateDir(dir, 0666); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = fileOp.WriteFile(path.Join(dir, "fullchain.pem"), strings.NewReader(websiteSSL.Pem), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = fileOp.WriteFile(path.Join(dir, "privkey.pem"), strings.NewReader(websiteSSL.PrivateKey), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileName := websiteSSL.PrimaryDomain + ".zip"
|
||||
if err = fileOp.Compress([]string{path.Join(dir, "fullchain.pem"), path.Join(dir, "privkey.pem")}, dir, fileName, files.SdkZip); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.Open(path.Join(dir, fileName))
|
||||
}
|
||||
|
||||
func (w WebsiteSSLService) SyncForRestart() error {
|
||||
sslList, err := websiteSSLRepo.List()
|
||||
if err != nil {
|
||||
|
@ -24,5 +24,6 @@ func (a *WebsiteSSLRouter) InitWebsiteSSLRouter(Router *gin.RouterGroup) {
|
||||
groupRouter.POST("/update", baseApi.UpdateWebsiteSSL)
|
||||
groupRouter.POST("/upload", baseApi.UploadWebsiteSSL)
|
||||
groupRouter.POST("/obtain", baseApi.ApplyWebsiteSSL)
|
||||
groupRouter.POST("/download", baseApi.DownloadWebsiteSSL)
|
||||
}
|
||||
}
|
||||
|
@ -12879,6 +12879,57 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ssl/download": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "下载证书文件",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website SSL"
|
||||
],
|
||||
"summary": "Download SSL file",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteResourceReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_ssls",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "primary_domain",
|
||||
"output_value": "domain"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "download ssl file [domain]",
|
||||
"formatZH": "下载证书文件 [domain]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ssl/obtain": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -17478,6 +17529,9 @@ const docTemplate = `{
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"dir": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -19522,14 +19576,33 @@ const docTemplate = `{
|
||||
"request.WebsiteSSLUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id"
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"autoRenew": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"privateKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"autoRenew",
|
||||
"description",
|
||||
"certificate",
|
||||
"privateKey"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -19551,6 +19624,9 @@ const docTemplate = `{
|
||||
"privateKeyPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"sslID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
@ -12872,6 +12872,57 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ssl/download": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "下载证书文件",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Website SSL"
|
||||
],
|
||||
"summary": "Download SSL file",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.WebsiteResourceReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"x-panel-log": {
|
||||
"BeforeFunctions": [
|
||||
{
|
||||
"db": "website_ssls",
|
||||
"input_column": "id",
|
||||
"input_value": "id",
|
||||
"isList": false,
|
||||
"output_column": "primary_domain",
|
||||
"output_value": "domain"
|
||||
}
|
||||
],
|
||||
"bodyKeys": [
|
||||
"id"
|
||||
],
|
||||
"formatEN": "download ssl file [domain]",
|
||||
"formatZH": "下载证书文件 [domain]",
|
||||
"paramKeys": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"/websites/ssl/obtain": {
|
||||
"post": {
|
||||
"security": [
|
||||
@ -17471,6 +17522,9 @@
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"dir": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -19515,14 +19569,33 @@
|
||||
"request.WebsiteSSLUpdate": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id"
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"autoRenew": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"certificate": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"privateKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"autoRenew",
|
||||
"description",
|
||||
"certificate",
|
||||
"privateKey"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -19544,6 +19617,9 @@
|
||||
"privateKeyPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"sslID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
@ -2800,6 +2800,8 @@ definitions:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
dir:
|
||||
type: string
|
||||
dnsAccount:
|
||||
@ -4178,10 +4180,24 @@ definitions:
|
||||
properties:
|
||||
autoRenew:
|
||||
type: boolean
|
||||
certificate:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
privateKey:
|
||||
type: string
|
||||
type:
|
||||
enum:
|
||||
- autoRenew
|
||||
- description
|
||||
- certificate
|
||||
- privateKey
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
type: object
|
||||
request.WebsiteSSLUpload:
|
||||
properties:
|
||||
@ -4193,6 +4209,8 @@ definitions:
|
||||
type: string
|
||||
privateKeyPath:
|
||||
type: string
|
||||
sslID:
|
||||
type: integer
|
||||
type:
|
||||
enum:
|
||||
- paste
|
||||
@ -12874,6 +12892,39 @@ paths:
|
||||
formatEN: Delete ssl [domain]
|
||||
formatZH: 删除 ssl [domain]
|
||||
paramKeys: []
|
||||
/websites/ssl/download:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 下载证书文件
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.WebsiteResourceReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Download SSL file
|
||||
tags:
|
||||
- Website SSL
|
||||
x-panel-log:
|
||||
BeforeFunctions:
|
||||
- db: website_ssls
|
||||
input_column: id
|
||||
input_value: id
|
||||
isList: false
|
||||
output_column: primary_domain
|
||||
output_value: domain
|
||||
bodyKeys:
|
||||
- id
|
||||
formatEN: download ssl file [domain]
|
||||
formatZH: 下载证书文件 [domain]
|
||||
paramKeys: []
|
||||
/websites/ssl/obtain:
|
||||
post:
|
||||
consumes:
|
||||
|
@ -490,4 +490,8 @@ export namespace Website {
|
||||
export interface RenewSSLByCA {
|
||||
SSLID: number;
|
||||
}
|
||||
|
||||
export interface SSLDownload {
|
||||
id: number;
|
||||
}
|
||||
}
|
||||
|
@ -263,3 +263,10 @@ export const DeleteCA = (req: Website.DelReq) => {
|
||||
export const RenewSSLByCA = (req: Website.RenewSSLByCA) => {
|
||||
return http.post<any>(`/websites/ca/renew`, req);
|
||||
};
|
||||
|
||||
export const DownloadFile = (params: Website.SSLDownload) => {
|
||||
return http.download<BlobPart>(`/websites/ssl/download`, params, {
|
||||
responseType: 'blob',
|
||||
timeout: TimeoutEnum.T_40S,
|
||||
});
|
||||
};
|
||||
|
@ -3,15 +3,13 @@
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('ssl.detail')" :back="handleClose" />
|
||||
</template>
|
||||
<div>
|
||||
<div v-loading="loading">
|
||||
<el-radio-group v-model="curr">
|
||||
<el-radio-button label="detail">{{ $t('ssl.msg') }}</el-radio-button>
|
||||
<el-radio-button label="ssl">{{ $t('ssl.ssl') }}</el-radio-button>
|
||||
<el-radio-button label="key">{{ $t('ssl.key') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<br />
|
||||
<br />
|
||||
<div v-if="curr === 'detail'">
|
||||
<div v-if="curr === 'detail'" class="mt-5">
|
||||
<el-descriptions border :column="1">
|
||||
<el-descriptions-item :label="$t('website.primaryDomain')">
|
||||
{{ ssl.primaryDomain }}
|
||||
@ -52,14 +50,14 @@
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<div v-else-if="curr === 'ssl'">
|
||||
<div v-else-if="curr === 'ssl'" class="mt-5">
|
||||
<el-input v-model="ssl.pem" :autosize="{ minRows: 15, maxRows: 30 }" type="textarea" id="textArea" />
|
||||
<div>
|
||||
<br />
|
||||
<el-button type="primary" @click="copyText(ssl.pem)">{{ $t('file.copy') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-else class="mt-5">
|
||||
<el-input
|
||||
v-model="ssl.privateKey"
|
||||
:autosize="{ minRows: 15, maxRows: 30 }"
|
||||
@ -88,6 +86,7 @@ const open = ref(false);
|
||||
const id = ref(0);
|
||||
const curr = ref('detail');
|
||||
const ssl = ref<any>({});
|
||||
const loading = ref(false);
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
|
@ -150,7 +150,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, computed } from 'vue';
|
||||
import OpDialog from '@/components/del-dialog/index.vue';
|
||||
import { DeleteSSL, SearchSSL, UpdateSSL } from '@/api/modules/website';
|
||||
import { DeleteSSL, DownloadFile, 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';
|
||||
@ -229,6 +229,12 @@ const buttons = [
|
||||
return row.provider == 'manual';
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('file.download'),
|
||||
click: function (row: Website.SSLDTO) {
|
||||
onDownload(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('commons.button.delete'),
|
||||
click: function (row: Website.SSLDTO) {
|
||||
@ -237,6 +243,23 @@ const buttons = [
|
||||
},
|
||||
];
|
||||
|
||||
const onDownload = (ssl: Website.SSLDTO) => {
|
||||
loading.value = true;
|
||||
DownloadFile({ id: ssl.id })
|
||||
.then((res) => {
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([res]));
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = downloadUrl;
|
||||
a.download = ssl.primaryDomain + '.zip';
|
||||
const event = new MouseEvent('click');
|
||||
a.dispatchEvent(event);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user