Add site permissions settings page

This commit is contained in:
2026-05-08 03:51:01 -04:00
parent ada0a82da8
commit 4988fd7c70
5 changed files with 275 additions and 0 deletions
@@ -15,6 +15,7 @@ mod search;
mod settings; mod settings;
mod shortcuts; mod shortcuts;
mod sidebar_tabs; mod sidebar_tabs;
mod site_permissions_settings;
mod site_settings; mod site_settings;
mod spaces; mod spaces;
mod sync; mod sync;
@@ -60,6 +61,9 @@ impl ElyShell {
"ely://settings/privacy-security" => self.render_privacy_security_page(snapshot, cx), "ely://settings/privacy-security" => self.render_privacy_security_page(snapshot, cx),
"ely://settings/downloads" => self.render_download_settings_page(snapshot, cx), "ely://settings/downloads" => self.render_download_settings_page(snapshot, cx),
"ely://settings/spaces" => self.render_spaces_page(snapshot, cx), "ely://settings/spaces" => self.render_spaces_page(snapshot, cx),
"ely://settings/site-permissions" => {
self.render_site_permissions_settings_page(snapshot, cx)
}
"ely://settings/shortcuts" => self.render_shortcuts_page(snapshot), "ely://settings/shortcuts" => self.render_shortcuts_page(snapshot),
"ely://settings/plugins" => self.render_plugins_page(snapshot, cx), "ely://settings/plugins" => self.render_plugins_page(snapshot, cx),
"ely://settings/profiles" => self.render_profiles_page(snapshot, cx), "ely://settings/profiles" => self.render_profiles_page(snapshot, cx),
@@ -54,6 +54,12 @@ const SETTINGS_ROUTES: &[SettingsRoute] = &[
detail: "Profile download location and save behavior.", detail: "Profile download location and save behavior.",
route: "ely://settings/downloads", route: "ely://settings/downloads",
}, },
SettingsRoute {
icon: IconName::Globe,
title: "Site Permissions",
detail: "Profile-scoped site permissions and local audit state.",
route: "ely://settings/site-permissions",
},
SettingsRoute { SettingsRoute {
icon: IconName::CircleUser, icon: IconName::CircleUser,
title: "Profiles", title: "Profiles",
@@ -0,0 +1,238 @@
use ely_browser_core::BrowserSnapshot;
use ely_design_system::colors;
use ely_domain::{SiteOrigin, SitePermissionDecision, SitePermissionEntry};
use gpui::{AnyElement, Context, IntoElement, ParentElement, Styled, div, px, rgb};
use gpui_component::{
IconName, Sizable, StyledExt,
button::{Button, ButtonVariants},
scroll::ScrollableElement,
};
use super::{ElyShell, render_canvas_surface};
impl ElyShell {
pub(super) fn render_site_permissions_settings_page(
&mut self,
snapshot: &BrowserSnapshot,
cx: &mut Context<Self>,
) -> AnyElement {
render_canvas_surface(
div()
.size_full()
.p_8()
.flex()
.flex_col()
.gap_5()
.child(render_site_permissions_header(snapshot))
.child(render_site_permissions_summary(snapshot))
.child(render_site_permissions_list(snapshot, cx)),
)
}
}
fn render_site_permissions_header(snapshot: &BrowserSnapshot) -> AnyElement {
div()
.flex()
.items_end()
.justify_between()
.gap_4()
.child(
div()
.min_w_0()
.flex()
.flex_col()
.gap_2()
.child(
div()
.text_size(px(26.0))
.text_color(rgb(colors::INK))
.child("Site Permissions"),
)
.child(
div()
.text_sm()
.truncate()
.text_color(rgb(colors::MUTED))
.child(format!("Profile: {}", snapshot.active_profile_name)),
),
)
.child(
div()
.flex()
.items_center()
.gap_2()
.text_xs()
.font_semibold()
.text_color(rgb(colors::MUTED))
.child(IconName::Globe)
.child("Profile scoped"),
)
.into_any_element()
}
fn render_site_permissions_summary(snapshot: &BrowserSnapshot) -> AnyElement {
div()
.border_t_1()
.border_b_1()
.border_color(rgb(colors::HAIRLINE))
.py_3()
.flex()
.items_center()
.justify_between()
.gap_4()
.children([
site_permission_metric("Configured", snapshot.site_permissions.len()),
site_permission_metric("Allowed", allowed_count(snapshot)),
site_permission_metric("Denied", denied_count(snapshot)),
site_permission_metric("Audit Events", snapshot.site_permission_audit_events.len()),
])
.into_any_element()
}
fn site_permission_metric(label: &'static str, value: usize) -> AnyElement {
div()
.min_w_0()
.flex()
.flex_col()
.gap_1()
.child(div().text_xs().text_color(rgb(colors::MUTED)).child(label))
.child(
div().text_sm().font_semibold().text_color(rgb(colors::INK)).child(value.to_string()),
)
.into_any_element()
}
fn render_site_permissions_list(
snapshot: &BrowserSnapshot,
cx: &mut Context<ElyShell>,
) -> AnyElement {
if snapshot.site_permissions.is_empty() {
return div()
.flex_1()
.border_t_1()
.border_color(rgb(colors::HAIRLINE))
.pt_5()
.text_sm()
.text_color(rgb(colors::MUTED))
.child("No site permissions are configured for this Profile.")
.into_any_element();
}
div()
.flex_1()
.min_h_0()
.flex()
.flex_col()
.overflow_y_scrollbar()
.border_t_1()
.border_color(rgb(colors::HAIRLINE))
.children(
snapshot
.site_permissions
.iter()
.enumerate()
.map(|(index, entry)| render_site_permission_entry(index, entry, cx)),
)
.into_any_element()
}
fn render_site_permission_entry(
index: usize,
entry: &SitePermissionEntry,
cx: &mut Context<ElyShell>,
) -> AnyElement {
let origin = entry.origin().clone();
let route = site_settings_route(&origin);
let decision = entry.decision();
div()
.py_3()
.border_b_1()
.border_color(rgb(colors::HAIRLINE))
.flex()
.items_center()
.justify_between()
.gap_4()
.child(
div()
.min_w_0()
.flex()
.items_center()
.gap_3()
.child(
div()
.text_color(rgb(permission_decision_color(decision)))
.child(permission_decision_icon(decision)),
)
.child(
div()
.min_w_0()
.flex()
.flex_col()
.gap_1()
.child(
div()
.text_sm()
.font_semibold()
.truncate()
.text_color(rgb(colors::INK))
.child(entry.origin().as_str().to_string()),
)
.child(div().text_xs().truncate().text_color(rgb(colors::MUTED)).child(
format!("{} - {}", entry.feature().label(), entry.decision().label()),
)),
),
)
.child(
Button::new(("open-site-permission-settings", index))
.ghost()
.xsmall()
.label("Open")
.tooltip("Open Site Settings")
.on_click(cx.listener(move |shell, _, window, cx| {
shell.open_internal_tab(&route, window, cx);
})),
)
.into_any_element()
}
fn allowed_count(snapshot: &BrowserSnapshot) -> usize {
snapshot
.site_permissions
.iter()
.filter(|entry| {
matches!(
entry.decision(),
SitePermissionDecision::AllowOnce | SitePermissionDecision::AllowAlways
)
})
.count()
}
fn denied_count(snapshot: &BrowserSnapshot) -> usize {
snapshot
.site_permissions
.iter()
.filter(|entry| entry.decision() == SitePermissionDecision::DenyAlways)
.count()
}
fn site_settings_route(origin: &SiteOrigin) -> String {
format!("ely://site/{}", origin.as_str())
}
fn permission_decision_color(decision: SitePermissionDecision) -> u32 {
match decision {
SitePermissionDecision::AllowOnce | SitePermissionDecision::AllowAlways => colors::SUCCESS,
SitePermissionDecision::DenyAlways => colors::ERROR,
}
}
fn permission_decision_icon(decision: SitePermissionDecision) -> IconName {
match decision {
SitePermissionDecision::AllowOnce | SitePermissionDecision::AllowAlways => {
IconName::CircleCheck
}
SitePermissionDecision::DenyAlways => IconName::CircleX,
}
}
@@ -31,6 +31,7 @@ fn internal_page_title(url: &str) -> Option<&'static str> {
"ely://settings/privacy-security" => Some("Privacy & Security Settings"), "ely://settings/privacy-security" => Some("Privacy & Security Settings"),
"ely://settings/downloads" => Some("Downloads Settings"), "ely://settings/downloads" => Some("Downloads Settings"),
"ely://settings/spaces" => Some("Space Settings"), "ely://settings/spaces" => Some("Space Settings"),
"ely://settings/site-permissions" => Some("Site Permissions Settings"),
"ely://settings/shortcuts" => Some("Shortcut Settings"), "ely://settings/shortcuts" => Some("Shortcut Settings"),
"ely://settings/plugins" => Some("Plugin Settings"), "ely://settings/plugins" => Some("Plugin Settings"),
"ely://settings/profiles" => Some("Profile Settings"), "ely://settings/profiles" => Some("Profile Settings"),
@@ -172,6 +173,11 @@ fn settings_page_route(query: &str) -> Option<&'static str> {
Some("ely://settings/downloads") Some("ely://settings/downloads")
} }
"space" | "spaces" | "space settings" | "spaces settings" => Some("ely://settings/spaces"), "space" | "spaces" | "space settings" | "spaces settings" => Some("ely://settings/spaces"),
"site permission"
| "site permissions"
| "site permissions settings"
| "permissions"
| "permission settings" => Some("ely://settings/site-permissions"),
"shortcut" | "shortcuts" | "keyboard" | "keyboard shortcuts" => { "shortcut" | "shortcuts" | "keyboard" | "keyboard shortcuts" => {
Some("ely://settings/shortcuts") Some("ely://settings/shortcuts")
} }
@@ -149,3 +149,24 @@ fn settings_scoped_search_opens_downloads_page() -> Result<(), Box<dyn Error>> {
assert_eq!(core.snapshot()?.command_query, ""); assert_eq!(core.snapshot()?.command_query, "");
Ok(()) Ok(())
} }
#[test]
fn settings_scoped_search_opens_site_permissions_page() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
core.set_command_query("@settings site permissions");
let intent = core.submit_command()?;
let active_tab = core.active_tab()?;
assert_eq!(
intent,
Some(CommandIntent::ScopedSearch {
scope: CommandScope::Settings,
query: "site permissions".to_string(),
})
);
assert_eq!(active_tab.title(), "Site Permissions Settings");
assert_eq!(active_tab.url().as_str(), "ely://settings/site-permissions");
assert_eq!(core.snapshot()?.command_query, "");
Ok(())
}