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 shortcuts;
mod sidebar_tabs;
mod site_permissions_settings;
mod site_settings;
mod spaces;
mod sync;
@@ -60,6 +61,9 @@ impl ElyShell {
"ely://settings/privacy-security" => self.render_privacy_security_page(snapshot, cx),
"ely://settings/downloads" => self.render_download_settings_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/plugins" => self.render_plugins_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.",
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 {
icon: IconName::CircleUser,
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,
}
}