Skip to content

Conversation

@ayushjain17
Copy link
Collaborator

@ayushjain17 ayushjain17 commented Feb 1, 2026

Change Log

  1. Minor refactor and improvements
  2. Improve UX and fix secrets related UI issues Frontend improvements for secrets #852

Summary by CodeRabbit

Release Notes

  • New Features

    • Added organization-level master encryption key generation and rotation
    • Improved webhook variable references to include secrets
  • Improvements

    • Enhanced key rotation workflows with clearer confirmation steps and instructions
    • Refined secret list filtering and date/time column formatting
    • Improved modal interactions and button styling
    • Added clipboard support for key management features

✏️ Tip: You can customize this high-level summary in your review settings.

@ayushjain17 ayushjain17 requested a review from a team as a code owner February 1, 2026 09:56
@semanticdiff-com
Copy link

semanticdiff-com bot commented Feb 1, 2026

Review changes with  SemanticDiff

Changed Files
File Status
  crates/frontend/src/pages/workspace.rs  58% smaller
  crates/frontend/src/components/key_rotation_modal.rs  42% smaller
  crates/frontend/src/pages/organisations.rs  41% smaller
  crates/frontend/src/components/modal.rs  37% smaller
  crates/frontend/src/components/webhook_form.rs  28% smaller
  crates/frontend/src/pages/webhooks.rs  23% smaller
  crates/frontend/src/pages/secrets_list.rs  4% smaller
  crates/frontend/Cargo.toml Unsupported file format
  crates/frontend/src/components/input.rs  0% smaller

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

Walkthrough

Frontend updates include adding Clipboard web-sys feature support, refactoring key rotation modals (renaming KeyRotationModal to WorkspaceKeyRotationModal, adding MasterKeyRotationModal), updating PortalModal component signature with optional heading, enhancing webhook form instructions, and refactoring state management across organisations, workspace, secrets list, and webhooks pages.

Changes

Cohort / File(s) Summary
Dependencies
crates/frontend/Cargo.toml
Added "Clipboard" feature to web-sys dependency to enable clipboard API support.
Component Styling
crates/frontend/src/components/input.rs
Enhanced Toggle component styling by adding disabled:!bg-[currentColor] class when input is disabled.
Modal Components
crates/frontend/src/components/modal.rs, crates/frontend/src/components/webhook_form.rs
PortalModal signature updated: heading changed from required String to optional String, handle_close callback signature simplified from Callback<(), ()> to Callback<()>. Webhook form Tip component expanded instructions to show both variable and secret reference examples.
Key Rotation Refactoring
crates/frontend/src/components/key_rotation_modal.rs
Renamed KeyRotationModal to WorkspaceKeyRotationModal with OrganisationId now derived from context instead of passed as parameter. Added new MasterKeyRotationModal component for organization-wide key rotation. Refactored UI to use inline confirmation flow instead of separate modal steps. Added key_rotation_instructions() helper function and updated API calls and messaging for workspace vs. master key scope.
Pages - Master Key Management
crates/frontend/src/pages/organisations.rs
Replaced hard-coded master key generation with reactive state-driven approach using key_generation_rws and show_master_rotation_modal flags. Added Label, MasterKeyRotationModal components and key_generation_instructions. Implemented try_generate_master_key handler with copy-to-clipboard functionality and conditional rendering for generated key display.
Pages - Workspace Updates
crates/frontend/src/pages/workspace.rs
Replaced dual-signal state (show_key_rotation_modal, key_rotation_workspace) with single RW signal (key_rotation_workspace_rws) storing optional workspace name. Updated import from KeyRotationModal to WorkspaceKeyRotationModal. Simplified modal triggering and conditional rendering logic.
Pages - Secrets and Webhooks
crates/frontend/src/pages/secrets_list.rs, crates/frontend/src/pages/webhooks.rs
Secrets list: integrated SecretFilterWidget and FilterSummary, added DatetimeStr rendering for created/modified date columns. Webhooks: replaced text-based "enabled" column with Toggle component rendering using Column::default_with_cell_formatter.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • knutties

