Move certificate transforms into header dropdown
This commit is contained in:
@@ -97,8 +97,8 @@ explicit/mandatory DN entries, default key type/size/curve, default validity).
|
||||
extensions, and a server-side **Validate extensions** action. OpenSSL config
|
||||
text is available on export.
|
||||
- Certificate details are split into Overview, Extensions, Validation,
|
||||
Metadata, Transform and CA tabs, with hash deep links. Header actions provide
|
||||
export, renewal, revocation and deletion. They show
|
||||
Metadata and CA tabs, with hash deep links. Header actions provide export,
|
||||
renewal, revocation, deletion and certificate transformation. They show
|
||||
subject/issuer, serial, validity, signatures, MD5/SHA-1/SHA-256
|
||||
fingerprints, parsed extensions, OpenSSL validation errors, purposes and the
|
||||
issuer chain.
|
||||
|
||||
+28
-1
@@ -101,11 +101,13 @@ test("core PKI workflow", async ({ page }) => {
|
||||
await page.getByRole("button", { name: "Search" }).click();
|
||||
await expect(page.getByRole("link", { name: leafName, exact: true })).toBeVisible();
|
||||
|
||||
// Certificate header actions replace the Export and Revoke tabs.
|
||||
// Certificate header controls replace the Export, Revoke and Transform tabs.
|
||||
await page.getByRole("link", { name: leafName, exact: true }).click();
|
||||
const leafUrl = page.url();
|
||||
const certificateTabs = page.locator(".nav-tabs");
|
||||
await expect(certificateTabs.getByRole("tab", { name: "Export", exact: true })).toHaveCount(0);
|
||||
await expect(certificateTabs.getByRole("tab", { name: "Revoke", exact: true })).toHaveCount(0);
|
||||
await expect(certificateTabs.getByRole("tab", { name: "Transform", exact: true })).toHaveCount(0);
|
||||
|
||||
const downloadPromise = page.waitForEvent("download");
|
||||
await page.getByRole("button", { name: "Export", exact: true }).click();
|
||||
@@ -117,6 +119,31 @@ test("core PKI workflow", async ({ page }) => {
|
||||
expect(download.suggestedFilename()).toBe(`${leafName}.crt`);
|
||||
await expect(exportDialog).toHaveCount(0);
|
||||
|
||||
const transformButton = page.getByRole("button", { name: "Transform", exact: true });
|
||||
await transformButton.click();
|
||||
for (const name of ["Extract public key", "Create request", "Create template", "Similar certificate"]) {
|
||||
await expect(page.getByRole("menuitem", { name, exact: true })).toBeVisible();
|
||||
}
|
||||
await page.getByRole("menuitem", { name: "Extract public key", exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/keys\/[^/]+$/);
|
||||
|
||||
await page.goto(leafUrl);
|
||||
await transformButton.click();
|
||||
await page.getByRole("menuitem", { name: "Create request", exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/requests\/[^/]+$/);
|
||||
|
||||
await page.goto(leafUrl);
|
||||
await transformButton.click();
|
||||
await page.getByRole("menuitem", { name: "Create template", exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/templates\/[^/]+$/);
|
||||
|
||||
await page.goto(leafUrl);
|
||||
await transformButton.click();
|
||||
await page.getByRole("menuitem", { name: "Similar certificate", exact: true }).click();
|
||||
await expect(page).toHaveURL(`${leafUrl}/similar`);
|
||||
await expect(page.getByRole("heading", { name: "New certificate" })).toBeVisible();
|
||||
await page.goto(leafUrl);
|
||||
|
||||
await page.getByRole("button", { name: "Actions", exact: true }).click();
|
||||
await expect(page.getByRole("menuitem", { name: "Renew", exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("menuitem", { name: "Delete", exact: true })).toBeVisible();
|
||||
|
||||
@@ -45,6 +45,7 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
let info = create_signal(None::<String>);
|
||||
let openssl_config = create_signal(String::new());
|
||||
let actions_open = create_signal(false);
|
||||
let transform_open = create_signal(false);
|
||||
let export_open = create_signal(false);
|
||||
let revoke_open = create_signal(false);
|
||||
let can_revoke = create_signal(false);
|
||||
@@ -352,12 +353,16 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
a(class="btn btn-outline-light", href=format!("/vaults/{id}/certificates")) { "Back" }
|
||||
button(class="btn btn-outline-light", r#type="button", on:click=move |_| {
|
||||
actions_open.set(false);
|
||||
transform_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" }
|
||||
on:click=move |_| {
|
||||
transform_open.set(false);
|
||||
actions_open.set(!actions_open.get());
|
||||
}) { "Actions" }
|
||||
(if actions_open.get() {
|
||||
view! {
|
||||
div(class="dropdown-menu dropdown-menu-end show", role="menu") {
|
||||
@@ -370,6 +375,34 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
}
|
||||
} else { view! {} })
|
||||
}
|
||||
div(class="dropdown") {
|
||||
button(class="btn btn-outline-light dropdown-toggle", r#type="button", aria-haspopup="true",
|
||||
aria-expanded=if transform_open.get() { "true" } else { "false" },
|
||||
on:click=move |_| {
|
||||
actions_open.set(false);
|
||||
transform_open.set(!transform_open.get());
|
||||
}) { "Transform" }
|
||||
(if transform_open.get() {
|
||||
view! {
|
||||
div(class="dropdown-menu dropdown-menu-end show", role="menu") {
|
||||
button(class="dropdown-item", r#type="button", role="menuitem", on:click=move |_| {
|
||||
transform_open.set(false);
|
||||
transform("open-key");
|
||||
}) { "Extract public key" }
|
||||
button(class="dropdown-item", r#type="button", role="menuitem", on:click=move |_| {
|
||||
transform_open.set(false);
|
||||
transform("request");
|
||||
}) { "Create request" }
|
||||
button(class="dropdown-item", r#type="button", role="menuitem", on:click=move |_| {
|
||||
transform_open.set(false);
|
||||
transform("template");
|
||||
}) { "Create template" }
|
||||
a(class="dropdown-item", href=format!("/vaults/{id}/certificates/{cert_id_value}/similar"), role="menuitem",
|
||||
on:click=move |_| transform_open.set(false)) { "Similar certificate" }
|
||||
}
|
||||
}
|
||||
} else { view! {} })
|
||||
}
|
||||
},
|
||||
))
|
||||
div(class="card mb-3") {
|
||||
@@ -391,7 +424,6 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
(tab("extensions", "Extensions"))
|
||||
(tab("validation", "Validation"))
|
||||
(tab("metadata", "Metadata"))
|
||||
(tab("transform", "Transform"))
|
||||
(if is_ca { tab("ca", "CA") } else { view! {} })
|
||||
}
|
||||
(if active_tab.get_clone() == "overview" {
|
||||
@@ -472,23 +504,6 @@ pub fn CertificateDetailPage(vault_id: String, cert_id: String) -> View {
|
||||
}
|
||||
}
|
||||
} else { view! {} })
|
||||
(if active_tab.get_clone() == "transform" {
|
||||
view! {
|
||||
div(id="panel-transform", role="tabpanel", aria-labelledby="tab-transform", class="row g-3") {
|
||||
div(class="col-12 col-lg-6") {
|
||||
div(class="card") {
|
||||
div(class="card-header") { "Transform" }
|
||||
div(class="card-body d-grid gap-2") {
|
||||
button(class="btn btn-outline-light", on:click=move |_| transform("open-key")) { "Extract public key" }
|
||||
button(class="btn btn-outline-light", on:click=move |_| transform("request")) { "Create request" }
|
||||
button(class="btn btn-outline-light", on:click=move |_| transform("template")) { "Create template" }
|
||||
a(class="btn btn-outline-light", href=format!("/vaults/{id}/certificates/{cert_id_value}/similar")) { "Similar certificate" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { view! {} })
|
||||
(if is_ca && active_tab.get_clone() == "ca" {
|
||||
view! {
|
||||
div(id="panel-ca", role="tabpanel", aria-labelledby="tab-ca", class="card mt-3") {
|
||||
@@ -673,9 +688,7 @@ fn initial_tab() -> String {
|
||||
.unwrap_or_default();
|
||||
let hash = hash.trim_start_matches('#');
|
||||
match hash {
|
||||
"overview" | "extensions" | "validation" | "metadata" | "transform" | "ca" => {
|
||||
hash.to_string()
|
||||
}
|
||||
"overview" | "extensions" | "validation" | "metadata" | "ca" => hash.to_string(),
|
||||
_ => "overview".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user