mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 22:26:38 +08:00
feat: 添加获取TLS证书内容的API和前端支持,更新相关组件
This commit is contained in:
@@ -65,6 +65,40 @@ func GetTLSCertificate(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetTLSCertificateContent godoc
|
||||
// @Summary Get TLS certificate PEM content
|
||||
// @Tags TLSCertificates
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param id path int true "Certificate ID"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} map[string]interface{}
|
||||
// @Router /api/tls-certificates/{id}/content [get]
|
||||
func GetTLSCertificateContent(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"success": false,
|
||||
"message": "invalid request",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
content, err := service.GetTLSCertificateContent(uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": content,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateTLSCertificate godoc
|
||||
// @Summary Create TLS certificate from PEM
|
||||
// @Tags TLSCertificates
|
||||
|
||||
@@ -93,6 +93,7 @@ func SetApiRouter(router *gin.Engine) {
|
||||
{
|
||||
tlsCertificateRoute.GET("/", controller.GetTLSCertificates)
|
||||
tlsCertificateRoute.GET("/:id", controller.GetTLSCertificate)
|
||||
tlsCertificateRoute.GET("/:id/content", controller.GetTLSCertificateContent)
|
||||
tlsCertificateRoute.POST("/", controller.CreateTLSCertificate)
|
||||
tlsCertificateRoute.PUT("/:id", controller.UpdateTLSCertificate)
|
||||
tlsCertificateRoute.POST("/import-file", controller.ImportTLSCertificateFile)
|
||||
|
||||
@@ -185,10 +185,20 @@ func TestPhase1HTTPSAndCertificateImportLifecycle(t *testing.T) {
|
||||
}
|
||||
|
||||
detailResp := performJSONRequest(t, engine, token, http.MethodGet, "/api/tls-certificates/"+toString(manualCertificate.ID), nil)
|
||||
var certificateDetail model.TLSCertificate
|
||||
var certificateDetail map[string]any
|
||||
decodeResponseData(t, detailResp, &certificateDetail)
|
||||
if certificateDetail.ID != manualCertificate.ID || certificateDetail.CertPEM == "" || certificateDetail.KeyPEM == "" {
|
||||
t.Fatal("expected certificate detail endpoint to return pem payloads")
|
||||
if _, exists := certificateDetail["cert_pem"]; exists {
|
||||
t.Fatal("expected certificate detail endpoint to omit cert_pem")
|
||||
}
|
||||
if _, exists := certificateDetail["key_pem"]; exists {
|
||||
t.Fatal("expected certificate detail endpoint to omit key_pem")
|
||||
}
|
||||
|
||||
contentResp := performJSONRequest(t, engine, token, http.MethodGet, "/api/tls-certificates/"+toString(manualCertificate.ID)+"/content", nil)
|
||||
var certificateContent map[string]any
|
||||
decodeResponseData(t, contentResp, &certificateContent)
|
||||
if certificateContent["cert_pem"] == "" || certificateContent["key_pem"] == "" {
|
||||
t.Fatal("expected certificate content endpoint to return pem payloads")
|
||||
}
|
||||
|
||||
updatedCertPEM, updatedKeyPEM := generateCertificatePairForRouterTest(t, []string{"secure.example.com", "www.secure.example.com"})
|
||||
|
||||
@@ -16,6 +16,14 @@ type TLSCertificateInput struct {
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type TLSCertificateContent struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CertPEM string `json:"cert_pem"`
|
||||
KeyPEM string `json:"key_pem"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
func ListTLSCertificates() ([]*model.TLSCertificate, error) {
|
||||
return model.ListTLSCertificates()
|
||||
}
|
||||
@@ -24,6 +32,21 @@ func GetTLSCertificate(id uint) (*model.TLSCertificate, error) {
|
||||
return model.GetTLSCertificateByID(id)
|
||||
}
|
||||
|
||||
func GetTLSCertificateContent(id uint) (*TLSCertificateContent, error) {
|
||||
certificate, err := model.GetTLSCertificateByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TLSCertificateContent{
|
||||
ID: certificate.ID,
|
||||
Name: certificate.Name,
|
||||
CertPEM: certificate.CertPEM,
|
||||
KeyPEM: certificate.KeyPEM,
|
||||
Remark: certificate.Remark,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func CreateTLSCertificate(input TLSCertificateInput) (*model.TLSCertificate, error) {
|
||||
certificate, err := buildTLSCertificate(nil, input)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { apiRequest } from '@/lib/api/client';
|
||||
|
||||
import type {
|
||||
TlsCertificateContentItem,
|
||||
TlsCertificateDetailItem,
|
||||
TlsCertificateFileImportPayload,
|
||||
TlsCertificateItem,
|
||||
@@ -22,6 +23,10 @@ export function getTlsCertificate(id: number) {
|
||||
return apiRequest<TlsCertificateDetailItem>(`/tls-certificates/${id}`);
|
||||
}
|
||||
|
||||
export function getTlsCertificateContent(id: number) {
|
||||
return apiRequest<TlsCertificateContentItem>(`/tls-certificates/${id}/content`);
|
||||
}
|
||||
|
||||
export function updateTlsCertificate(
|
||||
id: number,
|
||||
payload: TlsCertificateMutationPayload,
|
||||
|
||||
@@ -11,6 +11,11 @@ export interface TlsCertificateItem {
|
||||
}
|
||||
|
||||
export interface TlsCertificateDetailItem extends TlsCertificateItem {
|
||||
cert_pem?: never;
|
||||
key_pem?: never;
|
||||
}
|
||||
|
||||
export interface TlsCertificateContentItem extends TlsCertificateItem {
|
||||
cert_pem: string;
|
||||
key_pem: string;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ import { ErrorState } from '@/components/feedback/error-state';
|
||||
import { LoadingState } from '@/components/feedback/loading-state';
|
||||
import { AppModal } from '@/components/ui/app-modal';
|
||||
import { StatusBadge } from '@/components/ui/status-badge';
|
||||
import { getTlsCertificate } from '@/features/tls-certificates/api/tls-certificates';
|
||||
import {
|
||||
getTlsCertificate,
|
||||
getTlsCertificateContent,
|
||||
} from '@/features/tls-certificates/api/tls-certificates';
|
||||
import { getCertificateStatus, getErrorMessage } from '@/features/websites/utils';
|
||||
import {
|
||||
CodeBlock,
|
||||
@@ -40,7 +43,14 @@ export function CertificateDetailModal({
|
||||
enabled: isOpen && certificateId !== null,
|
||||
});
|
||||
|
||||
const contentQuery = useQuery({
|
||||
queryKey: ['tls-certificates', 'content', certificateId],
|
||||
queryFn: () => getTlsCertificateContent(certificateId as number),
|
||||
enabled: isOpen && certificateId !== null,
|
||||
});
|
||||
|
||||
const certificate = certificateQuery.data;
|
||||
const content = contentQuery.data;
|
||||
const status = certificate ? getCertificateStatus(certificate) : null;
|
||||
|
||||
return (
|
||||
@@ -72,14 +82,16 @@ export function CertificateDetailModal({
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{certificateQuery.isLoading ? (
|
||||
{certificateQuery.isLoading || contentQuery.isLoading ? (
|
||||
<LoadingState />
|
||||
) : certificateQuery.isError ? (
|
||||
) : certificateQuery.isError || contentQuery.isError ? (
|
||||
<ErrorState
|
||||
title="证书详情加载失败"
|
||||
description={getErrorMessage(certificateQuery.error)}
|
||||
description={getErrorMessage(
|
||||
certificateQuery.error ?? contentQuery.error,
|
||||
)}
|
||||
/>
|
||||
) : !certificate ? (
|
||||
) : !certificate || !content ? (
|
||||
<EmptyState
|
||||
title="证书不存在"
|
||||
description="当前证书可能已被删除。"
|
||||
@@ -138,7 +150,7 @@ export function CertificateDetailModal({
|
||||
证书 PEM
|
||||
</p>
|
||||
<CodeBlock className="max-h-56 overflow-y-auto whitespace-pre-wrap break-all">
|
||||
{certificate.cert_pem}
|
||||
{content.cert_pem}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
<div>
|
||||
@@ -146,7 +158,7 @@ export function CertificateDetailModal({
|
||||
私钥 PEM
|
||||
</p>
|
||||
<CodeBlock className="max-h-56 overflow-y-auto whitespace-pre-wrap break-all">
|
||||
{certificate.key_pem}
|
||||
{content.key_pem}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { InlineMessage } from '@/components/feedback/inline-message';
|
||||
import { LoadingState } from '@/components/feedback/loading-state';
|
||||
import { AppModal } from '@/components/ui/app-modal';
|
||||
import {
|
||||
getTlsCertificate,
|
||||
getTlsCertificateContent,
|
||||
updateTlsCertificate,
|
||||
} from '@/features/tls-certificates/api/tls-certificates';
|
||||
import type { TlsCertificateItem } from '@/features/tls-certificates/types';
|
||||
@@ -49,8 +49,8 @@ export function CertificateEditorModal({
|
||||
});
|
||||
|
||||
const certificateQuery = useQuery({
|
||||
queryKey: ['tls-certificates', 'detail', certificateId],
|
||||
queryFn: () => getTlsCertificate(certificateId as number),
|
||||
queryKey: ['tls-certificates', 'content', certificateId],
|
||||
queryFn: () => getTlsCertificateContent(certificateId as number),
|
||||
enabled: isOpen && certificateId !== null,
|
||||
});
|
||||
|
||||
@@ -120,7 +120,7 @@ export function CertificateEditorModal({
|
||||
<LoadingState />
|
||||
) : certificateQuery.isError ? (
|
||||
<ErrorState
|
||||
title="证书详情加载失败"
|
||||
title="证书内容加载失败"
|
||||
description={getErrorMessage(certificateQuery.error)}
|
||||
/>
|
||||
) : !certificateQuery.data ? (
|
||||
|
||||
Reference in New Issue
Block a user