Add site permissions settings page
This commit is contained in:
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user