Move certificate export and revoke actions into header
This commit is contained in:
@@ -44,8 +44,13 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
let active_tab = create_signal(initial_tab());
|
||||
let info = create_signal(None::<String>);
|
||||
let openssl_config = create_signal(String::new());
|
||||
let actions_open = create_signal(false);
|
||||
let export_open = create_signal(false);
|
||||
let revoke_open = create_signal(false);
|
||||
let can_revoke = create_signal(false);
|
||||
|
||||
let load = move || {
|
||||
can_revoke.set(false);
|
||||
spawn_local(async move {
|
||||
match api::certificate(id, cert_id_value).await {
|
||||
Ok(value) => {
|
||||
@@ -70,7 +75,17 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
purposes.set(value.purposes.clone());
|
||||
chain.set(value.chain.clone());
|
||||
openssl_config.set(value.openssl_config.clone());
|
||||
let issuer_id = if value.summary.revoked {
|
||||
None
|
||||
} else {
|
||||
value.summary.issuer_id.clone()
|
||||
};
|
||||
cert.set(Some(value));
|
||||
if let Some(issuer_id) = issuer_id {
|
||||
if let Ok(issuer) = api::certificate(id, &issuer_id).await {
|
||||
can_revoke.set(issuer.summary.has_private_key);
|
||||
}
|
||||
}
|
||||
if is_ca {
|
||||
if let Ok(page) = api::templates(id).await {
|
||||
templates.set(page.results);
|
||||
@@ -104,12 +119,13 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
spawn_local(async move {
|
||||
match api::export_certificate(id, cert_id_value, &payload).await {
|
||||
Ok(payload) => {
|
||||
if let Err(message) = dom::download_base64(
|
||||
match dom::download_base64(
|
||||
&payload.filename,
|
||||
&payload.content_type,
|
||||
&payload.content_base64,
|
||||
) {
|
||||
error.set(Some(message));
|
||||
Ok(()) => export_open.set(false),
|
||||
Err(message) => error.set(Some(message)),
|
||||
}
|
||||
}
|
||||
Err(message) if message == api::LOGIN_REQUIRED => {}
|
||||
@@ -119,11 +135,15 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
};
|
||||
|
||||
let revoke = move |_| {
|
||||
if !can_revoke.get() {
|
||||
return;
|
||||
}
|
||||
let payload = json!({"reason": rev_reason.get_clone()});
|
||||
spawn_local(async move {
|
||||
match api::revoke_certificate(id, cert_id_value, &payload).await {
|
||||
Ok(_) => {
|
||||
info.set(Some("Certificate revoked.".to_string()));
|
||||
revoke_open.set(false);
|
||||
load();
|
||||
}
|
||||
Err(message) if message == api::LOGIN_REQUIRED => {}
|
||||
@@ -132,7 +152,13 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
});
|
||||
};
|
||||
|
||||
let open_revoke = move |_| {
|
||||
actions_open.set(false);
|
||||
revoke_open.set(true);
|
||||
};
|
||||
|
||||
let remove = move |_| {
|
||||
actions_open.set(false);
|
||||
if !dom::confirm("Delete this certificate?") {
|
||||
return;
|
||||
}
|
||||
@@ -324,11 +350,26 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
format!("{} · serial {}", if is_ca { "CA certificate" } else { "end-entity certificate" }, header_serial),
|
||||
view! {
|
||||
a(class="btn btn-outline-light", href=format!("/vaults/{id}/certificates")) { "Back" }
|
||||
a(class="btn btn-primary", href=format!("/vaults/{id}/certificates/{cert_id_value}/renew")) { "Renew" }
|
||||
(if !summary.revoked && !is_ca {
|
||||
view! { button(class="btn btn-warning", on:click=revoke) { "Revoke" } }
|
||||
} else { view! {} })
|
||||
button(class="btn btn-outline-danger", on:click=remove) { "Delete" }
|
||||
button(class="btn btn-outline-light", r#type="button", on:click=move |_| {
|
||||
actions_open.set(false);
|
||||
export_open.set(true);
|
||||
}) { "Export" }
|
||||
div(class="dropdown") {
|
||||
button(class="btn btn-primary dropdown-toggle", r#type="button", aria-haspopup="true",
|
||||
aria-expanded=if actions_open.get() { "true" } else { "false" },
|
||||
on:click=move |_| actions_open.set(!actions_open.get())) { "Actions" }
|
||||
(if actions_open.get() {
|
||||
view! {
|
||||
div(class="dropdown-menu dropdown-menu-end show", role="menu") {
|
||||
a(class="dropdown-item", href=format!("/vaults/{id}/certificates/{cert_id_value}/renew"), role="menuitem",
|
||||
on:click=move |_| actions_open.set(false)) { "Renew" }
|
||||
button(class="dropdown-item text-warning", r#type="button", role="menuitem",
|
||||
disabled=!can_revoke.get(), on:click=open_revoke) { "Revoke" }
|
||||
button(class="dropdown-item text-danger", r#type="button", role="menuitem", on:click=remove) { "Delete" }
|
||||
}
|
||||
}
|
||||
} else { view! {} })
|
||||
}
|
||||
},
|
||||
))
|
||||
div(class="card mb-3") {
|
||||
@@ -349,9 +390,7 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
(tab("overview", "Overview"))
|
||||
(tab("extensions", "Extensions"))
|
||||
(tab("validation", "Validation"))
|
||||
(tab("export", "Export"))
|
||||
(tab("metadata", "Metadata"))
|
||||
(tab("revoke", "Revoke"))
|
||||
(tab("transform", "Transform"))
|
||||
(if is_ca { tab("ca", "CA") } else { view! {} })
|
||||
}
|
||||
@@ -411,42 +450,6 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
|
||||
}
|
||||
} else { view! {} })
|
||||
(if active_tab.get_clone() == "export" {
|
||||
view! {
|
||||
div(id="panel-export", role="tabpanel", aria-labelledby="tab-export", class="row g-3") {
|
||||
div(class="col-12 col-lg-6") {
|
||||
div(class="card mb-3") {
|
||||
div(class="card-header") { "Export" }
|
||||
div(class="card-body") {
|
||||
select(class="form-select mb-2", bind:value=export_format) {
|
||||
option(value="pem") { "PEM" }
|
||||
option(value="pem-chain") { "PEM chain" }
|
||||
option(value="pem-key") { "PEM + key" }
|
||||
option(value="pem-pkcs8") { "PEM + encrypted PKCS#8" }
|
||||
option(value="pkcs7") { "PKCS#7" }
|
||||
option(value="pkcs7-chain") { "PKCS#7 chain" }
|
||||
option(value="der") { "DER" }
|
||||
option(value="pkcs12") { "PKCS#12" }
|
||||
option(value="pkcs12-chain") { "PKCS#12 chain" }
|
||||
option(value="openvpn") { "OpenVPN" }
|
||||
option(value="openssl-config") { "OpenSSL config" }
|
||||
option(value="index") { "Certificate index" }
|
||||
option(value="vcalendar") { "vCalendar" }
|
||||
option(value="jwk") { "JWK" }
|
||||
option(value="jwk-chain") { "JWK chain" }
|
||||
option(value="tls-auth") { "OpenVPN tls-auth key" }
|
||||
}
|
||||
div(class="mb-2") {
|
||||
label(class="form-label") { "Password (encrypted formats)" }
|
||||
input(class="form-control", r#type="password", bind:value=export_password)
|
||||
}
|
||||
button(class="btn btn-primary", on:click=do_export) { "Download" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { view! {} })
|
||||
(if active_tab.get_clone() == "metadata" {
|
||||
view! {
|
||||
div(id="panel-metadata", role="tabpanel", aria-labelledby="tab-metadata", class="row g-3") {
|
||||
@@ -469,21 +472,6 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
}
|
||||
}
|
||||
} else { view! {} })
|
||||
(if active_tab.get_clone() == "revoke" {
|
||||
view! {
|
||||
div(id="panel-revoke", role="tabpanel", aria-labelledby="tab-revoke", class="row g-3") {
|
||||
div(class="col-12 col-lg-6") {
|
||||
div(class="card") {
|
||||
div(class="card-header") { "Revoke" }
|
||||
div(class="card-body") {
|
||||
select(class="form-select mb-2", bind:value=rev_reason) { (reason_options()) }
|
||||
button(class="btn btn-warning", disabled=summary.revoked, on:click=revoke) { "Revoke certificate" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { view! {} })
|
||||
(if active_tab.get_clone() == "transform" {
|
||||
view! {
|
||||
div(id="panel-transform", role="tabpanel", aria-labelledby="tab-transform", class="row g-3") {
|
||||
@@ -596,6 +584,71 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
} else {
|
||||
view! {}
|
||||
})
|
||||
(if export_open.get() {
|
||||
view! {
|
||||
div(class="modal fade show d-block", tabindex="-1", role="dialog", aria-modal="true", aria-labelledby="export-dialog-title") {
|
||||
div(class="modal-dialog modal-dialog-centered") {
|
||||
div(class="modal-content") {
|
||||
div(class="modal-header") {
|
||||
h2(class="modal-title h5", id="export-dialog-title") { "Export certificate" }
|
||||
button(class="btn-close btn-close-white", r#type="button", aria-label="Close", on:click=move |_| export_open.set(false))
|
||||
}
|
||||
div(class="modal-body") {
|
||||
label(class="form-label", r#for="export-format") { "Format" }
|
||||
select(id="export-format", class="form-select mb-3", bind:value=export_format) {
|
||||
option(value="pem") { "PEM" }
|
||||
option(value="pem-chain") { "PEM chain" }
|
||||
option(value="pem-key") { "PEM + key" }
|
||||
option(value="pem-pkcs8") { "PEM + encrypted PKCS#8" }
|
||||
option(value="pkcs7") { "PKCS#7" }
|
||||
option(value="pkcs7-chain") { "PKCS#7 chain" }
|
||||
option(value="der") { "DER" }
|
||||
option(value="pkcs12") { "PKCS#12" }
|
||||
option(value="pkcs12-chain") { "PKCS#12 chain" }
|
||||
option(value="openvpn") { "OpenVPN" }
|
||||
option(value="openssl-config") { "OpenSSL config" }
|
||||
option(value="index") { "Certificate index" }
|
||||
option(value="vcalendar") { "vCalendar" }
|
||||
option(value="jwk") { "JWK" }
|
||||
option(value="jwk-chain") { "JWK chain" }
|
||||
option(value="tls-auth") { "OpenVPN tls-auth key" }
|
||||
}
|
||||
label(class="form-label", r#for="export-password") { "Password (encrypted formats)" }
|
||||
input(id="export-password", class="form-control", r#type="password", bind:value=export_password)
|
||||
}
|
||||
div(class="modal-footer") {
|
||||
button(class="btn btn-outline-light", r#type="button", on:click=move |_| export_open.set(false)) { "Cancel" }
|
||||
button(class="btn btn-primary", r#type="button", on:click=do_export) { "Download" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div(class="modal-backdrop fade show")
|
||||
}
|
||||
} else { view! {} })
|
||||
(if revoke_open.get() {
|
||||
view! {
|
||||
div(class="modal fade show d-block", tabindex="-1", role="dialog", aria-modal="true", aria-labelledby="revoke-dialog-title") {
|
||||
div(class="modal-dialog modal-dialog-centered") {
|
||||
div(class="modal-content") {
|
||||
div(class="modal-header") {
|
||||
h2(class="modal-title h5", id="revoke-dialog-title") { "Revoke certificate" }
|
||||
button(class="btn-close btn-close-white", r#type="button", aria-label="Close", on:click=move |_| revoke_open.set(false))
|
||||
}
|
||||
div(class="modal-body") {
|
||||
label(class="form-label", r#for="revoke-reason") { "Reason" }
|
||||
select(id="revoke-reason", class="form-select", bind:value=rev_reason) { (reason_options()) }
|
||||
}
|
||||
div(class="modal-footer") {
|
||||
button(class="btn btn-outline-light", r#type="button", on:click=move |_| revoke_open.set(false)) { "Cancel" }
|
||||
button(class="btn btn-warning", r#type="button", disabled=!can_revoke.get(), on:click=revoke) { "Revoke certificate" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div(class="modal-backdrop fade show")
|
||||
}
|
||||
} else { view! {} })
|
||||
}
|
||||
};
|
||||
|
||||
@@ -620,8 +673,9 @@ fn initial_tab() -> String {
|
||||
.unwrap_or_default();
|
||||
let hash = hash.trim_start_matches('#');
|
||||
match hash {
|
||||
"overview" | "extensions" | "validation" | "export" | "metadata" | "revoke"
|
||||
| "transform" | "ca" => hash.to_string(),
|
||||
"overview" | "extensions" | "validation" | "metadata" | "transform" | "ca" => {
|
||||
hash.to_string()
|
||||
}
|
||||
_ => "overview".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user