Poem

🐰 With whiskers twitching bright and keen,
We've shuffled modals on the screen!
Key rotation hops with purpose true,
Workspace and master—modal's new.
From clipboard clicks to secrets shared,
Your frontend's organized and prepared! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions 'Minor enhancements and secrets related fixes' but the PR includes substantial refactoring: key rotation modals (workspace and master), clipboard feature, modal API changes, and organizational page restructuring. Update the title to reflect the major changes, such as 'refactor(UI): Add master key rotation modal and restructure key rotation flow' or provide more comprehensive scope coverage.
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ui4

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ayushjain17 ayushjain17 added bug Something isn't working enhancement New feature or request UI things relating to the UI documentation Improvements or additions to documentation labels Feb 1, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/frontend/src/pages/organisations.rs`:
- Around line 180-276: The clipboard write in the copy_to_clipboard closure
currently ignores that write_text returns a JS Promise; replace the sync call
with proper promise handling by calling
window.navigator().clipboard().write_text(&key), wrapping that Promise with
wasm_bindgen_futures::JsFuture::from(...) and driving it with
wasm_bindgen_futures::spawn_local so you can await it and handle errors; on
success call enqueue_alert("Key copied to clipboard", AlertType::Success, 3000)
and on failure enqueue an error alert (and optionally log the JS error). Update
the copy_to_clipboard closure in the popup_state handling (the closure that
references key_to_copy) to use spawn_local+JsFuture and import
wasm_bindgen_futures, leaving generate_master_key and key_generation_rws usage
unchanged.
🧹 Nitpick comments (2)
crates/frontend/src/components/webhook_form.rs (1)

266-266: Pre-existing typo: "Paylaod" should be "Payload".

While not part of this PR's changes, there's a typo in the label title. Consider fixing it while you're in this file.

📝 Suggested fix
-                <Label title="Paylaod Version" />
+                <Label title="Payload Version" />
crates/frontend/src/pages/secrets_list.rs (1)

64-78: Consider extracting sort callback logic into a helper.

The sort callback logic at lines 64-78, 95-109, and 126-140 is nearly identical, differing only in the SortOn variant. A helper function could reduce duplication:

fn create_sort_callback(
    sort_on: SortOn,
    filters_rws: RwSignal<SecretFilters>,
    pagination_params_rws: RwSignal<PaginationParams>,
) -> Callback<()> {
    Callback::new(move |_| {
        let filters = filters_rws.get();
        let current_sort_on = filters.sort_on.unwrap_or_default();
        let sort_by = if current_sort_on == sort_on {
            filters.sort_by.unwrap_or_default().flip()
        } else {
            SortBy::Desc
        };
        let new_filters = SecretFilters {
            sort_on: Some(sort_on),
            sort_by: Some(sort_by),
            ..filters
        };
        pagination_params_rws.update(|f| f.reset_page());
        filters_rws.set(new_filters);
    })
}

This is optional and can be deferred if you prefer keeping the current explicit approach.

