mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 17:56:37 +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
|
// CreateTLSCertificate godoc
|
||||||
// @Summary Create TLS certificate from PEM
|
// @Summary Create TLS certificate from PEM
|
||||||
// @Tags TLSCertificates
|
// @Tags TLSCertificates
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
{
|
{
|
||||||
tlsCertificateRoute.GET("/", controller.GetTLSCertificates)
|
tlsCertificateRoute.GET("/", controller.GetTLSCertificates)
|
||||||
tlsCertificateRoute.GET("/:id", controller.GetTLSCertificate)
|
tlsCertificateRoute.GET("/:id", controller.GetTLSCertificate)
|
||||||
|
tlsCertificateRoute.GET("/:id/content", controller.GetTLSCertificateContent)
|
||||||
tlsCertificateRoute.POST("/", controller.CreateTLSCertificate)
|
tlsCertificateRoute.POST("/", controller.CreateTLSCertificate)
|
||||||
tlsCertificateRoute.PUT("/:id", controller.UpdateTLSCertificate)
|
tlsCertificateRoute.PUT("/:id", controller.UpdateTLSCertificate)
|
||||||
tlsCertificateRoute.POST("/import-file", controller.ImportTLSCertificateFile)
|
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)
|
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)
|
decodeResponseData(t, detailResp, &certificateDetail)
|
||||||
if certificateDetail.ID != manualCertificate.ID || certificateDetail.CertPEM == "" || certificateDetail.KeyPEM == "" {
|
if _, exists := certificateDetail["cert_pem"]; exists {
|
||||||
t.Fatal("expected certificate detail endpoint to return pem payloads")
|
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"})
|
updatedCertPEM, updatedKeyPEM := generateCertificatePairForRouterTest(t, []string{"secure.example.com", "www.secure.example.com"})
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ type TLSCertificateInput struct {
|
|||||||
Remark string `json:"remark"`
|
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) {
|
func ListTLSCertificates() ([]*model.TLSCertificate, error) {
|
||||||
return model.ListTLSCertificates()
|
return model.ListTLSCertificates()
|
||||||
}
|
}
|
||||||
@@ -24,6 +32,21 @@ func GetTLSCertificate(id uint) (*model.TLSCertificate, error) {
|
|||||||
return model.GetTLSCertificateByID(id)
|
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) {
|
func CreateTLSCertificate(input TLSCertificateInput) (*model.TLSCertificate, error) {
|
||||||
certificate, err := buildTLSCertificate(nil, input)
|
certificate, err := buildTLSCertificate(nil, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { apiRequest } from '@/lib/api/client';
|
import { apiRequest } from '@/lib/api/client';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
TlsCertificateContentItem,
|
||||||
TlsCertificateDetailItem,
|
TlsCertificateDetailItem,
|
||||||
TlsCertificateFileImportPayload,
|
TlsCertificateFileImportPayload,
|
||||||
TlsCertificateItem,
|
TlsCertificateItem,
|
||||||
@@ -22,6 +23,10 @@ export function getTlsCertificate(id: number) {
|
|||||||
return apiRequest<TlsCertificateDetailItem>(`/tls-certificates/${id}`);
|
return apiRequest<TlsCertificateDetailItem>(`/tls-certificates/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTlsCertificateContent(id: number) {
|
||||||
|
return apiRequest<TlsCertificateContentItem>(`/tls-certificates/${id}/content`);
|
||||||
|
}
|
||||||
|
|
||||||
export function updateTlsCertificate(
|
export function updateTlsCertificate(
|
||||||
id: number,
|
id: number,
|
||||||
payload: TlsCertificateMutationPayload,
|
payload: TlsCertificateMutationPayload,
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ export interface TlsCertificateItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TlsCertificateDetailItem extends TlsCertificateItem {
|
export interface TlsCertificateDetailItem extends TlsCertificateItem {
|
||||||
|
cert_pem?: never;
|
||||||
|
key_pem?: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TlsCertificateContentItem extends TlsCertificateItem {
|
||||||
cert_pem: string;
|
cert_pem: string;
|
||||||
key_pem: string;
|
key_pem: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import { ErrorState } from '@/components/feedback/error-state';
|
|||||||
import { LoadingState } from '@/components/feedback/loading-state';
|
import { LoadingState } from '@/components/feedback/loading-state';
|
||||||
import { AppModal } from '@/components/ui/app-modal';
|
import { AppModal } from '@/components/ui/app-modal';
|
||||||
import { StatusBadge } from '@/components/ui/status-badge';
|
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 { getCertificateStatus, getErrorMessage } from '@/features/websites/utils';
|
||||||
import {
|
import {
|
||||||
CodeBlock,
|
CodeBlock,
|
||||||
@@ -40,7 +43,14 @@ export function CertificateDetailModal({
|
|||||||
enabled: isOpen && certificateId !== null,
|
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 certificate = certificateQuery.data;
|
||||||
|
const content = contentQuery.data;
|
||||||
const status = certificate ? getCertificateStatus(certificate) : null;
|
const status = certificate ? getCertificateStatus(certificate) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -72,14 +82,16 @@ export function CertificateDetailModal({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{certificateQuery.isLoading ? (
|
{certificateQuery.isLoading || contentQuery.isLoading ? (
|
||||||
<LoadingState />
|
<LoadingState />
|
||||||
) : certificateQuery.isError ? (
|
) : certificateQuery.isError || contentQuery.isError ? (
|
||||||
<ErrorState
|
<ErrorState
|
||||||
title="证书详情加载失败"
|
title="证书详情加载失败"
|
||||||
description={getErrorMessage(certificateQuery.error)}
|
description={getErrorMessage(
|
||||||
|
certificateQuery.error ?? contentQuery.error,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
) : !certificate ? (
|
) : !certificate || !content ? (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
title="证书不存在"
|
title="证书不存在"
|
||||||
description="当前证书可能已被删除。"
|
description="当前证书可能已被删除。"
|
||||||
@@ -138,7 +150,7 @@ export function CertificateDetailModal({
|
|||||||
证书 PEM
|
证书 PEM
|
||||||
</p>
|
</p>
|
||||||
<CodeBlock className="max-h-56 overflow-y-auto whitespace-pre-wrap break-all">
|
<CodeBlock className="max-h-56 overflow-y-auto whitespace-pre-wrap break-all">
|
||||||
{certificate.cert_pem}
|
{content.cert_pem}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -146,7 +158,7 @@ export function CertificateDetailModal({
|
|||||||
私钥 PEM
|
私钥 PEM
|
||||||
</p>
|
</p>
|
||||||
<CodeBlock className="max-h-56 overflow-y-auto whitespace-pre-wrap break-all">
|
<CodeBlock className="max-h-56 overflow-y-auto whitespace-pre-wrap break-all">
|
||||||
{certificate.key_pem}
|
{content.key_pem}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { InlineMessage } from '@/components/feedback/inline-message';
|
|||||||
import { LoadingState } from '@/components/feedback/loading-state';
|
import { LoadingState } from '@/components/feedback/loading-state';
|
||||||
import { AppModal } from '@/components/ui/app-modal';
|
import { AppModal } from '@/components/ui/app-modal';
|
||||||
import {
|
import {
|
||||||
getTlsCertificate,
|
getTlsCertificateContent,
|
||||||
updateTlsCertificate,
|
updateTlsCertificate,
|
||||||
} from '@/features/tls-certificates/api/tls-certificates';
|
} from '@/features/tls-certificates/api/tls-certificates';
|
||||||
import type { TlsCertificateItem } from '@/features/tls-certificates/types';
|
import type { TlsCertificateItem } from '@/features/tls-certificates/types';
|
||||||
@@ -49,8 +49,8 @@ export function CertificateEditorModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const certificateQuery = useQuery({
|
const certificateQuery = useQuery({
|
||||||
queryKey: ['tls-certificates', 'detail', certificateId],
|
queryKey: ['tls-certificates', 'content', certificateId],
|
||||||
queryFn: () => getTlsCertificate(certificateId as number),
|
queryFn: () => getTlsCertificateContent(certificateId as number),
|
||||||
enabled: isOpen && certificateId !== null,
|
enabled: isOpen && certificateId !== null,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ export function CertificateEditorModal({
|
|||||||
<LoadingState />
|
<LoadingState />
|
||||||
) : certificateQuery.isError ? (
|
) : certificateQuery.isError ? (
|
||||||
<ErrorState
|
<ErrorState
|
||||||
title="证书详情加载失败"
|
title="证书内容加载失败"
|
||||||
description={getErrorMessage(certificateQuery.error)}
|
description={getErrorMessage(certificateQuery.error)}
|
||||||
/>
|
/>
|
||||||
) : !certificateQuery.data ? (
|
) : !certificateQuery.data ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user