@@ -35,23 +35,53 @@ const cert_type_kind = computed(() => {
3535});
3636
3737const is_acme = computed (() => cert_type_kind .value === " acme" );
38+ const is_generated = computed (() => cert_type_kind .value === " generated" );
39+ const needs_domains = computed (() => is_acme .value || is_generated .value );
40+
41+ function buildDefaultAcmeCertType(): CertType {
42+ return {
43+ t: " acme" ,
44+ account_id: accounts .value [0 ]?.id ?? " " ,
45+ challenge_type: {
46+ dns: { dns_provider: { cloudflare: { api_token: " " } } },
47+ },
48+ key_type: " ecdsa_p256" ,
49+ auto_renew: true ,
50+ renew_before_days: 30 ,
51+ } as CertType ;
52+ }
53+
54+ function buildDefaultGeneratedCertType(): CertType {
55+ return {
56+ t: " generated" ,
57+ validity_days: 365 ,
58+ } as CertType ;
59+ }
60+
61+ function reset_cert_material() {
62+ if (! rule .value ) return ;
63+ rule .value .private_key = undefined ;
64+ rule .value .certificate = undefined ;
65+ rule .value .certificate_chain = undefined ;
66+ rule .value .issued_at = undefined ;
67+ rule .value .expires_at = undefined ;
68+ rule .value .status_message = undefined ;
69+ rule .value .status = " pending" ;
70+ }
3871
3972function on_cert_type_change(val : string ) {
4073 if (! rule .value ) return ;
74+ const domains = [... (rule .value .domains ?? [])];
75+ reset_cert_material ();
76+
4177 if (val === " acme" ) {
42- rule .value .cert_type = {
43- t: " acme" ,
44- account_id: accounts .value [0 ]?.id ?? " " ,
45- challenge_type: {
46- dns: { dns_provider: { cloudflare: { api_token: " " } } },
47- } as unknown as CertType extends { t: " acme" } ? CertType : never ,
48- key_type: " ecdsa_p256" ,
49- auto_renew: true ,
50- renew_before_days: 30 ,
51- } as CertType ;
78+ rule .value .cert_type = buildDefaultAcmeCertType ();
79+ rule .value .domains = domains ;
80+ } else if (val === " generated" ) {
81+ rule .value .cert_type = buildDefaultGeneratedCertType ();
82+ rule .value .domains = domains ;
5283 } else {
5384 rule .value .cert_type = { t: " manual" } as CertType ;
54- // Clear ACME-specific data
5585 rule .value .domains = [];
5686 }
5787}
@@ -99,6 +129,7 @@ const dns_provider_options = [
99129
100130const cert_type_options = [
101131 { label: t (" cert.type_acme" ), value: " acme" },
132+ { label: t (" cert.type_generated" ), value: " generated" },
102133 { label: t (" cert.type_manual" ), value: " manual" },
103134];
104135
@@ -198,16 +229,7 @@ async function enter() {
198229 status: " pending" ,
199230 for_api: false ,
200231 for_gateway: false ,
201- cert_type: {
202- t: " acme" ,
203- account_id: accounts .value [0 ]?.id ?? " " ,
204- challenge_type: {
205- dns: { dns_provider: { cloudflare: { api_token: " " } } },
206- },
207- key_type: " ecdsa_p256" ,
208- auto_renew: true ,
209- renew_before_days: 30 ,
210- } as CertType ,
232+ cert_type: buildDefaultAcmeCertType (),
211233 };
212234 }
213235 // Keep UI consistent with currently supported challenge/provider combinations.
@@ -289,11 +311,26 @@ const rules = {
289311 domains: {
290312 trigger: [" change" ],
291313 validator(_ : unknown , value : string []) {
292- if (is_acme .value && (! value || value .length === 0 ))
314+ if (needs_domains .value && (! value || value .length === 0 ))
293315 return new Error (t (" cert.cert_domains_required" ));
294316 return true ;
295317 },
296318 },
319+ " cert_type.validity_days" : {
320+ trigger: [" blur" , " change" ],
321+ validator() {
322+ if (
323+ is_generated .value &&
324+ (! rule .value ?.cert_type ||
325+ rule .value .cert_type .t !== " generated" ||
326+ ! rule .value .cert_type .validity_days ||
327+ rule .value .cert_type .validity_days < 1 )
328+ ) {
329+ return new Error (t (" cert.generated_validity_days_invalid" ));
330+ }
331+ return true ;
332+ },
333+ },
297334};
298335
299336async function save() {
@@ -355,6 +392,33 @@ async function save() {
355392 </n-switch >
356393 </n-form-item >
357394
395+ <n-form-item
396+ v-if =" needs_domains"
397+ :label =" t('cert.cert_domains')"
398+ path =" domains"
399+ >
400+ <n-dynamic-input
401+ v-model:value =" rule.domains"
402+ placeholder =" example.com"
403+ # =" { index }"
404+ >
405+ <n-form-item
406+ :path =" `domains[${index}]`"
407+ :rule =" domain_rule"
408+ ignore-path-change
409+ :show-label =" false"
410+ :show-feedback =" false"
411+ style =" margin-bottom : 0 ; flex : 1 "
412+ >
413+ <n-input
414+ v-model:value =" rule.domains[index]"
415+ placeholder =" example.com"
416+ @keydown.enter.prevent
417+ />
418+ </n-form-item >
419+ </n-dynamic-input >
420+ </n-form-item >
421+
358422 <!-- ===== ACME mode ===== -->
359423 <template v-if =" is_acme && rule .cert_type && rule .cert_type .t === ' acme' " >
360424 <n-form-item :label =" t('cert.acme_account')" >
@@ -366,29 +430,6 @@ async function save() {
366430 />
367431 </n-form-item >
368432
369- <n-form-item :label =" t('cert.cert_domains')" path =" domains" >
370- <n-dynamic-input
371- v-model:value =" rule.domains"
372- placeholder =" example.com"
373- # =" { index }"
374- >
375- <n-form-item
376- :path =" `domains[${index}]`"
377- :rule =" domain_rule"
378- ignore-path-change
379- :show-label =" false"
380- :show-feedback =" false"
381- style =" margin-bottom : 0 ; flex : 1 "
382- >
383- <n-input
384- v-model:value =" rule.domains[index]"
385- placeholder =" example.com"
386- @keydown.enter.prevent
387- />
388- </n-form-item >
389- </n-dynamic-input >
390- </n-form-item >
391-
392433 <n-form-item :label =" t('cert.acme_key_type')" >
393434 <n-select
394435 :value =" rule.cert_type.key_type"
@@ -551,8 +592,26 @@ async function save() {
551592 </n-form-item >
552593 </template >
553594
595+ <!-- ===== Generated mode ===== -->
596+ <template
597+ v-if ="
598+ is_generated && rule .cert_type && rule .cert_type .t === ' generated'
599+ "
600+ >
601+ <n-form-item
602+ :label =" t('cert.generated_validity_days')"
603+ path =" cert_type.validity_days"
604+ >
605+ <n-input-number
606+ v-model:value =" rule.cert_type.validity_days"
607+ :min =" 1"
608+ :max =" 36500"
609+ />
610+ </n-form-item >
611+ </template >
612+
554613 <!-- ===== Manual mode ===== -->
555- <template v-if =" ! is_acme " >
614+ <template v-if =" ! is_acme && ! is_generated " >
556615 <n-form-item :label =" t('cert.upload_cert')" >
557616 <n-input
558617 v-model:value =" rule.certificate"
0 commit comments