diff --git a/api/certificate/certificate.go b/api/certificate/certificate.go
index 23518da2..7ebaca29 100644
--- a/api/certificate/certificate.go
+++ b/api/certificate/certificate.go
@@ -131,6 +131,31 @@ func AddCert(c *gin.Context) {
return
}
+ // Detect and set certificate type
+ if len(json.SSLCertificate) > 0 {
+ keyType, err := cert.GetKeyType(json.SSLCertificate)
+ if err == nil && keyType != "" {
+ // Set KeyType based on certificate type
+ switch keyType {
+ case "2048":
+ certModel.KeyType = certcrypto.RSA2048
+ case "3072":
+ certModel.KeyType = certcrypto.RSA3072
+ case "4096":
+ certModel.KeyType = certcrypto.RSA4096
+ case "P256":
+ certModel.KeyType = certcrypto.EC256
+ case "P384":
+ certModel.KeyType = certcrypto.EC384
+ }
+ // Update certificate model
+ err = certModel.Updates(&model.Cert{KeyType: certModel.KeyType})
+ if err != nil {
+ notification.Error("Update Certificate Type Error", err.Error(), nil)
+ }
+ }
+ }
+
err = cert.SyncToRemoteServer(certModel)
if err != nil {
notification.Error("Sync Certificate Error", err.Error(), nil)
@@ -157,7 +182,8 @@ func ModifyCert(c *gin.Context) {
return
}
- err = certModel.Updates(&model.Cert{
+ // Create update data object
+ updateData := &model.Cert{
Name: json.Name,
SSLCertificatePath: json.SSLCertificatePath,
SSLCertificateKeyPath: json.SSLCertificateKeyPath,
@@ -166,11 +192,6 @@ func ModifyCert(c *gin.Context) {
DnsCredentialID: json.DnsCredentialID,
ACMEUserID: json.ACMEUserID,
SyncNodeIds: json.SyncNodeIds,
- })
-
- if err != nil {
- cosy.ErrHandler(c, err)
- return
}
content := &cert.Content{
@@ -186,6 +207,32 @@ func ModifyCert(c *gin.Context) {
return
}
+ // Detect and set certificate type
+ if len(json.SSLCertificate) > 0 {
+ keyType, err := cert.GetKeyType(json.SSLCertificate)
+ if err == nil && keyType != "" {
+ // Set KeyType based on certificate type
+ switch keyType {
+ case "2048":
+ updateData.KeyType = certcrypto.RSA2048
+ case "3072":
+ updateData.KeyType = certcrypto.RSA3072
+ case "4096":
+ updateData.KeyType = certcrypto.RSA4096
+ case "P256":
+ updateData.KeyType = certcrypto.EC256
+ case "P384":
+ updateData.KeyType = certcrypto.EC384
+ }
+ }
+ }
+
+ err = certModel.Updates(updateData)
+ if err != nil {
+ cosy.ErrHandler(c, err)
+ return
+ }
+
err = cert.SyncToRemoteServer(certModel)
if err != nil {
notification.Error("Sync Certificate Error", err.Error(), nil)
diff --git a/app/src/views/certificate/CertificateList/certColumns.tsx b/app/src/views/certificate/CertificateList/certColumns.tsx
index 6acb1d9b..b8d6d2e5 100644
--- a/app/src/views/certificate/CertificateList/certColumns.tsx
+++ b/app/src/views/certificate/CertificateList/certColumns.tsx
@@ -33,27 +33,24 @@ const columns: Column[] = [{
if (text === true || text === 1) {
template.push(
- { managed }
+ {managed}
,
)
}
else if (text === 2) {
template.push(
- { sync }
+ {sync}
,
)
}
else {
template.push(
- {
- general
- }
+ {general}
,
)
}
-
return h('div', template)
},
sorter: true,
diff --git a/app/src/views/site/site_list/SiteList.vue b/app/src/views/site/site_list/SiteList.vue
index fe543eb8..cc60cab2 100644
--- a/app/src/views/site/site_list/SiteList.vue
+++ b/app/src/views/site/site_list/SiteList.vue
@@ -164,7 +164,7 @@ function handleBatchUpdated() {
:get-params="{
env_group_id: envGroupId,
}"
- :scroll-x="1200"
+ :scroll-x="1600"
@click-edit="(r: string) => router.push({
path: `/sites/${r}`,
})"
diff --git a/app/src/views/stream/StreamList.vue b/app/src/views/stream/StreamList.vue
index ce8d1ecc..b62fc35e 100644
--- a/app/src/views/stream/StreamList.vue
+++ b/app/src/views/stream/StreamList.vue
@@ -24,6 +24,7 @@ const columns: Column[] = [{
type: input,
},
search: true,
+ width: 150,
}, {
title: () => $gettext('Node Group'),
dataIndex: 'env_group_id',
diff --git a/internal/cert/errors.go b/internal/cert/errors.go
index 2a700d8d..480bf755 100644
--- a/internal/cert/errors.go
+++ b/internal/cert/errors.go
@@ -10,4 +10,5 @@ var (
ErrCertParse = e.New(50004, "certificate parse error")
ErrPayloadResourceIsNil = e.New(50005, "payload resource is nil")
ErrPathIsNotUnderTheNginxConfDir = e.New(50006, "path: {0} is not under the nginx conf dir: {1}")
+ ErrCertPathIsEmpty = e.New(50007, "certificate path is empty")
)
diff --git a/internal/cert/helper.go b/internal/cert/helper.go
index de93e995..919e5fb2 100644
--- a/internal/cert/helper.go
+++ b/internal/cert/helper.go
@@ -1,6 +1,8 @@
package cert
import (
+ "crypto/ecdsa"
+ "crypto/rsa"
"crypto/x509"
"encoding/pem"
"os"
@@ -79,3 +81,67 @@ func IsPrivateKeyPath(path string) bool {
return IsPrivateKey(string(bytes))
}
+
+// GetKeyType determines the key type from a PEM certificate string.
+// Returns "2048", "3072", "4096", "P256", "P384" or empty string.
+func GetKeyType(pemStr string) (string, error) {
+ block, _ := pem.Decode([]byte(pemStr))
+ if block == nil {
+ return "", ErrCertDecode
+ }
+
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ return "", ErrCertParse
+ }
+
+ switch cert.PublicKeyAlgorithm {
+ case x509.RSA:
+ rsaKey, ok := cert.PublicKey.(*rsa.PublicKey)
+ if !ok {
+ return "", nil
+ }
+ keySize := rsaKey.Size() * 8 // Size returns size in bytes, convert to bits
+ switch keySize {
+ case 2048:
+ return "2048", nil
+ case 3072:
+ return "3072", nil
+ case 4096:
+ return "4096", nil
+ default:
+ return "", nil
+ }
+ case x509.ECDSA:
+ ecKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
+ if !ok {
+ return "", nil
+ }
+ curve := ecKey.Curve.Params().Name
+ switch curve {
+ case "P-256":
+ return "P256", nil
+ case "P-384":
+ return "P384", nil
+ default:
+ return "", nil
+ }
+ default:
+ return "", nil
+ }
+}
+
+// GetKeyTypeFromPath determines the key type from a certificate file.
+// Returns "2048", "3072", "4096", "P256", "P384" or empty string.
+func GetKeyTypeFromPath(path string) (string, error) {
+ if path == "" {
+ return "", ErrCertPathIsEmpty
+ }
+
+ bytes, err := os.ReadFile(path)
+ if err != nil {
+ return "", err
+ }
+
+ return GetKeyType(string(bytes))
+}