mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 00:56:37 +08:00
[新增] 添加转换上传的 TLS 证书为 ACME 管理证书的功能
This commit is contained in:
@@ -334,6 +334,50 @@ func UpdateAcmeCertificate(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConvertTLSCertificateToAcme godoc
|
||||||
|
// @Summary Convert uploaded TLS certificate to ACME managed certificate
|
||||||
|
// @Tags TLSCertificates
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param id path int true "Certificate ID"
|
||||||
|
// @Param payload body service.TLSApplyInput true "TLS apply payload"
|
||||||
|
// @Success 200 {object} map[string]interface{}
|
||||||
|
// @Failure 400 {object} map[string]interface{}
|
||||||
|
// @Router /api/tls-certificates/{id}/convert-acme [post]
|
||||||
|
func ConvertTLSCertificateToAcme(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var input service.TLSApplyInput
|
||||||
|
if err := json.NewDecoder(c.Request.Body).Decode(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": "无效的参数",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
certificate, err := service.ConvertTLSCertificateToAcme(uint(id), input)
|
||||||
|
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": certificate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// RenewTLSCertificate godoc
|
// RenewTLSCertificate godoc
|
||||||
// @Summary Renew TLS certificate
|
// @Summary Renew TLS certificate
|
||||||
// @Tags TLSCertificates
|
// @Tags TLSCertificates
|
||||||
|
|||||||
@@ -326,6 +326,31 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/acme-accounts/default": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"AcmeAccounts"
|
||||||
|
],
|
||||||
|
"summary": "Get default ACME account",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/agent/apply-logs": {
|
"/api/agent/apply-logs": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -600,6 +625,49 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/config-versions/cleanup": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ConfigVersions"
|
||||||
|
],
|
||||||
|
"summary": "Cleanup old config versions",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Cleanup request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.CleanupConfigVersionRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/config-versions/diff": {
|
"/api/config-versions/diff": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -789,6 +857,148 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/dns-accounts/": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"DnsAccounts"
|
||||||
|
],
|
||||||
|
"summary": "List DNS accounts",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"DnsAccounts"
|
||||||
|
],
|
||||||
|
"summary": "Create DNS account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "DNS account payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.DnsAccountInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/dns-accounts/{id}/delete": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"DnsAccounts"
|
||||||
|
],
|
||||||
|
"summary": "Delete DNS account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "DNS Account ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/dns-accounts/{id}/update": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"DnsAccounts"
|
||||||
|
],
|
||||||
|
"summary": "Update DNS account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "DNS Account ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "DNS account payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.DnsAccountInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/managed-domains/": {
|
"/api/managed-domains/": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1552,6 +1762,47 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/option/update-batch": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Options"
|
||||||
|
],
|
||||||
|
"summary": "Batch update options",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Batch option payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.optionBatchPayload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/proxy-routes/": {
|
"/api/proxy-routes/": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1621,6 +1872,47 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/proxy-routes/{id}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ProxyRoutes"
|
||||||
|
],
|
||||||
|
"summary": "Get proxy route detail",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Route ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/proxy-routes/{id}/delete": {
|
"/api/proxy-routes/{id}/delete": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1804,6 +2096,52 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/tls-certificates/apply": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"TLSCertificates"
|
||||||
|
],
|
||||||
|
"summary": "Apply TLS certificate via ACME",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "TLS apply payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/service.TLSApplyInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/tls-certificates/import-file": {
|
"/api/tls-certificates/import-file": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1950,6 +2288,59 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/tls-certificates/{id}/convert-acme": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"TLSCertificates"
|
||||||
|
],
|
||||||
|
"summary": "Convert uploaded TLS certificate to ACME managed certificate",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Certificate ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "TLS apply payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/service.TLSApplyInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/tls-certificates/{id}/delete": {
|
"/api/tls-certificates/{id}/delete": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1991,6 +2382,47 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/tls-certificates/{id}/renew": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"TLSCertificates"
|
||||||
|
],
|
||||||
|
"summary": "Renew TLS certificate",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Certificate ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/tls-certificates/{id}/update": {
|
"/api/tls-certificates/{id}/update": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2044,6 +2476,59 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/tls-certificates/{id}/update-acme": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"TLSCertificates"
|
||||||
|
],
|
||||||
|
"summary": "Update ACME TLS certificate",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Certificate ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "TLS apply payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/service.TLSApplyInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/update/latest-release": {
|
"/api/update/latest-release": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@@ -2141,6 +2626,32 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"controller.CleanupConfigVersionRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"keep_count"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"keep_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"controller.DnsAccountInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"authorization": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"controller.geoIPLookupRequest": {
|
"controller.geoIPLookupRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2152,6 +2663,17 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"controller.optionBatchPayload": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"options": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/model.Option"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"model.Option": {
|
"model.Option": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2491,6 +3013,15 @@ const docTemplate = `{
|
|||||||
"service.ProxyRouteInput": {
|
"service.ProxyRouteInput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"basic_auth_enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"basic_auth_password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"basic_auth_username": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"cache_enabled": {
|
"cache_enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@@ -2506,6 +3037,12 @@ const docTemplate = `{
|
|||||||
"cert_id": {
|
"cert_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"cert_ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"custom_headers": {
|
"custom_headers": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -2515,24 +3052,69 @@ const docTemplate = `{
|
|||||||
"domain": {
|
"domain": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"domain_cert_ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domains": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"enable_https": {
|
"enable_https": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"limit_conn_per_ip": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"limit_conn_per_server": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"limit_rate": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"origin_address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"origin_host": {
|
"origin_host": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"origin_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"origin_port": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"origin_scheme": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"origin_uri": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"origin_url": {
|
"origin_url": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"pow_config": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pow_enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"redirect_http": {
|
"redirect_http": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"remark": {
|
"remark": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"site_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"upstreams": {
|
"upstreams": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -2541,6 +3123,47 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"service.TLSApplyInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"acme_account_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"auto_renew": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"disable_cname": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"dns1": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"dns2": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"dns_account_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"key_algorithm": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"other_domains": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"primary_domain": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"remark": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"skip_dns": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"service.TLSCertificateInput": {
|
"service.TLSCertificateInput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -323,6 +323,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/acme-accounts/default": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"AcmeAccounts"
|
||||||
|
],
|
||||||
|
"summary": "Get default ACME account",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/agent/apply-logs": {
|
"/api/agent/apply-logs": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -597,6 +622,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/config-versions/cleanup": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ConfigVersions"
|
||||||
|
],
|
||||||
|
"summary": "Cleanup old config versions",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Cleanup request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.CleanupConfigVersionRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/config-versions/diff": {
|
"/api/config-versions/diff": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -786,6 +854,148 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/dns-accounts/": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"DnsAccounts"
|
||||||
|
],
|
||||||
|
"summary": "List DNS accounts",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"DnsAccounts"
|
||||||
|
],
|
||||||
|
"summary": "Create DNS account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "DNS account payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.DnsAccountInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/dns-accounts/{id}/delete": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"DnsAccounts"
|
||||||
|
],
|
||||||
|
"summary": "Delete DNS account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "DNS Account ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/dns-accounts/{id}/update": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"DnsAccounts"
|
||||||
|
],
|
||||||
|
"summary": "Update DNS account",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "DNS Account ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "DNS account payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.DnsAccountInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/managed-domains/": {
|
"/api/managed-domains/": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1549,6 +1759,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/option/update-batch": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Options"
|
||||||
|
],
|
||||||
|
"summary": "Batch update options",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Batch option payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/controller.optionBatchPayload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/proxy-routes/": {
|
"/api/proxy-routes/": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1618,6 +1869,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/proxy-routes/{id}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ProxyRoutes"
|
||||||
|
],
|
||||||
|
"summary": "Get proxy route detail",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Route ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/proxy-routes/{id}/delete": {
|
"/api/proxy-routes/{id}/delete": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1801,6 +2093,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/tls-certificates/apply": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"TLSCertificates"
|
||||||
|
],
|
||||||
|
"summary": "Apply TLS certificate via ACME",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "TLS apply payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/service.TLSApplyInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/tls-certificates/import-file": {
|
"/api/tls-certificates/import-file": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1947,6 +2285,59 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/tls-certificates/{id}/convert-acme": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"TLSCertificates"
|
||||||
|
],
|
||||||
|
"summary": "Convert uploaded TLS certificate to ACME managed certificate",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Certificate ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "TLS apply payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/service.TLSApplyInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/tls-certificates/{id}/delete": {
|
"/api/tls-certificates/{id}/delete": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1988,6 +2379,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/tls-certificates/{id}/renew": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"TLSCertificates"
|
||||||
|
],
|
||||||
|
"summary": "Renew TLS certificate",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Certificate ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/tls-certificates/{id}/update": {
|
"/api/tls-certificates/{id}/update": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2041,6 +2473,59 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/tls-certificates/{id}/update-acme": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"TLSCertificates"
|
||||||
|
],
|
||||||
|
"summary": "Update ACME TLS certificate",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Certificate ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "TLS apply payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/service.TLSApplyInput"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/update/latest-release": {
|
"/api/update/latest-release": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@@ -2138,6 +2623,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"controller.CleanupConfigVersionRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"keep_count"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"keep_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"controller.DnsAccountInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"authorization": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"controller.geoIPLookupRequest": {
|
"controller.geoIPLookupRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2149,6 +2660,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"controller.optionBatchPayload": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"options": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/model.Option"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"model.Option": {
|
"model.Option": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2488,6 +3010,15 @@
|
|||||||
"service.ProxyRouteInput": {
|
"service.ProxyRouteInput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"basic_auth_enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"basic_auth_password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"basic_auth_username": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"cache_enabled": {
|
"cache_enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@@ -2503,6 +3034,12 @@
|
|||||||
"cert_id": {
|
"cert_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"cert_ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"custom_headers": {
|
"custom_headers": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -2512,24 +3049,69 @@
|
|||||||
"domain": {
|
"domain": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"domain_cert_ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domains": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"enable_https": {
|
"enable_https": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"limit_conn_per_ip": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"limit_conn_per_server": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"limit_rate": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"origin_address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"origin_host": {
|
"origin_host": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"origin_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"origin_port": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"origin_scheme": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"origin_uri": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"origin_url": {
|
"origin_url": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"pow_config": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pow_enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"redirect_http": {
|
"redirect_http": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"remark": {
|
"remark": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"site_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"upstreams": {
|
"upstreams": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -2538,6 +3120,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"service.TLSApplyInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"acme_account_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"auto_renew": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"disable_cname": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"dns1": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"dns2": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"dns_account_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"key_algorithm": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"other_domains": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"primary_domain": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"remark": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"skip_dns": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"service.TLSCertificateInput": {
|
"service.TLSCertificateInput": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
basePath: /
|
basePath: /
|
||||||
definitions:
|
definitions:
|
||||||
|
controller.CleanupConfigVersionRequest:
|
||||||
|
properties:
|
||||||
|
keep_count:
|
||||||
|
minimum: 3
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- keep_count
|
||||||
|
type: object
|
||||||
|
controller.DnsAccountInput:
|
||||||
|
properties:
|
||||||
|
authorization:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
controller.geoIPLookupRequest:
|
controller.geoIPLookupRequest:
|
||||||
properties:
|
properties:
|
||||||
ip:
|
ip:
|
||||||
@@ -7,6 +24,13 @@ definitions:
|
|||||||
provider:
|
provider:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
controller.optionBatchPayload:
|
||||||
|
properties:
|
||||||
|
options:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/model.Option'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
model.Option:
|
model.Option:
|
||||||
properties:
|
properties:
|
||||||
key:
|
key:
|
||||||
@@ -229,6 +253,12 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
service.ProxyRouteInput:
|
service.ProxyRouteInput:
|
||||||
properties:
|
properties:
|
||||||
|
basic_auth_enabled:
|
||||||
|
type: boolean
|
||||||
|
basic_auth_password:
|
||||||
|
type: string
|
||||||
|
basic_auth_username:
|
||||||
|
type: string
|
||||||
cache_enabled:
|
cache_enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
cache_policy:
|
cache_policy:
|
||||||
@@ -239,29 +269,90 @@ definitions:
|
|||||||
type: array
|
type: array
|
||||||
cert_id:
|
cert_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
cert_ids:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
custom_headers:
|
custom_headers:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/service.ProxyRouteCustomHeaderInput'
|
$ref: '#/definitions/service.ProxyRouteCustomHeaderInput'
|
||||||
type: array
|
type: array
|
||||||
domain:
|
domain:
|
||||||
type: string
|
type: string
|
||||||
|
domain_cert_ids:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
domains:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
enable_https:
|
enable_https:
|
||||||
type: boolean
|
type: boolean
|
||||||
enabled:
|
enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
limit_conn_per_ip:
|
||||||
|
type: integer
|
||||||
|
limit_conn_per_server:
|
||||||
|
type: integer
|
||||||
|
limit_rate:
|
||||||
|
type: string
|
||||||
|
origin_address:
|
||||||
|
type: string
|
||||||
origin_host:
|
origin_host:
|
||||||
type: string
|
type: string
|
||||||
|
origin_id:
|
||||||
|
type: integer
|
||||||
|
origin_port:
|
||||||
|
type: string
|
||||||
|
origin_scheme:
|
||||||
|
type: string
|
||||||
|
origin_uri:
|
||||||
|
type: string
|
||||||
origin_url:
|
origin_url:
|
||||||
type: string
|
type: string
|
||||||
|
pow_config:
|
||||||
|
type: string
|
||||||
|
pow_enabled:
|
||||||
|
type: boolean
|
||||||
redirect_http:
|
redirect_http:
|
||||||
type: boolean
|
type: boolean
|
||||||
remark:
|
remark:
|
||||||
type: string
|
type: string
|
||||||
|
site_name:
|
||||||
|
type: string
|
||||||
upstreams:
|
upstreams:
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
service.TLSApplyInput:
|
||||||
|
properties:
|
||||||
|
acme_account_id:
|
||||||
|
type: integer
|
||||||
|
auto_renew:
|
||||||
|
type: boolean
|
||||||
|
disable_cname:
|
||||||
|
type: boolean
|
||||||
|
dns_account_id:
|
||||||
|
type: integer
|
||||||
|
dns1:
|
||||||
|
type: string
|
||||||
|
dns2:
|
||||||
|
type: string
|
||||||
|
key_algorithm:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
other_domains:
|
||||||
|
type: string
|
||||||
|
primary_domain:
|
||||||
|
type: string
|
||||||
|
remark:
|
||||||
|
type: string
|
||||||
|
skip_dns:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
service.TLSCertificateInput:
|
service.TLSCertificateInput:
|
||||||
properties:
|
properties:
|
||||||
cert_pem:
|
cert_pem:
|
||||||
@@ -477,6 +568,21 @@ paths:
|
|||||||
summary: Get access log IP trend
|
summary: Get access log IP trend
|
||||||
tags:
|
tags:
|
||||||
- AccessLogs
|
- AccessLogs
|
||||||
|
/api/acme-accounts/default:
|
||||||
|
get:
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Get default ACME account
|
||||||
|
tags:
|
||||||
|
- AcmeAccounts
|
||||||
/api/agent/apply-logs:
|
/api/agent/apply-logs:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -698,6 +804,33 @@ paths:
|
|||||||
summary: Get active config version
|
summary: Get active config version
|
||||||
tags:
|
tags:
|
||||||
- ConfigVersions
|
- ConfigVersions
|
||||||
|
/api/config-versions/cleanup:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- description: Cleanup request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.CleanupConfigVersionRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Cleanup old config versions
|
||||||
|
tags:
|
||||||
|
- ConfigVersions
|
||||||
/api/config-versions/diff:
|
/api/config-versions/diff:
|
||||||
get:
|
get:
|
||||||
produces:
|
produces:
|
||||||
@@ -763,6 +896,94 @@ paths:
|
|||||||
summary: Get dashboard overview
|
summary: Get dashboard overview
|
||||||
tags:
|
tags:
|
||||||
- Dashboard
|
- Dashboard
|
||||||
|
/api/dns-accounts/:
|
||||||
|
get:
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: List DNS accounts
|
||||||
|
tags:
|
||||||
|
- DnsAccounts
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: DNS account payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.DnsAccountInput'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Create DNS account
|
||||||
|
tags:
|
||||||
|
- DnsAccounts
|
||||||
|
/api/dns-accounts/{id}/delete:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- description: DNS Account ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Delete DNS account
|
||||||
|
tags:
|
||||||
|
- DnsAccounts
|
||||||
|
/api/dns-accounts/{id}/update:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: DNS Account ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: DNS account payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.DnsAccountInput'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Update DNS account
|
||||||
|
tags:
|
||||||
|
- DnsAccounts
|
||||||
/api/managed-domains/:
|
/api/managed-domains/:
|
||||||
get:
|
get:
|
||||||
produces:
|
produces:
|
||||||
@@ -1246,6 +1467,33 @@ paths:
|
|||||||
summary: Update option
|
summary: Update option
|
||||||
tags:
|
tags:
|
||||||
- Options
|
- Options
|
||||||
|
/api/option/update-batch:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Batch option payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/controller.optionBatchPayload'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
summary: Batch update options
|
||||||
|
tags:
|
||||||
|
- Options
|
||||||
/api/proxy-routes/:
|
/api/proxy-routes/:
|
||||||
get:
|
get:
|
||||||
produces:
|
produces:
|
||||||
@@ -1289,6 +1537,32 @@ paths:
|
|||||||
summary: Create proxy route
|
summary: Create proxy route
|
||||||
tags:
|
tags:
|
||||||
- ProxyRoutes
|
- ProxyRoutes
|
||||||
|
/api/proxy-routes/{id}:
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: Route ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Get proxy route detail
|
||||||
|
tags:
|
||||||
|
- ProxyRoutes
|
||||||
/api/proxy-routes/{id}/delete:
|
/api/proxy-routes/{id}/delete:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
@@ -1457,6 +1731,40 @@ paths:
|
|||||||
summary: Get TLS certificate PEM content
|
summary: Get TLS certificate PEM content
|
||||||
tags:
|
tags:
|
||||||
- TLSCertificates
|
- TLSCertificates
|
||||||
|
/api/tls-certificates/{id}/convert-acme:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Certificate ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: TLS apply payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/service.TLSApplyInput'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Convert uploaded TLS certificate to ACME managed certificate
|
||||||
|
tags:
|
||||||
|
- TLSCertificates
|
||||||
/api/tls-certificates/{id}/delete:
|
/api/tls-certificates/{id}/delete:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
@@ -1483,6 +1791,32 @@ paths:
|
|||||||
summary: Delete TLS certificate
|
summary: Delete TLS certificate
|
||||||
tags:
|
tags:
|
||||||
- TLSCertificates
|
- TLSCertificates
|
||||||
|
/api/tls-certificates/{id}/renew:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- description: Certificate ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Renew TLS certificate
|
||||||
|
tags:
|
||||||
|
- TLSCertificates
|
||||||
/api/tls-certificates/{id}/update:
|
/api/tls-certificates/{id}/update:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -1517,6 +1851,69 @@ paths:
|
|||||||
summary: Update TLS certificate from PEM
|
summary: Update TLS certificate from PEM
|
||||||
tags:
|
tags:
|
||||||
- TLSCertificates
|
- TLSCertificates
|
||||||
|
/api/tls-certificates/{id}/update-acme:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Certificate ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: TLS apply payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/service.TLSApplyInput'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Update ACME TLS certificate
|
||||||
|
tags:
|
||||||
|
- TLSCertificates
|
||||||
|
/api/tls-certificates/apply:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: TLS apply payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/service.TLSApplyInput'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Apply TLS certificate via ACME
|
||||||
|
tags:
|
||||||
|
- TLSCertificates
|
||||||
/api/tls-certificates/import-file:
|
/api/tls-certificates/import-file:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
tlsCertificateRoute.POST("/", controller.CreateTLSCertificate)
|
tlsCertificateRoute.POST("/", controller.CreateTLSCertificate)
|
||||||
tlsCertificateRoute.POST("/:id/update", controller.UpdateTLSCertificate)
|
tlsCertificateRoute.POST("/:id/update", controller.UpdateTLSCertificate)
|
||||||
tlsCertificateRoute.POST("/:id/update-acme", controller.UpdateAcmeCertificate)
|
tlsCertificateRoute.POST("/:id/update-acme", controller.UpdateAcmeCertificate)
|
||||||
|
tlsCertificateRoute.POST("/:id/convert-acme", controller.ConvertTLSCertificateToAcme)
|
||||||
tlsCertificateRoute.POST("/import-file", controller.ImportTLSCertificateFile)
|
tlsCertificateRoute.POST("/import-file", controller.ImportTLSCertificateFile)
|
||||||
tlsCertificateRoute.POST("/:id/delete", controller.DeleteTLSCertificate)
|
tlsCertificateRoute.POST("/:id/delete", controller.DeleteTLSCertificate)
|
||||||
tlsCertificateRoute.POST("/apply", controller.ApplyTLSCertificate)
|
tlsCertificateRoute.POST("/apply", controller.ApplyTLSCertificate)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-contrib/sessions/cookie"
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -387,6 +388,95 @@ func TestPhase1HTTPSAndCertificateImportLifecycle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTLSCertificateConvertAcmeAPI(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
common.RedisEnabled = false
|
||||||
|
setupTestDB(t)
|
||||||
|
|
||||||
|
engine := gin.New()
|
||||||
|
engine.Use(sessions.Sessions("session", cookie.NewStore([]byte("test-secret"))))
|
||||||
|
router.SetApiRouter(engine)
|
||||||
|
|
||||||
|
token := prepareRootToken(t)
|
||||||
|
certPEM, keyPEM := generateCertificatePairForRouterTest(t, []string{"manual.example.com"})
|
||||||
|
createResp := performJSONRequest(t, engine, token, http.MethodPost, "/api/tls-certificates/", map[string]any{
|
||||||
|
"name": "manual-example",
|
||||||
|
"cert_pem": certPEM,
|
||||||
|
"key_pem": keyPEM,
|
||||||
|
})
|
||||||
|
var certificate model.TLSCertificate
|
||||||
|
decodeResponseData(t, createResp, &certificate)
|
||||||
|
|
||||||
|
started := make(chan struct{}, 1)
|
||||||
|
release := make(chan struct{})
|
||||||
|
done := make(chan struct{})
|
||||||
|
restore := service.SetTLSCertificateObtainFuncForTest(func(c *model.TLSCertificate) error {
|
||||||
|
defer close(done)
|
||||||
|
started <- struct{}{}
|
||||||
|
<-release
|
||||||
|
return errors.New("stop test conversion before external ACME call")
|
||||||
|
})
|
||||||
|
t.Cleanup(func() {
|
||||||
|
close(release)
|
||||||
|
<-done
|
||||||
|
restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
convertResp := performJSONRequest(t, engine, token, http.MethodPost, "/api/tls-certificates/"+toString(certificate.ID)+"/convert-acme", map[string]any{
|
||||||
|
"name": "managed-example",
|
||||||
|
"remark": "convert via api",
|
||||||
|
"acme_account_id": 1,
|
||||||
|
"dns_account_id": 2,
|
||||||
|
"key_algorithm": "EC256",
|
||||||
|
"auto_renew": true,
|
||||||
|
"primary_domain": "manual.example.com",
|
||||||
|
})
|
||||||
|
var converted model.TLSCertificate
|
||||||
|
decodeResponseData(t, convertResp, &converted)
|
||||||
|
if converted.ID != certificate.ID || converted.Provider != "upload" || converted.ApplyStatus != "applying" {
|
||||||
|
t.Fatalf("expected conversion API to keep upload provider while applying, got %+v", converted)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-started:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("expected conversion task to start")
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicateResp := performJSONRequestNoFatal(t, engine, token, http.MethodPost, "/api/tls-certificates/"+toString(certificate.ID)+"/convert-acme", map[string]any{
|
||||||
|
"name": "managed-example",
|
||||||
|
"primary_domain": "manual.example.com",
|
||||||
|
})
|
||||||
|
if duplicateResp.Success || !strings.Contains(duplicateResp.Message, "already applying") {
|
||||||
|
t.Fatalf("expected duplicate conversion to fail, got %+v", duplicateResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidResp := performJSONRequestNoFatal(t, engine, token, http.MethodPost, "/api/tls-certificates/not-a-number/convert-acme", map[string]any{})
|
||||||
|
if invalidResp.Success || !strings.Contains(invalidResp.Message, "invalid request") {
|
||||||
|
t.Fatalf("expected invalid id to fail, got %+v", invalidResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
acmeCertPEM, acmeKeyPEM := generateCertificatePairForRouterTest(t, []string{"acme.example.com"})
|
||||||
|
acmeResp := performJSONRequest(t, engine, token, http.MethodPost, "/api/tls-certificates/", map[string]any{
|
||||||
|
"name": "already-acme",
|
||||||
|
"cert_pem": acmeCertPEM,
|
||||||
|
"key_pem": acmeKeyPEM,
|
||||||
|
})
|
||||||
|
var acmeCertificate model.TLSCertificate
|
||||||
|
decodeResponseData(t, acmeResp, &acmeCertificate)
|
||||||
|
acmeCertificate.Provider = "acme"
|
||||||
|
if err := acmeCertificate.Update(); err != nil {
|
||||||
|
t.Fatalf("failed to mark certificate acme: %v", err)
|
||||||
|
}
|
||||||
|
nonUploadResp := performJSONRequestNoFatal(t, engine, token, http.MethodPost, "/api/tls-certificates/"+toString(acmeCertificate.ID)+"/convert-acme", map[string]any{
|
||||||
|
"name": "already-acme",
|
||||||
|
"primary_domain": "acme.example.com",
|
||||||
|
})
|
||||||
|
if nonUploadResp.Success || !strings.Contains(nonUploadResp.Message, "only uploaded") {
|
||||||
|
t.Fatalf("expected non-upload conversion to fail, got %+v", nonUploadResp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setupTestDB(t *testing.T) {
|
func setupTestDB(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
dbPath := filepath.Join(t.TempDir(), "phase1.db")
|
dbPath := filepath.Join(t.TempDir(), "phase1.db")
|
||||||
@@ -445,6 +535,33 @@ func performJSONRequest(t *testing.T, engine http.Handler, token string, method
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func performJSONRequestNoFatal(t *testing.T, engine http.Handler, token string, method string, path string, body any) apiResponse {
|
||||||
|
t.Helper()
|
||||||
|
var payload []byte
|
||||||
|
var err error
|
||||||
|
if body != nil {
|
||||||
|
payload, err = json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal request body: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(method, path, bytes.NewReader(payload))
|
||||||
|
if body != nil {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
engine.ServeHTTP(recorder, req)
|
||||||
|
if recorder.Code != http.StatusOK && recorder.Code != http.StatusBadRequest {
|
||||||
|
t.Fatalf("unexpected status %d for %s %s: %s", recorder.Code, method, path, recorder.Body.String())
|
||||||
|
}
|
||||||
|
var resp apiResponse
|
||||||
|
if err = json.Unmarshal(recorder.Body.Bytes(), &resp); err != nil {
|
||||||
|
t.Fatalf("failed to unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
func decodeResponseData(t *testing.T, resp apiResponse, target any) {
|
func decodeResponseData(t *testing.T, resp apiResponse, target any) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if err := json.Unmarshal(resp.Data, target); err != nil {
|
if err := json.Unmarshal(resp.Data, target); err != nil {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"openflare/model"
|
"openflare/model"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -84,3 +86,186 @@ func TestAcmeAndDnsIntegration(t *testing.T) {
|
|||||||
t.Fatalf("Failed to delete DNS Account after cert cleanup: %v", err)
|
t.Fatalf("Failed to delete DNS Account after cert cleanup: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConvertTLSCertificateToAcmePreservesUploadUntilSuccess(t *testing.T) {
|
||||||
|
setupServiceTestDB(t)
|
||||||
|
|
||||||
|
originalCertPEM, originalKeyPEM := generateCertificatePair(t, []string{"manual.example.com"})
|
||||||
|
cert, err := CreateTLSCertificate(TLSCertificateInput{
|
||||||
|
Name: "manual-cert",
|
||||||
|
CertPEM: originalCertPEM,
|
||||||
|
KeyPEM: originalKeyPEM,
|
||||||
|
Remark: "manual upload",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateTLSCertificate failed: %v", err)
|
||||||
|
}
|
||||||
|
originalCertPEM = cert.CertPEM
|
||||||
|
originalKeyPEM = cert.KeyPEM
|
||||||
|
|
||||||
|
newCertPEM, newKeyPEM := generateCertificatePair(t, []string{"managed.example.com"})
|
||||||
|
started := make(chan struct{}, 1)
|
||||||
|
release := make(chan struct{})
|
||||||
|
restore := SetTLSCertificateObtainFuncForTest(func(c *model.TLSCertificate) error {
|
||||||
|
started <- struct{}{}
|
||||||
|
<-release
|
||||||
|
c.CertPEM = newCertPEM
|
||||||
|
c.KeyPEM = newKeyPEM
|
||||||
|
c.NotBefore = time.Now().Add(-time.Hour)
|
||||||
|
c.NotAfter = time.Now().Add(90 * 24 * time.Hour)
|
||||||
|
c.ApplyStatus = "ready"
|
||||||
|
c.ApplyMessage = ""
|
||||||
|
return model.DB.Save(c).Error
|
||||||
|
})
|
||||||
|
t.Cleanup(restore)
|
||||||
|
|
||||||
|
converted, err := ConvertTLSCertificateToAcme(cert.ID, TLSApplyInput{
|
||||||
|
Name: "managed-cert",
|
||||||
|
Remark: "converted",
|
||||||
|
AcmeAccountID: 1,
|
||||||
|
DnsAccountID: 2,
|
||||||
|
KeyAlgorithm: "EC256",
|
||||||
|
AutoRenew: true,
|
||||||
|
PrimaryDomain: "managed.example.com",
|
||||||
|
OtherDomains: "www.managed.example.com",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ConvertTLSCertificateToAcme failed: %v", err)
|
||||||
|
}
|
||||||
|
if converted.ID != cert.ID {
|
||||||
|
t.Fatalf("expected converted certificate to keep id %d, got %d", cert.ID, converted.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-started:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("expected conversion obtain task to start")
|
||||||
|
}
|
||||||
|
|
||||||
|
applying, err := model.GetTLSCertificateByID(cert.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("reload applying certificate failed: %v", err)
|
||||||
|
}
|
||||||
|
if applying.Provider != "upload" {
|
||||||
|
t.Fatalf("expected provider to remain upload while applying, got %s", applying.Provider)
|
||||||
|
}
|
||||||
|
if applying.ApplyStatus != "applying" {
|
||||||
|
t.Fatalf("expected applying status, got %s", applying.ApplyStatus)
|
||||||
|
}
|
||||||
|
if applying.CertPEM != originalCertPEM || applying.KeyPEM != originalKeyPEM {
|
||||||
|
t.Fatal("expected original PEM payloads to be preserved while applying")
|
||||||
|
}
|
||||||
|
|
||||||
|
close(release)
|
||||||
|
|
||||||
|
finalCert := waitForCertificateState(t, cert.ID, func(c *model.TLSCertificate) bool {
|
||||||
|
return c.Provider == "acme" && c.ApplyStatus == "ready"
|
||||||
|
})
|
||||||
|
if finalCert.CertPEM != newCertPEM || finalCert.KeyPEM != newKeyPEM {
|
||||||
|
t.Fatal("expected successful conversion to replace PEM payloads")
|
||||||
|
}
|
||||||
|
if !finalCert.AutoRenew {
|
||||||
|
t.Fatal("expected converted certificate to keep auto renew enabled")
|
||||||
|
}
|
||||||
|
if finalCert.PrimaryDomain != "managed.example.com" || finalCert.OtherDomains != "www.managed.example.com" {
|
||||||
|
t.Fatalf("expected converted certificate to persist ACME domains, got %+v", finalCert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertTLSCertificateToAcmePreservesUploadOnFailure(t *testing.T) {
|
||||||
|
setupServiceTestDB(t)
|
||||||
|
|
||||||
|
originalCertPEM, originalKeyPEM := generateCertificatePair(t, []string{"manual.example.com"})
|
||||||
|
cert, err := CreateTLSCertificate(TLSCertificateInput{
|
||||||
|
Name: "manual-cert",
|
||||||
|
CertPEM: originalCertPEM,
|
||||||
|
KeyPEM: originalKeyPEM,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateTLSCertificate failed: %v", err)
|
||||||
|
}
|
||||||
|
originalCertPEM = cert.CertPEM
|
||||||
|
originalKeyPEM = cert.KeyPEM
|
||||||
|
|
||||||
|
restore := SetTLSCertificateObtainFuncForTest(func(c *model.TLSCertificate) error {
|
||||||
|
err := errors.New("dns challenge failed")
|
||||||
|
updateCertError(c, err.Error())
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
t.Cleanup(restore)
|
||||||
|
|
||||||
|
if _, err := ConvertTLSCertificateToAcme(cert.ID, TLSApplyInput{
|
||||||
|
Name: "manual-cert",
|
||||||
|
DnsAccountID: 1,
|
||||||
|
PrimaryDomain: "manual.example.com",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("ConvertTLSCertificateToAcme failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalCert := waitForCertificateState(t, cert.ID, func(c *model.TLSCertificate) bool {
|
||||||
|
return c.ApplyStatus == "error"
|
||||||
|
})
|
||||||
|
if finalCert.Provider != "upload" {
|
||||||
|
t.Fatalf("expected failed conversion to keep upload provider, got %s", finalCert.Provider)
|
||||||
|
}
|
||||||
|
if finalCert.CertPEM != originalCertPEM || finalCert.KeyPEM != originalKeyPEM {
|
||||||
|
t.Fatal("expected failed conversion to preserve original PEM payloads")
|
||||||
|
}
|
||||||
|
if !strings.Contains(finalCert.ApplyMessage, "dns challenge failed") {
|
||||||
|
t.Fatalf("expected conversion error message, got %q", finalCert.ApplyMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertTLSCertificateToAcmeRejectsInvalidStates(t *testing.T) {
|
||||||
|
setupServiceTestDB(t)
|
||||||
|
|
||||||
|
certPEM, keyPEM := generateCertificatePair(t, []string{"manual.example.com"})
|
||||||
|
cert, err := CreateTLSCertificate(TLSCertificateInput{
|
||||||
|
Name: "manual-cert",
|
||||||
|
CertPEM: certPEM,
|
||||||
|
KeyPEM: keyPEM,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateTLSCertificate failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert.Provider = "acme"
|
||||||
|
if err := cert.Update(); err != nil {
|
||||||
|
t.Fatalf("failed to mark certificate acme: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := ConvertTLSCertificateToAcme(cert.ID, TLSApplyInput{Name: "manual-cert"}); err == nil || !strings.Contains(err.Error(), "only uploaded") {
|
||||||
|
t.Fatalf("expected non-upload conversion to fail, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert.Provider = "upload"
|
||||||
|
cert.ApplyStatus = "applying"
|
||||||
|
if err := cert.Update(); err != nil {
|
||||||
|
t.Fatalf("failed to mark certificate applying: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := ConvertTLSCertificateToAcme(cert.ID, TLSApplyInput{Name: "manual-cert"}); err == nil || !strings.Contains(err.Error(), "already applying") {
|
||||||
|
t.Fatalf("expected applying conversion to fail, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForCertificateState(t *testing.T, id uint, matches func(*model.TLSCertificate) bool) *model.TLSCertificate {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
deadline := time.Now().Add(2 * time.Second)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
cert, err := model.GetTLSCertificateByID(id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("reload certificate %d failed: %v", id, err)
|
||||||
|
}
|
||||||
|
if matches(cert) {
|
||||||
|
return cert
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := model.GetTLSCertificateByID(id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("reload certificate %d failed: %v", id, err)
|
||||||
|
}
|
||||||
|
t.Fatalf("certificate %d did not reach expected state: %+v", id, cert)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,16 @@ type TLSApplyInput struct {
|
|||||||
DNS2 string `json:"dns2"`
|
DNS2 string `json:"dns2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var obtainTLSCertificate = ObtainSSL
|
||||||
|
|
||||||
|
func SetTLSCertificateObtainFuncForTest(fn func(*model.TLSCertificate) error) func() {
|
||||||
|
previous := obtainTLSCertificate
|
||||||
|
obtainTLSCertificate = fn
|
||||||
|
return func() {
|
||||||
|
obtainTLSCertificate = previous
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ListTLSCertificates() ([]*model.TLSCertificate, error) {
|
func ListTLSCertificates() ([]*model.TLSCertificate, error) {
|
||||||
return model.ListTLSCertificates()
|
return model.ListTLSCertificates()
|
||||||
}
|
}
|
||||||
@@ -191,7 +201,7 @@ func ApplyTLSCertificate(input TLSApplyInput) (*model.TLSCertificate, error) {
|
|||||||
|
|
||||||
// Async obtain SSL
|
// Async obtain SSL
|
||||||
go func(c *model.TLSCertificate) {
|
go func(c *model.TLSCertificate) {
|
||||||
_ = ObtainSSL(c)
|
_ = obtainTLSCertificate(c)
|
||||||
}(cert)
|
}(cert)
|
||||||
|
|
||||||
return cert, nil
|
return cert, nil
|
||||||
@@ -233,7 +243,64 @@ func UpdateAcmeCertificate(id uint, input TLSApplyInput) (*model.TLSCertificate,
|
|||||||
|
|
||||||
// Async obtain SSL with updated config
|
// Async obtain SSL with updated config
|
||||||
go func(c *model.TLSCertificate) {
|
go func(c *model.TLSCertificate) {
|
||||||
_ = ObtainSSL(c)
|
_ = obtainTLSCertificate(c)
|
||||||
|
}(cert)
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertTLSCertificateToAcme(id uint, input TLSApplyInput) (*model.TLSCertificate, error) {
|
||||||
|
cert, err := model.GetTLSCertificateByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cert.Provider != "upload" {
|
||||||
|
return nil, errors.New("only uploaded certificates can be converted to acme")
|
||||||
|
}
|
||||||
|
if cert.ApplyStatus == "applying" {
|
||||||
|
return nil, errors.New("certificate is already applying")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := strings.TrimSpace(input.Name)
|
||||||
|
if name == "" {
|
||||||
|
return nil, errors.New("certificate name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert.Name = name
|
||||||
|
cert.Remark = strings.TrimSpace(input.Remark)
|
||||||
|
cert.AcmeAccountID = input.AcmeAccountID
|
||||||
|
cert.DnsAccountID = input.DnsAccountID
|
||||||
|
cert.KeyAlgorithm = input.KeyAlgorithm
|
||||||
|
cert.AutoRenew = input.AutoRenew
|
||||||
|
cert.PrimaryDomain = strings.TrimSpace(input.PrimaryDomain)
|
||||||
|
cert.OtherDomains = strings.TrimSpace(input.OtherDomains)
|
||||||
|
cert.DisableCNAME = input.DisableCNAME
|
||||||
|
cert.SkipDNS = input.SkipDNS
|
||||||
|
cert.DNS1 = strings.TrimSpace(input.DNS1)
|
||||||
|
cert.DNS2 = strings.TrimSpace(input.DNS2)
|
||||||
|
cert.ApplyStatus = "applying"
|
||||||
|
cert.ApplyMessage = ""
|
||||||
|
|
||||||
|
if err := cert.Update(); err != nil {
|
||||||
|
if isUniqueConstraintError(err) {
|
||||||
|
return nil, errors.New("certificate name already exists")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(c *model.TLSCertificate) {
|
||||||
|
if err := obtainTLSCertificate(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
latest, err := model.GetTLSCertificateByID(c.ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
latest.Provider = "acme"
|
||||||
|
latest.ApplyStatus = "ready"
|
||||||
|
latest.ApplyMessage = ""
|
||||||
|
_ = latest.Update()
|
||||||
}(cert)
|
}(cert)
|
||||||
|
|
||||||
return cert, nil
|
return cert, nil
|
||||||
@@ -250,7 +317,7 @@ func RenewTLSCertificate(id uint) (*model.TLSCertificate, error) {
|
|||||||
|
|
||||||
// Async obtain SSL
|
// Async obtain SSL
|
||||||
go func(c *model.TLSCertificate) {
|
go func(c *model.TLSCertificate) {
|
||||||
_ = ObtainSSL(c)
|
_ = obtainTLSCertificate(c)
|
||||||
}(cert)
|
}(cert)
|
||||||
|
|
||||||
cert.ApplyStatus = "applying"
|
cert.ApplyStatus = "applying"
|
||||||
|
|||||||
@@ -158,15 +158,15 @@ function DnsAccountCreateModal({ isOpen, onClose, onCreated }: { isOpen: boolean
|
|||||||
<form onSubmit={onSubmit} className="space-y-5">
|
<form onSubmit={onSubmit} className="space-y-5">
|
||||||
{error && <InlineMessage tone="danger" message={error} />}
|
{error && <InlineMessage tone="danger" message={error} />}
|
||||||
<ResourceField label="账号名称" error={formState.errors.name?.message as string}>
|
<ResourceField label="账号名称" error={formState.errors.name?.message as string}>
|
||||||
<ResourceInput placeholder="例如:我的 Cloudflare" {...register('name', { required: '请输入名称' })} />
|
<ResourceInput placeholder="Cloudlfare 邮箱账号" {...register('name', { required: '请输入名称' })} />
|
||||||
</ResourceField>
|
</ResourceField>
|
||||||
<ResourceField label="DNS 服务商">
|
<ResourceField label="DNS 服务商">
|
||||||
<ResourceSelect {...register('type')}>
|
<ResourceSelect {...register('type')}>
|
||||||
<option value="cloudflare">Cloudflare</option>
|
<option value="cloudflare">Cloudflare</option>
|
||||||
</ResourceSelect>
|
</ResourceSelect>
|
||||||
</ResourceField>
|
</ResourceField>
|
||||||
<ResourceField label="API Token (Authorization)" hint="输入对应 DNS 平台提供的 API Token。">
|
<ResourceField label="API Token" hint="请勿使用 Global API Key">
|
||||||
<ResourceInput placeholder="xxxxxxxxxxxxxxxxxxxxxxxxxxx" {...register('authorization', { required: '请输入 Token' })} />
|
<ResourceInput {...register('authorization', { required: '请输入 Token' })} />
|
||||||
</ResourceField>
|
</ResourceField>
|
||||||
<PrimaryButton type="submit" disabled={createMutation.isPending}>
|
<PrimaryButton type="submit" disabled={createMutation.isPending}>
|
||||||
{createMutation.isPending ? '提交中...' : '提交'}
|
{createMutation.isPending ? '提交中...' : '提交'}
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ export function getTlsCertificate(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTlsCertificateContent(id: number) {
|
export function getTlsCertificateContent(id: number) {
|
||||||
return apiRequest<TlsCertificateContentItem>(`/tls-certificates/${id}/content`);
|
return apiRequest<TlsCertificateContentItem>(
|
||||||
|
`/tls-certificates/${id}/content`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateTlsCertificate(
|
export function updateTlsCertificate(
|
||||||
@@ -38,7 +40,9 @@ export function updateTlsCertificate(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importTlsCertificateFiles(payload: TlsCertificateFileImportPayload) {
|
export function importTlsCertificateFiles(
|
||||||
|
payload: TlsCertificateFileImportPayload,
|
||||||
|
) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('name', payload.name);
|
formData.append('name', payload.name);
|
||||||
formData.append('remark', payload.remark);
|
formData.append('remark', payload.remark);
|
||||||
@@ -53,7 +57,7 @@ export function importTlsCertificateFiles(payload: TlsCertificateFileImportPaylo
|
|||||||
|
|
||||||
export function deleteTlsCertificate(id: number) {
|
export function deleteTlsCertificate(id: number) {
|
||||||
return apiRequest<void>(`/tls-certificates/${id}/delete`, {
|
return apiRequest<void>(`/tls-certificates/${id}/delete`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,9 +74,25 @@ export function renewTlsCertificate(id: number) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateAcmeCertificate(id: number, payload: TlsCertificateApplyPayload) {
|
export function updateAcmeCertificate(
|
||||||
|
id: number,
|
||||||
|
payload: TlsCertificateApplyPayload,
|
||||||
|
) {
|
||||||
return apiRequest<TlsCertificateItem>(`/tls-certificates/${id}/update-acme`, {
|
return apiRequest<TlsCertificateItem>(`/tls-certificates/${id}/update-acme`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertTlsCertificateToAcme(
|
||||||
|
id: number,
|
||||||
|
payload: TlsCertificateApplyPayload,
|
||||||
|
) {
|
||||||
|
return apiRequest<TlsCertificateItem>(
|
||||||
|
`/tls-certificates/${id}/convert-acme`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
+59
-14
@@ -21,7 +21,10 @@ import { CertificateDetailModal } from '@/features/websites/components/certifica
|
|||||||
import { CertificateEditorModal } from '@/features/websites/components/certificate-editor-modal';
|
import { CertificateEditorModal } from '@/features/websites/components/certificate-editor-modal';
|
||||||
import { CertificateImportModal } from '@/features/websites/components/certificate-import-modal';
|
import { CertificateImportModal } from '@/features/websites/components/certificate-import-modal';
|
||||||
import { CertificateApplyModal } from '@/features/websites/components/certificate-apply-modal';
|
import { CertificateApplyModal } from '@/features/websites/components/certificate-apply-modal';
|
||||||
import { getCertificateStatus, getErrorMessage } from '@/features/websites/utils';
|
import {
|
||||||
|
getCertificateStatus,
|
||||||
|
getErrorMessage,
|
||||||
|
} from '@/features/websites/utils';
|
||||||
import {
|
import {
|
||||||
DangerButton,
|
DangerButton,
|
||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
@@ -35,6 +38,7 @@ type FeedbackState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const certificatesQueryKey = ['tls-certificates', 'list'] as const;
|
const certificatesQueryKey = ['tls-certificates', 'list'] as const;
|
||||||
|
type CertificateApplyMode = 'edit-acme' | 'convert-upload';
|
||||||
|
|
||||||
export function TlsCertificatesPage() {
|
export function TlsCertificatesPage() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -46,7 +50,9 @@ export function TlsCertificatesPage() {
|
|||||||
>(null);
|
>(null);
|
||||||
const [isDetailOpen, setIsDetailOpen] = useState(false);
|
const [isDetailOpen, setIsDetailOpen] = useState(false);
|
||||||
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
||||||
const [editAcmeCertificate, setEditAcmeCertificate] = useState<TlsCertificateItem | null>(null);
|
const [applyCertificate, setApplyCertificate] =
|
||||||
|
useState<TlsCertificateItem | null>(null);
|
||||||
|
const [applyMode, setApplyMode] = useState<CertificateApplyMode>('edit-acme');
|
||||||
|
|
||||||
const certificatesQuery = useQuery({
|
const certificatesQuery = useQuery({
|
||||||
queryKey: certificatesQueryKey,
|
queryKey: certificatesQueryKey,
|
||||||
@@ -67,7 +73,10 @@ export function TlsCertificatesPage() {
|
|||||||
const renewCertificateMutation = useMutation({
|
const renewCertificateMutation = useMutation({
|
||||||
mutationFn: renewTlsCertificate,
|
mutationFn: renewTlsCertificate,
|
||||||
onSuccess: async (cert) => {
|
onSuccess: async (cert) => {
|
||||||
setFeedback({ tone: 'success', message: `证书 ${cert.name} 续期任务已提交。` });
|
setFeedback({
|
||||||
|
tone: 'success',
|
||||||
|
message: `证书 ${cert.name} 续期任务已提交。`,
|
||||||
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['tls-certificates'] });
|
await queryClient.invalidateQueries({ queryKey: ['tls-certificates'] });
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@@ -105,7 +114,8 @@ export function TlsCertificatesPage() {
|
|||||||
|
|
||||||
const handleOpenCertificateEditor = (certificate: TlsCertificateItem) => {
|
const handleOpenCertificateEditor = (certificate: TlsCertificateItem) => {
|
||||||
if (certificate.provider === 'acme') {
|
if (certificate.provider === 'acme') {
|
||||||
setEditAcmeCertificate(certificate);
|
setApplyMode('edit-acme');
|
||||||
|
setApplyCertificate(certificate);
|
||||||
} else {
|
} else {
|
||||||
setSelectedCertificateId(certificate.id);
|
setSelectedCertificateId(certificate.id);
|
||||||
setIsEditorOpen(true);
|
setIsEditorOpen(true);
|
||||||
@@ -142,7 +152,10 @@ export function TlsCertificatesPage() {
|
|||||||
>
|
>
|
||||||
DNS 账号
|
DNS 账号
|
||||||
</Link>
|
</Link>
|
||||||
<PrimaryButton type="button" onClick={() => setIsImportOpen(true)}>
|
<PrimaryButton
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsImportOpen(true)}
|
||||||
|
>
|
||||||
导入证书
|
导入证书
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
<PrimaryButton type="button" onClick={() => setIsApplyOpen(true)}>
|
<PrimaryButton type="button" onClick={() => setIsApplyOpen(true)}>
|
||||||
@@ -196,9 +209,28 @@ export function TlsCertificatesPage() {
|
|||||||
<div className="text-xs leading-5 text-[var(--foreground-secondary)]">
|
<div className="text-xs leading-5 text-[var(--foreground-secondary)]">
|
||||||
<p>生效:{formatDateTime(certificate.not_before)}</p>
|
<p>生效:{formatDateTime(certificate.not_before)}</p>
|
||||||
<p>到期:{formatDateTime(certificate.not_after)}</p>
|
<p>到期:{formatDateTime(certificate.not_after)}</p>
|
||||||
<p>来源:{certificate.provider === 'acme' ? 'ACME 申请' : '手动上传'}</p>
|
<p>
|
||||||
{certificate.apply_status === 'applying' && <p className="text-blue-500">状态:申请中...</p>}
|
来源:
|
||||||
{certificate.apply_status === 'error' && <p className="text-red-500">状态:申请失败 ({certificate.apply_message})</p>}
|
{certificate.provider === 'acme'
|
||||||
|
? 'ACME 申请'
|
||||||
|
: '手动上传'}
|
||||||
|
</p>
|
||||||
|
{certificate.provider === 'upload' &&
|
||||||
|
certificate.apply_status === 'applying' ? (
|
||||||
|
<p className="text-blue-500">状态:转换申请中...</p>
|
||||||
|
) : certificate.apply_status === 'applying' ? (
|
||||||
|
<p className="text-blue-500">状态:申请中...</p>
|
||||||
|
) : null}
|
||||||
|
{certificate.provider === 'upload' &&
|
||||||
|
certificate.apply_status === 'error' ? (
|
||||||
|
<p className="text-red-500">
|
||||||
|
状态:转换失败 ({certificate.apply_message})
|
||||||
|
</p>
|
||||||
|
) : certificate.apply_status === 'error' ? (
|
||||||
|
<p className="text-red-500">
|
||||||
|
状态:申请失败 ({certificate.apply_message})
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
<p>备注:{certificate.remark || '暂无备注'}</p>
|
<p>备注:{certificate.remark || '暂无备注'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -206,14 +238,18 @@ export function TlsCertificatesPage() {
|
|||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleOpenCertificateDetail(certificate)}
|
onClick={() =>
|
||||||
|
handleOpenCertificateDetail(certificate)
|
||||||
|
}
|
||||||
className="px-3 py-2 text-xs"
|
className="px-3 py-2 text-xs"
|
||||||
>
|
>
|
||||||
查看
|
查看
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleOpenCertificateEditor(certificate)}
|
onClick={() =>
|
||||||
|
handleOpenCertificateEditor(certificate)
|
||||||
|
}
|
||||||
className="px-3 py-2 text-xs"
|
className="px-3 py-2 text-xs"
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
@@ -272,15 +308,19 @@ export function TlsCertificatesPage() {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{editAcmeCertificate ? (
|
{applyCertificate ? (
|
||||||
<CertificateApplyModal
|
<CertificateApplyModal
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
onClose={() => setEditAcmeCertificate(null)}
|
onClose={() => setApplyCertificate(null)}
|
||||||
editCertificate={editAcmeCertificate}
|
mode={applyMode}
|
||||||
|
certificate={applyCertificate}
|
||||||
onApplied={(certificate) => {
|
onApplied={(certificate) => {
|
||||||
setFeedback({
|
setFeedback({
|
||||||
tone: 'success',
|
tone: 'success',
|
||||||
message: `证书 ${certificate.name} 配置已更新,重新申请中...`,
|
message:
|
||||||
|
applyMode === 'convert-upload'
|
||||||
|
? `证书 ${certificate.name} 转换申请已提交。`
|
||||||
|
: `证书 ${certificate.name} 配置已更新,重新申请中...`,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -324,6 +364,11 @@ export function TlsCertificatesPage() {
|
|||||||
message: `证书 ${certificate.name} 已更新。`,
|
message: `证书 ${certificate.name} 已更新。`,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
onConvert={(certificate) => {
|
||||||
|
setIsEditorOpen(false);
|
||||||
|
setApplyMode('convert-upload');
|
||||||
|
setApplyCertificate(certificate);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ import { useForm } from 'react-hook-form';
|
|||||||
|
|
||||||
import { InlineMessage } from '@/components/feedback/inline-message';
|
import { InlineMessage } from '@/components/feedback/inline-message';
|
||||||
import { AppModal } from '@/components/ui/app-modal';
|
import { AppModal } from '@/components/ui/app-modal';
|
||||||
import { applyTlsCertificate, updateAcmeCertificate } from '@/features/tls-certificates/api/tls-certificates';
|
import {
|
||||||
|
applyTlsCertificate,
|
||||||
|
convertTlsCertificateToAcme,
|
||||||
|
updateAcmeCertificate,
|
||||||
|
} from '@/features/tls-certificates/api/tls-certificates';
|
||||||
import type { TlsCertificateItem } from '@/features/tls-certificates/types';
|
import type { TlsCertificateItem } from '@/features/tls-certificates/types';
|
||||||
import { getDnsAccounts } from '@/features/dns-accounts/api/dns-accounts';
|
import { getDnsAccounts } from '@/features/dns-accounts/api/dns-accounts';
|
||||||
import { getDefaultAcmeAccount } from '@/features/acme-accounts/api/acme-accounts';
|
import { getDefaultAcmeAccount } from '@/features/acme-accounts/api/acme-accounts';
|
||||||
@@ -29,17 +33,22 @@ interface CertificateApplyModalProps {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onApplied?: (certificate: TlsCertificateItem) => void;
|
onApplied?: (certificate: TlsCertificateItem) => void;
|
||||||
editCertificate?: TlsCertificateItem | null;
|
mode?: 'create' | 'edit-acme' | 'convert-upload';
|
||||||
|
certificate?: TlsCertificateItem | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CertificateApplyModal({
|
export function CertificateApplyModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
onApplied,
|
onApplied,
|
||||||
editCertificate,
|
mode = 'create',
|
||||||
|
certificate,
|
||||||
}: CertificateApplyModalProps) {
|
}: CertificateApplyModalProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [feedback, setFeedback] = useState<{ tone: 'success' | 'danger'; message: string } | null>(null);
|
const [feedback, setFeedback] = useState<{
|
||||||
|
tone: 'success' | 'danger';
|
||||||
|
message: string;
|
||||||
|
} | null>(null);
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||||
|
|
||||||
const dnsAccountsQuery = useQuery({
|
const dnsAccountsQuery = useQuery({
|
||||||
@@ -63,41 +72,59 @@ export function CertificateApplyModal({
|
|||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
setFeedback(null);
|
setFeedback(null);
|
||||||
setShowAdvanced(false);
|
setShowAdvanced(false);
|
||||||
|
|
||||||
if (editCertificate) {
|
if (certificate) {
|
||||||
form.reset({
|
form.reset({
|
||||||
name: editCertificate.name,
|
name: certificate.name,
|
||||||
primary_domain: editCertificate.primary_domain || '',
|
primary_domain:
|
||||||
other_domains: editCertificate.other_domains || '',
|
mode === 'convert-upload' ? '' : certificate.primary_domain || '',
|
||||||
remark: editCertificate.remark || '',
|
other_domains:
|
||||||
acme_account_id: editCertificate.acme_account_id,
|
mode === 'convert-upload' ? '' : certificate.other_domains || '',
|
||||||
dns_account_id: editCertificate.dns_account_id,
|
remark: certificate.remark || '',
|
||||||
key_algorithm: editCertificate.key_algorithm as any || 'EC256',
|
acme_account_id: certificate.acme_account_id,
|
||||||
auto_renew: editCertificate.auto_renew,
|
dns_account_id:
|
||||||
dns1: editCertificate.dns1 || '',
|
mode === 'convert-upload' ? 0 : certificate.dns_account_id,
|
||||||
dns2: editCertificate.dns2 || '',
|
key_algorithm: certificate.key_algorithm || 'EC256',
|
||||||
disable_cname: editCertificate.disable_cname,
|
auto_renew: mode === 'convert-upload' ? true : certificate.auto_renew,
|
||||||
skip_dns: editCertificate.skip_dns,
|
dns1: mode === 'convert-upload' ? '' : certificate.dns1 || '',
|
||||||
|
dns2: mode === 'convert-upload' ? '' : certificate.dns2 || '',
|
||||||
|
disable_cname:
|
||||||
|
mode === 'convert-upload' ? false : certificate.disable_cname,
|
||||||
|
skip_dns: mode === 'convert-upload' ? false : certificate.skip_dns,
|
||||||
});
|
});
|
||||||
if (editCertificate.dns1 || editCertificate.dns2 || editCertificate.disable_cname || editCertificate.skip_dns) {
|
if (
|
||||||
|
mode !== 'convert-upload' &&
|
||||||
|
(certificate.dns1 ||
|
||||||
|
certificate.dns2 ||
|
||||||
|
certificate.disable_cname ||
|
||||||
|
certificate.skip_dns)
|
||||||
|
) {
|
||||||
setShowAdvanced(true);
|
setShowAdvanced(true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
form.reset(defaultAcmeApplyValues);
|
form.reset(defaultAcmeApplyValues);
|
||||||
}
|
}
|
||||||
}, [isOpen, form, editCertificate]);
|
}, [isOpen, form, mode, certificate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultAcmeAccountQuery.data) {
|
if (
|
||||||
|
defaultAcmeAccountQuery.data &&
|
||||||
|
form.getValues('acme_account_id') === 0
|
||||||
|
) {
|
||||||
form.setValue('acme_account_id', defaultAcmeAccountQuery.data.id);
|
form.setValue('acme_account_id', defaultAcmeAccountQuery.data.id);
|
||||||
}
|
}
|
||||||
}, [defaultAcmeAccountQuery.data, form]);
|
}, [defaultAcmeAccountQuery.data, form, isOpen]);
|
||||||
|
|
||||||
const applyMutation = useMutation({
|
const applyMutation = useMutation({
|
||||||
mutationFn: (values: AcmeApplyFormValues) =>
|
mutationFn: (values: AcmeApplyFormValues) => {
|
||||||
editCertificate
|
if (mode === 'edit-acme' && certificate) {
|
||||||
? updateAcmeCertificate(editCertificate.id, values)
|
return updateAcmeCertificate(certificate.id, values);
|
||||||
: applyTlsCertificate(values),
|
}
|
||||||
|
if (mode === 'convert-upload' && certificate) {
|
||||||
|
return convertTlsCertificateToAcme(certificate.id, values);
|
||||||
|
}
|
||||||
|
return applyTlsCertificate(values);
|
||||||
|
},
|
||||||
onSuccess: async (certificate) => {
|
onSuccess: async (certificate) => {
|
||||||
await queryClient.invalidateQueries({ queryKey: ['tls-certificates'] });
|
await queryClient.invalidateQueries({ queryKey: ['tls-certificates'] });
|
||||||
onApplied?.(certificate);
|
onApplied?.(certificate);
|
||||||
@@ -117,8 +144,20 @@ export function CertificateApplyModal({
|
|||||||
<AppModal
|
<AppModal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={editCertificate ? "编辑并重新申请证书" : "申请证书"}
|
title={
|
||||||
description={editCertificate ? "修改 ACME 证书配置。保存后将使用新配置重新申请证书。" : "使用 ACME (Let's Encrypt 等) 自动申请和续期证书,支持通配符域名。"}
|
mode === 'edit-acme'
|
||||||
|
? '编辑并重新申请证书'
|
||||||
|
: mode === 'convert-upload'
|
||||||
|
? '转换为申请证书'
|
||||||
|
: '申请证书'
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
mode === 'edit-acme'
|
||||||
|
? '修改 ACME 证书配置。保存后将使用新配置重新申请证书。'
|
||||||
|
: mode === 'convert-upload'
|
||||||
|
? '填写 ACME 申请资料。申请成功后,当前手动证书会原地转换为可自动续签的申请证书。'
|
||||||
|
: "使用 ACME (Let's Encrypt 等) 自动申请和续期证书,支持通配符域名。"
|
||||||
|
}
|
||||||
size="xl"
|
size="xl"
|
||||||
>
|
>
|
||||||
<form className="space-y-5" onSubmit={onSubmit}>
|
<form className="space-y-5" onSubmit={onSubmit}>
|
||||||
@@ -131,13 +170,19 @@ export function CertificateApplyModal({
|
|||||||
label="证书名称"
|
label="证书名称"
|
||||||
error={form.formState.errors.name?.message}
|
error={form.formState.errors.name?.message}
|
||||||
>
|
>
|
||||||
<ResourceInput placeholder="例如:主站证书" {...form.register('name')} />
|
<ResourceInput
|
||||||
|
placeholder="例如:主站证书"
|
||||||
|
{...form.register('name')}
|
||||||
|
/>
|
||||||
</ResourceField>
|
</ResourceField>
|
||||||
<ResourceField
|
<ResourceField
|
||||||
label="主域名"
|
label="主域名"
|
||||||
error={form.formState.errors.primary_domain?.message}
|
error={form.formState.errors.primary_domain?.message}
|
||||||
>
|
>
|
||||||
<ResourceInput placeholder="example.com 或 *.example.com" {...form.register('primary_domain')} />
|
<ResourceInput
|
||||||
|
placeholder="example.com 或 *.example.com"
|
||||||
|
{...form.register('primary_domain')}
|
||||||
|
/>
|
||||||
</ResourceField>
|
</ResourceField>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -147,7 +192,7 @@ export function CertificateApplyModal({
|
|||||||
error={form.formState.errors.other_domains?.message}
|
error={form.formState.errors.other_domains?.message}
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full rounded-xl border border-[var(--border-default)] bg-[var(--control-background)] px-4 py-3 text-sm text-[var(--foreground-primary)] outline-none transition focus:border-[var(--brand-primary)]"
|
className="w-full rounded-xl border border-[var(--border-default)] bg-[var(--control-background)] px-4 py-3 text-sm text-[var(--foreground-primary)] transition outline-none focus:border-[var(--brand-primary)]"
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder="example.net"
|
placeholder="example.net"
|
||||||
{...form.register('other_domains')}
|
{...form.register('other_domains')}
|
||||||
@@ -187,7 +232,10 @@ export function CertificateApplyModal({
|
|||||||
label="备注"
|
label="备注"
|
||||||
error={form.formState.errors.remark?.message}
|
error={form.formState.errors.remark?.message}
|
||||||
>
|
>
|
||||||
<ResourceInput placeholder="可选,用于记录证书用途。" {...form.register('remark')} />
|
<ResourceInput
|
||||||
|
placeholder="可选,用于记录证书用途。"
|
||||||
|
{...form.register('remark')}
|
||||||
|
/>
|
||||||
</ResourceField>
|
</ResourceField>
|
||||||
|
|
||||||
<ToggleField
|
<ToggleField
|
||||||
@@ -198,7 +246,7 @@ export function CertificateApplyModal({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] overflow-hidden">
|
<div className="overflow-hidden rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex w-full items-center justify-between px-4 py-3 text-sm font-medium text-[var(--foreground-primary)] transition hover:bg-[var(--surface-muted)]"
|
className="flex w-full items-center justify-between px-4 py-3 text-sm font-medium text-[var(--foreground-primary)] transition hover:bg-[var(--surface-muted)]"
|
||||||
@@ -211,7 +259,12 @@ export function CertificateApplyModal({
|
|||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M19 9l-7 7-7-7"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{showAdvanced && (
|
{showAdvanced && (
|
||||||
@@ -222,14 +275,20 @@ export function CertificateApplyModal({
|
|||||||
hint="可选,如 8.8.8.8"
|
hint="可选,如 8.8.8.8"
|
||||||
error={form.formState.errors.dns1?.message}
|
error={form.formState.errors.dns1?.message}
|
||||||
>
|
>
|
||||||
<ResourceInput placeholder="为空则使用默认权威 DNS" {...form.register('dns1')} />
|
<ResourceInput
|
||||||
|
placeholder="为空则使用默认权威 DNS"
|
||||||
|
{...form.register('dns1')}
|
||||||
|
/>
|
||||||
</ResourceField>
|
</ResourceField>
|
||||||
<ResourceField
|
<ResourceField
|
||||||
label="DNS 验证服务器 2"
|
label="DNS 验证服务器 2"
|
||||||
hint="可选,如 1.1.1.1"
|
hint="可选,如 1.1.1.1"
|
||||||
error={form.formState.errors.dns2?.message}
|
error={form.formState.errors.dns2?.message}
|
||||||
>
|
>
|
||||||
<ResourceInput placeholder="为空则使用默认权威 DNS" {...form.register('dns2')} />
|
<ResourceInput
|
||||||
|
placeholder="为空则使用默认权威 DNS"
|
||||||
|
{...form.register('dns2')}
|
||||||
|
/>
|
||||||
</ResourceField>
|
</ResourceField>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
@@ -237,7 +296,9 @@ export function CertificateApplyModal({
|
|||||||
label="跳过 CNAME 检查"
|
label="跳过 CNAME 检查"
|
||||||
description="在执行 DNS-01 验证时不追踪 CNAME 记录。"
|
description="在执行 DNS-01 验证时不追踪 CNAME 记录。"
|
||||||
checked={form.watch('disable_cname')}
|
checked={form.watch('disable_cname')}
|
||||||
onChange={(checked) => form.setValue('disable_cname', checked)}
|
onChange={(checked) =>
|
||||||
|
form.setValue('disable_cname', checked)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<ToggleField
|
<ToggleField
|
||||||
label="跳过 DNS 前置检查"
|
label="跳过 DNS 前置检查"
|
||||||
@@ -251,7 +312,13 @@ export function CertificateApplyModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PrimaryButton type="submit" disabled={applyMutation.isPending}>
|
<PrimaryButton type="submit" disabled={applyMutation.isPending}>
|
||||||
{applyMutation.isPending ? '提交中...' : (editCertificate ? '保存并申请' : '开始申请')}
|
{applyMutation.isPending
|
||||||
|
? '提交中...'
|
||||||
|
: mode === 'edit-acme'
|
||||||
|
? '保存并申请'
|
||||||
|
: mode === 'convert-upload'
|
||||||
|
? '开始转换'
|
||||||
|
: '开始申请'}
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</form>
|
</form>
|
||||||
</AppModal>
|
</AppModal>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ interface CertificateEditorModalProps {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSaved?: (certificate: TlsCertificateItem) => void;
|
onSaved?: (certificate: TlsCertificateItem) => void;
|
||||||
|
onConvert?: (certificate: TlsCertificateItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CertificateEditorModal({
|
export function CertificateEditorModal({
|
||||||
@@ -41,6 +42,7 @@ export function CertificateEditorModal({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
onSaved,
|
onSaved,
|
||||||
|
onConvert,
|
||||||
}: CertificateEditorModalProps) {
|
}: CertificateEditorModalProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const form = useForm<ManualImportFormValues>({
|
const form = useForm<ManualImportFormValues>({
|
||||||
@@ -90,6 +92,10 @@ export function CertificateEditorModal({
|
|||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const canConvert =
|
||||||
|
certificateQuery.data?.provider === 'upload' &&
|
||||||
|
certificateQuery.data.apply_status !== 'applying';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppModal
|
<AppModal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
@@ -98,21 +104,38 @@ export function CertificateEditorModal({
|
|||||||
description="可以修改证书名称、备注,以及重新上传 PEM 证书和私钥内容。"
|
description="可以修改证书名称、备注,以及重新上传 PEM 证书和私钥内容。"
|
||||||
size="xl"
|
size="xl"
|
||||||
footer={
|
footer={
|
||||||
<div className="flex flex-wrap justify-end gap-3">
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||||
<SecondaryButton
|
<div>
|
||||||
type="button"
|
{canConvert ? (
|
||||||
onClick={handleClose}
|
<SecondaryButton
|
||||||
disabled={updateMutation.isPending}
|
type="button"
|
||||||
>
|
onClick={() => {
|
||||||
取消
|
if (certificateQuery.data) {
|
||||||
</SecondaryButton>
|
onConvert?.(certificateQuery.data);
|
||||||
<PrimaryButton
|
}
|
||||||
type="submit"
|
}}
|
||||||
form="certificate-editor-form"
|
disabled={updateMutation.isPending}
|
||||||
disabled={updateMutation.isPending || certificateQuery.isLoading}
|
>
|
||||||
>
|
转换来源
|
||||||
{updateMutation.isPending ? '保存中...' : '保存证书'}
|
</SecondaryButton>
|
||||||
</PrimaryButton>
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap justify-end gap-3">
|
||||||
|
<SecondaryButton
|
||||||
|
type="button"
|
||||||
|
onClick={handleClose}
|
||||||
|
disabled={updateMutation.isPending}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</SecondaryButton>
|
||||||
|
<PrimaryButton
|
||||||
|
type="submit"
|
||||||
|
form="certificate-editor-form"
|
||||||
|
disabled={updateMutation.isPending || certificateQuery.isLoading}
|
||||||
|
>
|
||||||
|
{updateMutation.isPending ? '保存中...' : '保存证书'}
|
||||||
|
</PrimaryButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -124,10 +147,7 @@ export function CertificateEditorModal({
|
|||||||
description={getErrorMessage(certificateQuery.error)}
|
description={getErrorMessage(certificateQuery.error)}
|
||||||
/>
|
/>
|
||||||
) : !certificateQuery.data ? (
|
) : !certificateQuery.data ? (
|
||||||
<EmptyState
|
<EmptyState title="证书不存在" description="当前证书可能已被删除。" />
|
||||||
title="证书不存在"
|
|
||||||
description="当前证书可能已被删除。"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<form
|
<form
|
||||||
id="certificate-editor-form"
|
id="certificate-editor-form"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user