Comment on lines +180 to +276
{move || match key_generation_rws.get() {
Some(popup_state) => {
view! {
<PortalModal
heading="Generate Master Encryption Key"
handle_close=move |_| key_generation_rws.set(None)
>
<div class="alert alert-warning">
<i class="ri-alert-line" />
<span>
"This will generate a new master encryption key. Save it securely - it will not be shown again."
</span>
</div>
{match popup_state.clone() {
Some(generated_key) => {
let key_to_copy = generated_key.clone();
let copy_to_clipboard = move |_| {
let key = key_to_copy.clone();
if let Some(window) = web_sys::window() {
let _ = window.navigator().clipboard().write_text(&key);
enqueue_alert(
"Key copied to clipboard".to_string(),
AlertType::Success,
3000,
);
}
};

{move || {
if let Some(generated_key) = generated_key.get() {
view! {
<>
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm mt-2 break-all select-all">
{generated_key}
view! {
<div class="flex flex-col gap-2">
<div class="control-form">
<Label title="Generated Key" />
<div class="p-4 flex justify-between items-center bg-gray-900 text-green-400 rounded-lg">
<div class="font-mono text-[14px] break-all select-all">
{generated_key}
</div>
<button
type="button"
class="px-1 bg-gray-800 hover:bg-gray-700 text-white rounded transition-colors"
on:click=copy_to_clipboard
title="Copy to clipboard"
>
<i class="ri-file-copy-line" />
</button>
</div>
<pre class="text-sm mt-2 whitespace-pre-wrap bg-base-200 p-3 rounded">
{"1. Copy this key immediately - it will NOT be shown again.\n2. Store it securely in your secrets manager.\n3. Set it as environment variable: MASTER_ENCRYPTION_KEY=<key>\n4. Restart the service for the key to take effect."}
</pre>
<Button
text="Close"
on_click=move |_| {
set_show_master_key_generate_modal.set(false);
set_generated_key.set(None);
}
icon_class="ri-close-line"
class="mt-4 w-full"
/>
</>
}
} else {
view! {
<>
<button
class="btn btn-primary w-full"
disabled=is_generating.get()
on:click=move |_| generate_master_key_action.dispatch(())
>
{if is_generating.get() {
"Generating..."
} else {
"Generate Key"
}}
<i class="ri-key-2-line"></i>
</button>
</>
}
</div>
<div class="control-form">
<Label title="Instructions" />
<ol class="p-4 flex flex-col gap-3 bg-base-200 rounded-lg list-none">
{key_generation_instructions()
.into_iter()
.enumerate()
.map(|(index, instruction)| {
view! {
<li class="flex gap-3 items-center">
<span class="flex-shrink-0 w-6 h-6 flex items-center justify-center bg-primary text-primary-content rounded-full text-xs font-semibold">
{(index + 1).to_string()}
</span>
<div class="text-[14px] flex-1">{instruction}</div>
</li>
}
})
.collect_view()}
</ol>
</div>
</div>
}
.into_view()
}
None => ().into_view(),
}}
{match popup_state.clone() {
Some(_) => {
view! {
<Button
text="Close"
on_click=move |_| key_generation_rws.set(None)
icon_class="ri-close-line"
/>
}
}}
</PortalModal>
}
})
}
None => {
view! {
<Button
text="Generate Key"
on_click=move |_| generate_master_key()
icon_class="ri-key-2-line"
/>
}
}
}}
</PortalModal>
}
}
None => ().into_view(),
}}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

web-sys Clipboard write_text return type promise

💡 Result:

In web-sys, web_sys::Clipboard::write_text returns a JavaScript promise:

pub fn write_text(&self, data: &str) -> js_sys::Promise

So the return type is js_sys::Promise (not a Rust Future). You can await it from Rust by converting it with wasm_bindgen_futures::JsFuture. [1]

In the underlying Web API, Clipboard.writeText() returns a Promise that resolves once the clipboard has been updated. [2]

Sources:
[1] https://wasm-bindgen.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
[2] https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText


🏁 Script executed:

# First, let's check if this codebase uses web-sys and what version
fd -e toml -e cargo | head -20
rg "web-sys" --type toml -A 2 -B 2

Repository: juspay/superposition

Length of output: 5888


🏁 Script executed:

# Search for how Clipboard is used elsewhere in the codebase
rg "clipboard" -i --type rust -B 3 -A 3

Repository: juspay/superposition

Length of output: 4862


Handle clipboard write failures with proper promise handling instead of ignoring failures.

The clipboard write_text() returns a js_sys::Promise, not a Rust Result, so the suggested match approach won't compile. The promise must be converted and awaited using wasm_bindgen_futures::JsFuture::from() and spawn_local() within the closure context:

🔧 Correct fix using promise handling
-                                        let _ = window.navigator().clipboard().write_text(&key);
-                                        enqueue_alert(
-                                            "Key copied to clipboard".to_string(),
-                                            AlertType::Success,
-                                            3000,
-                                        );
+                                        let promise = window.navigator().clipboard().write_text(&key);
+                                        wasm_bindgen_futures::spawn_local(async move {
+                                            match wasm_bindgen_futures::JsFuture::from(promise).await {
+                                                Ok(_) => enqueue_alert(
+                                                    "Key copied to clipboard".to_string(),
+                                                    AlertType::Success,
+                                                    3000,
+                                                ),
+                                                Err(_) => enqueue_alert(
+                                                    "Failed to copy key to clipboard".to_string(),
+                                                    AlertType::Error,
+                                                    3000,
+                                                ),
+                                            }
+                                        });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{move || match key_generation_rws.get() {
Some(popup_state) => {
view! {
<PortalModal
heading="Generate Master Encryption Key"
handle_close=move |_| key_generation_rws.set(None)
>
<div class="alert alert-warning">
<i class="ri-alert-line" />
<span>
"This will generate a new master encryption key. Save it securely - it will not be shown again."
</span>
</div>
{match popup_state.clone() {
Some(generated_key) => {
let key_to_copy = generated_key.clone();
let copy_to_clipboard = move |_| {
let key = key_to_copy.clone();
if let Some(window) = web_sys::window() {
let _ = window.navigator().clipboard().write_text(&key);
enqueue_alert(
"Key copied to clipboard".to_string(),
AlertType::Success,
3000,
);
}
};
{move || {
if let Some(generated_key) = generated_key.get() {
view! {
<>
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm mt-2 break-all select-all">
{generated_key}
view! {
<div class="flex flex-col gap-2">
<div class="control-form">
<Label title="Generated Key" />
<div class="p-4 flex justify-between items-center bg-gray-900 text-green-400 rounded-lg">
<div class="font-mono text-[14px] break-all select-all">
{generated_key}
</div>
<button
type="button"
class="px-1 bg-gray-800 hover:bg-gray-700 text-white rounded transition-colors"
on:click=copy_to_clipboard
title="Copy to clipboard"
>
<i class="ri-file-copy-line" />
</button>
</div>
<pre class="text-sm mt-2 whitespace-pre-wrap bg-base-200 p-3 rounded">
{"1. Copy this key immediately - it will NOT be shown again.\n2. Store it securely in your secrets manager.\n3. Set it as environment variable: MASTER_ENCRYPTION_KEY=<key>\n4. Restart the service for the key to take effect."}
</pre>
<Button
text="Close"
on_click=move |_| {
set_show_master_key_generate_modal.set(false);
set_generated_key.set(None);
}
icon_class="ri-close-line"
class="mt-4 w-full"
/>
</>
}
} else {
view! {
<>
<button
class="btn btn-primary w-full"
disabled=is_generating.get()
on:click=move |_| generate_master_key_action.dispatch(())
>
{if is_generating.get() {
"Generating..."
} else {
"Generate Key"
}}
<i class="ri-key-2-line"></i>
</button>
</>
}
</div>
<div class="control-form">
<Label title="Instructions" />
<ol class="p-4 flex flex-col gap-3 bg-base-200 rounded-lg list-none">
{key_generation_instructions()
.into_iter()
.enumerate()
.map(|(index, instruction)| {
view! {
<li class="flex gap-3 items-center">
<span class="flex-shrink-0 w-6 h-6 flex items-center justify-center bg-primary text-primary-content rounded-full text-xs font-semibold">
{(index + 1).to_string()}
</span>
<div class="text-[14px] flex-1">{instruction}</div>
</li>
}
})
.collect_view()}
</ol>
</div>
</div>
}
.into_view()
}
None => ().into_view(),
}}
{match popup_state.clone() {
Some(_) => {
view! {
<Button
text="Close"
on_click=move |_| key_generation_rws.set(None)
icon_class="ri-close-line"
/>
}
}}
</PortalModal>
}
})
}
None => {
view! {
<Button
text="Generate Key"
on_click=move |_| generate_master_key()
icon_class="ri-key-2-line"
/>
}
}
}}
</PortalModal>
}
}
None => ().into_view(),
}}
{move || match key_generation_rws.get() {
Some(popup_state) => {
view! {
<PortalModal
heading="Generate Master Encryption Key"
handle_close=move |_| key_generation_rws.set(None)
>
<div class="alert alert-warning">
<i class="ri-alert-line" />
<span>
"This will generate a new master encryption key. Save it securely - it will not be shown again."
</span>
</div>
{match popup_state.clone() {
Some(generated_key) => {
let key_to_copy = generated_key.clone();
let copy_to_clipboard = move |_| {
let key = key_to_copy.clone();
if let Some(window) = web_sys::window() {
let promise = window.navigator().clipboard().write_text(&key);
wasm_bindgen_futures::spawn_local(async move {
match wasm_bindgen_futures::JsFuture::from(promise).await {
Ok(_) => enqueue_alert(
"Key copied to clipboard".to_string(),
AlertType::Success,
3000,
),
Err(_) => enqueue_alert(
"Failed to copy key to clipboard".to_string(),
AlertType::Error,
3000,
),
}
});
}
};
view! {
<div class="flex flex-col gap-2">
<div class="control-form">
<Label title="Generated Key" />
<div class="p-4 flex justify-between items-center bg-gray-900 text-green-400 rounded-lg">
<div class="font-mono text-[14px] break-all select-all">
{generated_key}
</div>
<button
type="button"
class="px-1 bg-gray-800 hover:bg-gray-700 text-white rounded transition-colors"
on:click=copy_to_clipboard
title="Copy to clipboard"
>
<i class="ri-file-copy-line" />
</button>
</div>
</div>
<div class="control-form">
<Label title="Instructions" />
<ol class="p-4 flex flex-col gap-3 bg-base-200 rounded-lg list-none">
{key_generation_instructions()
.into_iter()
.enumerate()
.map(|(index, instruction)| {
view! {
<li class="flex gap-3 items-center">
<span class="flex-shrink-0 w-6 h-6 flex items-center justify-center bg-primary text-primary-content rounded-full text-xs font-semibold">
{(index + 1).to_string()}
</span>
<div class="text-[14px] flex-1">{instruction}</div>
</li>
}
})
.collect_view()}
</ol>
</div>
</div>
}
.into_view()
}
None => ().into_view(),
}}
{match popup_state.clone() {
Some(_) => {
view! {
<Button
text="Close"
on_click=move |_| key_generation_rws.set(None)
icon_class="ri-close-line"
/>
}
}
None => {
view! {
<Button
text="Generate Key"
on_click=move |_| generate_master_key()
icon_class="ri-key-2-line"
/>
}
}
}}
</PortalModal>
}
}
None => ().into_view(),
}}
🤖 Prompt for AI Agents
In `@crates/frontend/src/pages/organisations.rs` around lines 180 - 276, The
clipboard write in the copy_to_clipboard closure currently ignores that
write_text returns a JS Promise; replace the sync call with proper promise
handling by calling window.navigator().clipboard().write_text(&key), wrapping
that Promise with wasm_bindgen_futures::JsFuture::from(...) and driving it with
wasm_bindgen_futures::spawn_local so you can await it and handle errors; on
success call enqueue_alert("Key copied to clipboard", AlertType::Success, 3000)
and on failure enqueue an error alert (and optionally log the JS error). Update
the copy_to_clipboard closure in the popup_state handling (the closure that
references key_to_copy) to use spawn_local+JsFuture and import
wasm_bindgen_futures, leaving generate_master_key and key_generation_rws usage
unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request UI things relating to the UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants