#[cfg(target_os = "macos")]
mod gallery {
use fluidattacks_designs::file_picker::{self, Picked};
use fluidattacks_designs::tokens::container::Scroll;
use fluidattacks_designs::tokens::file_picker::{Request, Selection};
use fluidattacks_designs::tokens::list::SelectionMode;
use fluidattacks_designs::tokens::modal::{Outcome, Size as ModalSize};
use fluidattacks_designs::tokens::table::{
compare, searched, ColumnKind, Page, PageCounts, Sort,
};
use fluidattacks_designs::tokens::{
alert, button, color, loading, tabs, tag, theme, typography,
};
use fluidattacks_designs::{
ActiveTheme as _, Alert, AlertDialog, Assets, Avatar, Button, Checkbox, CloudImage, Column,
ConfirmDialog, Container, EmptyState, Heading, Icon, ImageView, ImageViewer, List,
ListItem, ListState, Loading, Modal, NotificationSign, Scrollbar, Select, SelectOption,
SelectState, Span, TabItem, Table, TableState, Tabs, Tag, Text, TextArea, TextAreaState,
TextInput, TextInputState, Theme, Tooltip,
};
use gpui::{
div, px, rgb, size, App, AppContext as _, Bounds, Context, ImageFormat,
InteractiveElement as _, IntoElement, ParentElement as _, Render, ScrollHandle,
SharedString, StatefulInteractiveElement as _, Styled as _, Window, WindowBounds,
WindowOptions,
};
use gpui_platform::application;
struct Gallery {
tab: usize,
boxed_tab: usize,
scroll: ScrollHandle,
strip_scroll: ScrollHandle,
token_input: gpui::Entity<TextInputState>,
secret_input: gpui::Entity<TextInputState>,
error_input: gpui::Entity<TextInputState>,
success_input: gpui::Entity<TextInputState>,
disabled_input: gpui::Entity<TextInputState>,
areas: TextAreas,
table: gpui::Entity<TableState>,
narrowing: Narrowing,
lists: Lists,
listed: SharedString,
group_select: gpui::Entity<SelectState>,
team_select: gpui::Entity<SelectState>,
disabled_select: gpui::Entity<SelectState>,
chosen: SharedString,
picked: SharedString,
only_tests: bool,
negative: bool,
dialog: Option<Dialog>,
said: Option<SharedString>,
form_path: gpui::Entity<TextInputState>,
form_line: gpui::Entity<TextInputState>,
form_severity: gpui::Entity<SelectState>,
form_tested: bool,
}
const EVIDENCE: &[u8] = include_bytes!("evidence.png");
const EVIDENCE_NAME: &str = "evidence.png";
fn evidence_path() -> std::path::PathBuf {
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/evidence.png")
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Dialog {
Form,
Confirm,
Blocking,
Alert(alert::Variant),
Picture,
}
const ALERT_VARIANTS: [alert::Variant; 4] = [
alert::Variant::Error,
alert::Variant::Info,
alert::Variant::Success,
alert::Variant::Warning,
];
const fn alert_copy(variant: alert::Variant) -> (&'static str, &'static str) {
match variant {
alert::Variant::Error => ("Session", "The API refused the token, so nothing was sent."),
alert::Variant::Info => ("Lines", "Nothing has changed since the last scan."),
alert::Variant::Success => ("Reattacks", "Four verifications were submitted."),
alert::Variant::Warning => ("Roots", "Three roots answered slowly and were skipped."),
}
}
fn raising(dialog: Dialog, cx: &Context<Gallery>) -> impl Fn(&mut Window, &mut App) {
let gallery = cx.entity();
move |_window, app| {
gallery.update(app, |this, inner| {
this.dialog = Some(dialog);
inner.notify();
});
}
}
fn reporting(said: &'static str, cx: &Context<Gallery>) -> impl Fn(&mut Window, &mut App) {
let gallery = cx.entity();
move |_window, app| {
gallery.update(app, |this, inner| {
this.said = Some(SharedString::from(said));
this.dialog = None;
inner.notify();
});
}
}
fn alert_banner(variant: alert::Variant) -> Alert {
let (title, message) = alert_copy(variant);
Alert::new(format!("alert-{}", title.to_lowercase()), message)
.variant(variant)
.title(title)
}
fn alerts_row(cx: &Context<Gallery>) -> gpui::Div {
div()
.flex()
.flex_col()
.gap(px(12.0))
.w(px(560.0))
.children(ALERT_VARIANTS.map(alert_banner))
.child(
Alert::new(
"alert-plain",
"With no title the message carries the emphasis itself.",
)
.variant(alert::Variant::Info)
.closable(true)
.on_close(reporting("Alert dismissed", cx)),
)
.child(
Alert::new(
"alert-fading",
"This one reports and retires itself after four seconds.",
)
.variant(alert::Variant::Success)
.icon_position(alert::IconPosition::Top)
.auto_hide(true)
.time(alert::Time::Short)
.on_timeout(reporting("Alert timed out", cx)),
)
.child(
div().w(px(360.0)).child(
Alert::new(
"alert-long",
"The repositories folder is no longer where it was \
configured, so nothing was cloned.",
)
.variant(alert::Variant::Error)
.title("Roots")
.closable(true)
.on_close(reporting("Alert dismissed", cx)),
),
)
}
fn alert_dialog_button(variant: alert::Variant, cx: &Context<Gallery>) -> Button {
let (label, _) = alert_copy(variant);
Button::new(label, label)
.variant(button::Variant::Tertiary)
.on_click(cx.listener(move |this, _, _window, inner| {
this.raise(Dialog::Alert(variant), inner);
}))
}
fn alert_dialogs_row(cx: &Context<Gallery>) -> gpui::Div {
div()
.flex()
.items_center()
.gap(px(16.0))
.children(ALERT_VARIANTS.map(|variant| alert_dialog_button(variant, cx)))
}
const SEED: [[&str; 7]; 8] = [
[
"src/api/auth.py",
"9",
"critical",
"open",
"SQL injection",
"89",
"sast",
],
[
"src/api/users.py",
"104",
"medium",
"closed",
"Excessive privileges",
"269",
"sca",
],
[
"src/db/schema.sql",
"12",
"low",
"open",
"Weak credentials",
"521",
"sast",
],
[
"src/web/index.tsx",
"87",
"critical",
"submitted",
"XSS",
"79",
"dast",
],
[
"src/web/router.tsx",
"3",
"medium",
"open",
"Open redirect",
"601",
"dast",
],
[
"src/utils/hash.rs",
"1024",
"low",
"closed",
"Insecure hashing",
"328",
"sast",
],
[
"src/utils/parse.rs",
"56",
"critical",
"open",
"Buffer overflow",
"120",
"sast",
],
[
"src/main.rs",
"7",
"medium",
"submitted",
"Missing auth",
"306",
"sca",
],
];
fn sample_rows() -> Vec<Vec<SharedString>> {
(0..40)
.flat_map(|round: usize| SEED.iter().map(move |cells| row_at(cells, round)))
.collect()
}
fn row_at(cells: &[&str; 7], round: usize) -> Vec<SharedString> {
cells
.iter()
.enumerate()
.map(|(column, cell)| {
if column == 0 {
SharedString::from(format!("{cell}:{round}"))
} else {
SharedString::from((*cell).to_owned())
}
})
.collect()
}
const TABLE_PAGE_SIZE: usize = 25;
#[derive(Clone, Default)]
struct Query {
page: Page,
text: String,
narrowing: Narrowing,
sorting: Option<(String, Sort)>,
}
#[derive(Clone, Copy, Default, PartialEq, Eq)]
enum Narrowing {
#[default]
All,
Criticals,
Nothing,
}
impl Narrowing {
fn keeps(self, severity: &str) -> bool {
match self {
Self::All => true,
Self::Criticals => severity == "critical",
Self::Nothing => false,
}
}
}
fn column_of(id: &str) -> Option<(usize, ColumnKind)> {
match id {
"path" => Some((0, ColumnKind::Text)),
"line" => Some((1, ColumnKind::Number)),
"severity" => Some((2, ColumnKind::Text)),
"status" => Some((3, ColumnKind::Text)),
"weakness" => Some((4, ColumnKind::Text)),
"cwe" => Some((5, ColumnKind::Number)),
"source" => Some((6, ColumnKind::Text)),
_ => None,
}
}
fn sort_rows(rows: &mut [Vec<SharedString>], sorting: Option<&(String, Sort)>) {
let Some((id, sort)) = sorting else { return };
let Some((index, kind)) = column_of(id) else {
return;
};
rows.sort_by(|left, right| {
let cell =
|row: &Vec<SharedString>| row.get(index).map_or(String::new(), ToString::to_string);
sort.apply(compare(kind, &cell(left), &cell(right)))
});
}
fn fetch_page(query: &Query) -> (Vec<Vec<SharedString>>, usize) {
let mut matching: Vec<Vec<SharedString>> = sample_rows()
.into_iter()
.filter(|row| row.get(2).is_some_and(|s| query.narrowing.keeps(s)))
.filter(|row| searched(&query.text, row.iter().map(SharedString::as_ref)))
.collect();
sort_rows(&mut matching, query.sorting.as_ref());
let total = matching.len();
let rows = matching
.into_iter()
.skip(query.page.consumed())
.take(query.page.size)
.collect();
(rows, total)
}
fn page_query(state: &TableState, page: Page, narrowing: Narrowing) -> Query {
Query {
page,
text: state.query().to_owned(),
narrowing,
sorting: state.sorting().map(|(id, sort)| (id.to_owned(), sort)),
}
}
fn narrow(state: &mut TableState, narrowing: Narrowing, cx: &mut Context<TableState>) {
state.on_search(move |table, inner| {
let query = page_query(table, Page::resized(TABLE_PAGE_SIZE), narrowing);
serve_page(table, &query, inner);
});
state.on_sort(move |table, inner| {
let query = page_query(table, Page::resized(TABLE_PAGE_SIZE), narrowing);
serve_page(table, &query, inner);
});
let query = page_query(state, Page::resized(TABLE_PAGE_SIZE), narrowing);
serve_page(state, &query, cx);
}
fn jump_to_end(
state: &mut TableState,
narrowing: Narrowing,
window: &mut Window,
cx: &mut Context<TableState>,
) {
let total = state.page_counts().total.unwrap_or_default();
let page = state.page().last_of(total);
serve_page(state, &page_query(state, page, narrowing), cx);
state.focus_row(state.visible_rows().saturating_sub(1), window, cx);
}
fn serve_into(
table: &gpui::Entity<TableState>,
page: Page,
narrowing: Narrowing,
app: &mut App,
) {
table.update(app, move |state, inner| {
serve_page(state, &page_query(state, page, narrowing), inner);
});
}
fn serve_page(state: &mut TableState, query: &Query, cx: &mut Context<TableState>) {
let (rows, total) = fetch_page(query);
state.set_page(query.page, cx);
state.set_rows(rows, cx);
state.set_page_counts(PageCounts::of(total), cx);
paint_statuses(state, cx);
}
fn group_options() -> Vec<SelectOption> {
(1..=40)
.map(|number| {
let option = SelectOption::new(format!("group-{number:02}"), format!("g{number}"));
option.disabled(number % 13 == 0)
})
.collect()
}
fn paint_statuses(state: &mut TableState, cx: &mut Context<TableState>) {
let statuses = [
(0, color::SUCCESS_50),
(1, color::ERROR_50),
(2, color::WARNING_50),
];
for (row, tint) in statuses {
state.set_row_color(row, tint, cx);
}
}
const TABLE_COLUMN_WIDTH: f32 = 210.0;
fn table_columns() -> Vec<Column> {
vec![
Column::new("path", "Location").width(TABLE_COLUMN_WIDTH),
Column::new("line", "Line")
.kind(ColumnKind::Number)
.width(TABLE_COLUMN_WIDTH),
Column::new("severity", "Severity").width(TABLE_COLUMN_WIDTH),
Column::new("status", "Status").width(TABLE_COLUMN_WIDTH),
Column::new("weakness", "Weakness").width(TABLE_COLUMN_WIDTH),
Column::new("cwe", "CWE")
.kind(ColumnKind::Number)
.width(90.0),
Column::new("source", "Source").width(120.0),
]
}
fn team_options() -> Vec<SelectOption> {
vec![
SelectOption::new("Everyone", "all"),
SelectOption::new("Zulema", "z").group("Red team"),
SelectOption::new("Ana", "a").group("Red team"),
SelectOption::new("Bruno", "b").group("Blue team"),
SelectOption::new("Carla", "c").group("Blue team"),
SelectOption::new("On leave", "d")
.group("Blue team")
.disabled(true),
]
}
fn severity_options() -> Vec<SelectOption> {
vec![
SelectOption::new("Critical", "critical"),
SelectOption::new("High", "high"),
SelectOption::new("Medium", "medium"),
SelectOption::new("Low", "low"),
]
}
struct TextAreas {
comment: gpui::Entity<TextAreaState>,
filled: gpui::Entity<TextAreaState>,
error: gpui::Entity<TextAreaState>,
success: gpui::Entity<TextAreaState>,
disabled: gpui::Entity<TextAreaState>,
}
const SAMPLE_NOTE: &str = concat!(
"Reattack requested against the wrong root.\n",
"\n",
"Point it at the production mirror instead — the staging one was ",
"rebuilt on Tuesday and no longer carries the commit this was raised ",
"on.\n",
"Confirmed with the team on the call."
);
const SAMPLE_LOCKED: &str = concat!(
"Locked while the reattack is in flight. The justification cannot be ",
"edited until the analyst closes it out."
);
fn text_areas(window: &mut Window, cx: &mut Context<Gallery>) -> TextAreas {
let filled = cx.new(|cx| TextAreaState::new(window, cx).rows(6));
filled.update(cx, |state, cx| state.set_value(SAMPLE_NOTE, cx));
let disabled = cx.new(|cx| TextAreaState::new(window, cx).rows(3));
disabled.update(cx, |state, cx| state.set_value(SAMPLE_LOCKED, cx));
let success = cx.new(|cx| TextAreaState::new(window, cx).rows(3));
success.update(cx, |state, cx| state.set_value("Looks right to me.", cx));
TextAreas {
comment: cx.new(|cx| {
TextAreaState::new(window, cx)
.rows(3)
.max_length(400)
.placeholder("Leave a comment")
}),
filled,
error: cx.new(|cx| {
TextAreaState::new(window, cx)
.rows(3)
.placeholder("paste the deploy key")
}),
success,
disabled,
}
}
struct Lists {
topics: gpui::Entity<ListState>,
progress: gpui::Entity<ListState>,
bare: gpui::Entity<ListState>,
}
fn lists(cx: &mut Context<Gallery>) -> Lists {
Lists {
topics: topic_list(cx),
progress: progress_list(cx),
bare: cx.new(|inner| ListState::new(inner)),
}
}
fn topic_list(cx: &mut Context<Gallery>) -> gpui::Entity<ListState> {
let list = cx.new(|inner| ListState::new(inner));
list.update(cx, |state, inner| {
state.set_items(
vec![
ListItem::new("001. Anonymous connection", "001").icon("file-lines"),
ListItem::new("008. Reflected cross-site scripting", "008").icon("file-lines"),
ListItem::new("011. Use of insecure channel", "011").icon("file-lines"),
ListItem::new("037. Technical information leak", "037").icon("file-lines"),
ListItem::new("117. Unverifiable files", "117")
.icon("file-lines")
.disabled(true),
],
inner,
);
state.set_value(Some("008"), inner);
});
list
}
fn progress_list(cx: &mut Context<Gallery>) -> gpui::Entity<ListState> {
let list = cx.new(|inner| ListState::new(inner).selection(SelectionMode::None));
list.update(cx, |state, inner| {
state.set_items(
vec![
ListItem::new("Anonymous connection (F001)", "F001")
.icon("circle-check")
.right_icon("circle-check"),
ListItem::new("Insecure channel (F011)", "F011")
.icon("spinner")
.right_icon("spinner"),
ListItem::new("Information leak (F037) — rejected", "F037")
.icon("circle-xmark")
.right_icon("circle-xmark"),
],
inner,
);
});
list
}
impl Gallery {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let success_input = cx.new(|cx| TextInputState::new(window, cx));
let areas = text_areas(window, cx);
let secret_input =
cx.new(|cx| TextInputState::new(window, cx).masked(true).max_length(20));
success_input.update(cx, |state, cx| state.set_value("valid value", cx));
let table = cx.new(|cx| TableState::new(table_columns(), window, cx));
table.update(cx, |state, inner| {
state.set_search_placeholder("Search by location, severity or status", inner);
state.set_page_size(TABLE_PAGE_SIZE, inner);
narrow(state, Narrowing::All, inner);
});
let group_select = cx.new(|cx| SelectState::new("Select a group", window, cx));
let team_select = cx.new(|cx| SelectState::new("Assign to", window, cx));
let disabled_select = cx.new(|cx| SelectState::new("Unavailable", window, cx));
let form_severity = cx.new(|cx| SelectState::new("Severity", window, cx));
form_severity.update(cx, |state, cx| state.set_options(severity_options(), cx));
group_select.update(cx, |state, cx| state.set_options(group_options(), cx));
team_select.update(cx, |state, cx| state.set_options(team_options(), cx));
Self {
lists: lists(cx),
listed: SharedString::new_static("nothing chosen yet"),
group_select,
team_select,
disabled_select,
chosen: SharedString::new_static("nothing chosen yet"),
picked: SharedString::new_static("no path picked yet"),
tab: 0,
boxed_tab: 0,
table,
narrowing: Narrowing::All,
only_tests: false,
negative: true,
dialog: None,
said: None,
form_severity,
form_tested: false,
form_path: cx
.new(|cx| TextInputState::new(window, cx).placeholder("src/api/auth.py")),
form_line: cx.new(|cx| TextInputState::new(window, cx).placeholder("42")),
scroll: ScrollHandle::new(),
strip_scroll: ScrollHandle::new(),
token_input: cx
.new(|cx| TextInputState::new(window, cx).placeholder("glpat-… (api scope)")),
error_input: cx.new(|cx| TextInputState::new(window, cx).placeholder("with error")),
secret_input,
success_input,
disabled_input: cx
.new(|cx| TextInputState::new(window, cx).placeholder("disabled")),
areas,
}
}
fn image_views_row(cx: &Context<Self>) -> gpui::Div {
div()
.flex()
.items_center()
.gap(px(16.0))
.child(
ImageView::new("evidence-still")
.bytes(ImageFormat::Png, EVIDENCE)
.alt("A screenshot the platform returned with this finding")
.width(120.0)
.on_click(raising(Dialog::Picture, cx)),
)
.child(
ImageView::new("evidence-dimmed")
.bytes(ImageFormat::Png, EVIDENCE)
.width(120.0)
.scrim(0.5)
.on_click(raising(Dialog::Picture, cx)),
)
.child(
ImageView::new("evidence-picked")
.path(evidence_path())
.alt("The file the pentester just chose")
.height(96.0),
)
.child(ImageView::new("evidence-pending").width(120.0).height(96.0))
.child(ImageView::new("evidence-video").video())
.child(
ImageView::new("evidence-undecodable")
.bytes(ImageFormat::Png, b"not a picture at all".to_vec()),
)
}
fn selects_row(&self, cx: &Context<Self>) -> gpui::Div {
div()
.flex()
.items_start()
.gap(px(16.0))
.child(self.group_picker(cx))
.child(div().w(px(260.0)).child(Select::new(&self.team_select)))
.child(
div()
.w(px(200.0))
.child(Select::new(&self.disabled_select).disabled(true)),
)
.child(
Text::new(format!("on_change: {}", self.chosen)).size(typography::TextSize::Md),
)
}
fn group_picker(&self, cx: &Context<Self>) -> gpui::Div {
let gallery = cx.entity();
div()
.w(px(260.0))
.child(
Select::new(&self.group_select).on_change(move |value, app| {
gallery.update(app, |this, inner| record(this, value, inner));
}),
)
}
fn lists_row(&self, cx: &Context<Self>) -> gpui::Div {
let gallery = cx.entity();
div()
.flex()
.items_start()
.gap(px(16.0))
.child(
div()
.w(px(320.0))
.child(List::new(&self.lists.topics).on_change(move |value, app| {
gallery.update(app, |this, inner| note(this, value, inner));
})),
)
.child(div().w(px(320.0)).child(List::new(&self.lists.progress)))
.child(div().w(px(280.0)).child(List::new(&self.lists.bare).empty(
EmptyState::new("No findings", "Pick a group to see its findings here"),
)))
.child(
Text::new(format!("on_change: {}", self.listed)).size(typography::TextSize::Md),
)
}
fn checkboxes_row(&self, cx: &Context<Self>) -> gpui::Div {
div()
.flex()
.items_center()
.gap(px(24.0))
.child(
Checkbox::new("only-tests", self.only_tests)
.label("Only in tests")
.on_change(cx.listener(|this, checked: &bool, _window, inner| {
this.only_tests = *checked;
inner.notify();
})),
)
.child(
Checkbox::new("negative", self.negative)
.label("Negative entrypoint")
.tooltip("negative-tip", "an entrypoint that must not answer")
.on_change(cx.listener(|this, checked: &bool, _window, inner| {
this.negative = *checked;
inner.notify();
})),
)
.child(Checkbox::new("locked", true).label("Locked").disabled(true))
.child(
Checkbox::new("locked-empty", false)
.label("Locked, empty")
.disabled(true),
)
}
fn raise(&mut self, dialog: Dialog, cx: &mut Context<Self>) {
self.dialog = Some(dialog);
self.said = None;
cx.notify();
}
fn form_body(&self, cx: &Context<Self>) -> gpui::Div {
div()
.flex()
.flex_col()
.gap(px(16.0))
.child(
div()
.flex()
.items_start()
.gap(px(16.0))
.child(
div().flex_1().child(
TextInput::new(&self.form_path).label("Path").required(true),
),
)
.child(
div().w(px(120.0)).child(
TextInput::new(&self.form_line).label("Line").required(true),
),
),
)
.child(Select::new(&self.form_severity))
.child(
Checkbox::new("form-tested", self.form_tested)
.label("Already reattacked")
.on_change(cx.listener(|this, checked: &bool, _window, inner| {
this.form_tested = *checked;
inner.notify();
})),
)
}
fn pickers_row(&self, cx: &Context<Self>) -> gpui::Div {
div()
.flex()
.flex_col()
.gap(px(8.0))
.child(
div()
.flex()
.items_center()
.gap(px(16.0))
.child(Button::new("pick-file", "Pick a CSV").on_click(cx.listener(
|this, _, _window, inner| {
let request = Request::new(Selection::File)
.extensions(&["*.csv"])
.title("Select CSV file");
this.pick(&request, inner);
},
)))
.child(
Button::new("pick-files", "Pick evidence")
.variant(button::Variant::Tertiary)
.on_click(cx.listener(|this, _, _window, inner| {
let request = Request::new(Selection::Files)
.extensions(&["png", "jpg", "gif"])
.title("Select evidence files");
this.pick(&request, inner);
})),
)
.child(
Button::new("pick-directory", "Pick a folder")
.variant(button::Variant::Secondary)
.on_click(cx.listener(|this, _, _window, inner| {
let request = Request::new(Selection::Directory)
.title("Select Directory")
.start_in("");
this.pick(&request, inner);
})),
),
)
.child(Text::new(self.picked.clone()))
}
fn pick(&mut self, request: &Request<'_>, cx: &mut Context<Self>) {
let opened = file_picker::open(request, cx);
self.picked = SharedString::new_static("waiting on the panel…");
cx.spawn(async move |gallery, app| {
let said = describe(&opened.await);
gallery
.update(app, |this, inner| report(this, said, inner))
.ok();
})
.detach();
cx.notify();
}
fn modals_row(&self, cx: &Context<Self>) -> gpui::Div {
div()
.flex()
.items_center()
.gap(px(16.0))
.child(Button::new("open-form", "Open a modal").on_click(
cx.listener(|this, _, _window, inner| this.raise(Dialog::Form, inner)),
))
.child(
Button::new("open-confirm", "Ask before deleting")
.variant(button::Variant::Tertiary)
.on_click(cx.listener(|this, _, _window, inner| {
this.raise(Dialog::Confirm, inner);
})),
)
.child(
Button::new("open-blocking", "One that will not close")
.variant(button::Variant::Secondary)
.on_click(cx.listener(|this, _, _window, inner| {
this.raise(Dialog::Blocking, inner);
})),
)
.children(self.said.clone().map(Text::new))
}
fn dialog_layer(&self, cx: &Context<Self>) -> Option<gpui::AnyElement> {
let closing = cx.listener(|this, outcome: &Outcome, _window, inner| {
this.said = Some(SharedString::from(format!("{outcome:?}")));
this.dialog = None;
inner.notify();
});
let raised = match self.dialog? {
Dialog::Form => Modal::new("gallery-form")
.title("Add a line")
.description("Add is disabled until the path is filled in.")
.size(ModalSize::Md)
.body_height(180.0)
.confirm_text("Add")
.cancel_text("Cancel")
.confirm_disabled(self.form_path.read(cx).value().is_empty())
.on_outcome(closing)
.child(self.form_body(cx))
.into_any_element(),
Dialog::Confirm => ConfirmDialog::new(
"gallery-confirm",
"Delete this root?",
"Everything scanned under it goes with it.",
)
.confirm_text("Delete")
.on_outcome(closing)
.into_any_element(),
Dialog::Alert(variant) => {
let (title, message) = alert_copy(variant);
AlertDialog::new("gallery-alert", title, message)
.variant(variant)
.on_acknowledge(reporting("Alert acknowledged", cx))
.into_any_element()
}
Dialog::Picture => picture_dialog(cx),
Dialog::Blocking => Modal::new("gallery-blocking")
.title("Waiting on the API")
.description("No dismiss control, no Escape, and a confirm that cannot fire.")
.size(ModalSize::Fixed)
.dismissable(false)
.confirm_text("Continue")
.cancel_text("Give up")
.confirm_disabled(true)
.on_outcome(closing)
.into_any_element(),
};
Some(raised)
}
fn inputs_row(&self) -> gpui::Div {
div()
.flex()
.items_start()
.gap(px(16.0))
.child(
TextInput::new(&self.token_input)
.label("Token")
.required(true)
.label_tooltip("token-tip", "a personal access token")
.has_icon(true)
.help_text("scopes: api")
.help_link("token-help", "docs", "https://fluidattacks.com"),
)
.child(
TextInput::new(&self.secret_input)
.label("Secret")
.required(true),
)
.child(
TextInput::new(&self.error_input)
.label("Error")
.error("This field is required"),
)
.child(
TextInput::new(&self.success_input)
.label("Success")
.success(true),
)
.child(
TextInput::new(&self.disabled_input)
.label("Disabled")
.disabled(true),
)
}
fn text_areas_row(&self) -> gpui::Div {
div()
.flex()
.items_start()
.gap(px(16.0))
.child(
TextArea::new(&self.areas.comment)
.label("Comment")
.required(true)
.label_tooltip("comment-tip", "markdown is not rendered")
.help_text("three rows, and a counter"),
)
.child(
TextArea::new(&self.areas.filled)
.label("Justification")
.required(true)
.help_text("six rows, and the rest scrolls"),
)
.child(
TextArea::new(&self.areas.error)
.label("SSH key")
.required(true)
.error("This field is required"),
)
.child(
TextArea::new(&self.areas.success)
.label("Description")
.success(true),
)
.child(
TextArea::new(&self.areas.disabled)
.label("Disabled")
.disabled(true),
)
}
}
fn picture_dialog(cx: &Context<Gallery>) -> gpui::AnyElement {
ImageViewer::new(
"gallery-picture",
EVIDENCE_NAME,
ImageView::new("gallery-picture-full")
.bytes(ImageFormat::Png, EVIDENCE)
.alt("A screenshot the platform returned with this finding"),
)
.on_close(reporting("Viewer closed", cx))
.into_any_element()
}
fn note(gallery: &mut Gallery, value: &str, cx: &mut Context<Gallery>) {
gallery.listed = SharedString::from(value.to_owned());
cx.notify();
}
fn record(gallery: &mut Gallery, value: &str, cx: &mut Context<Gallery>) {
gallery.chosen = SharedString::from(value.to_owned());
cx.notify();
}
type TabHandler = Box<dyn Fn(&gpui::ClickEvent, &mut Window, &mut gpui::App) + 'static>;
fn images_row() -> gpui::Div {
let ids = [
"integrates/empty/addRoot",
"integrates/empty/analyticsIcon",
"integrates/empty/cloning",
"integrates/empty/cloningError",
"integrates/empty/consulting",
"integrates/autoenrollment/cloud-check-icon",
];
let mut row = div().flex().items_center().gap(px(16.0));
for id in ids {
row = row.child(CloudImage::new(id).width(96.0).height(96.0));
}
row
}
fn notification_signs_row() -> gpui::Div {
use fluidattacks_designs::tokens::notification_sign::Variant;
div()
.flex()
.items_center()
.gap(px(16.0))
.child(NotificationSign::new().count(3))
.child(NotificationSign::new().count(120))
.child(NotificationSign::new().variant(Variant::Warning).count(5))
.child(
div()
.relative()
.child(Text::new("anchored dot").size(typography::TextSize::Md))
.child(NotificationSign::new().top(-2.0).left(76.0)),
)
}
fn tooltip_row() -> gpui::Div {
use fluidattacks_designs::tokens::tooltip::Place;
div()
.flex()
.items_center()
.gap(px(48.0))
.child(
Tooltip::new("tooltip-plain", "a plain tip")
.child(Text::new("hover me").size(typography::TextSize::Md)),
)
.child(
Tooltip::new(
"tooltip-titled",
"with the bold heading above the body text",
)
.title("a title")
.place(Place::Top)
.child(Text::new("hover me too").size(typography::TextSize::Md)),
)
.child(
Tooltip::new("tooltip-right", "to the right")
.place(Place::Right)
.child(Text::new("and me").size(typography::TextSize::Md)),
)
}
fn empty_state_row() -> impl IntoElement {
EmptyState::new("No merge requests", "Everything in your court is done.")
.size(fluidattacks_designs::tokens::empty_state::Size::Sm)
.confirm("empty-refresh", "refresh", |_event, _window, _cx| {})
.cancel("empty-dismiss", "dismiss", |_event, _window, _cx| {})
}
fn icons_row() -> gpui::Div {
let names = [
"magnifying-glass",
"bug",
"shield-check",
"trash",
"check",
"xmark",
];
let mut row = div().flex().items_center().gap(px(12.0));
for name in names {
row = row.child(Icon::new(name));
}
for name in names {
row = row.child(
Icon::new(name)
.style(fluidattacks_designs::tokens::icon::Style::Light)
.size(fluidattacks_designs::tokens::icon::Size::Xl)
.color(fluidattacks_designs::tokens::color::PRIMARY_500),
);
}
for name in names {
row = row.child(
Icon::new(name)
.style(fluidattacks_designs::tokens::icon::Style::Gradient)
.size(fluidattacks_designs::tokens::icon::Size::Xl),
);
}
row
}
fn loading_row() -> gpui::Div {
div()
.flex()
.items_center()
.gap(px(16.0))
.child(Loading::new().label("loading…"))
.child(
Loading::new()
.size(loading::Size::Sm)
.color(loading::Color::Blue),
)
.child(
div()
.p(px(8.0))
.rounded(px(4.0))
.bg(rgb(fluidattacks_designs::tokens::color::GRAY_800))
.child(
Loading::new()
.size(loading::Size::Sm)
.color(loading::Color::White)
.label("white"),
),
)
.child(
Loading::new()
.size(loading::Size::Sm)
.color(loading::Color::Custom(
fluidattacks_designs::tokens::color::SUCCESS_500,
))
.label("custom"),
)
}
fn avatars_row() -> gpui::Div {
div()
.flex()
.items_center()
.gap(px(8.0))
.child(Avatar::new("dacevedo"))
.child(Avatar::new("Ana Maria").show_username(true))
.child(
Avatar::new("Clickable User")
.show_username(true)
.on_click("avatar-click", |_event, _window, _cx| {}),
)
.child(
Avatar::new("Menu User")
.show_username(true)
.show_icon(true)
.on_click("avatar-menu", |_event, _window, _cx| {}),
)
}
fn typography_column() -> gpui::Div {
let span_demo = Span::new("span: fixed 20px leading, pre-line text\nsecond line")
.size(typography::TextSize::Md);
div()
.flex()
.flex_col()
.gap(px(4.0))
.child(Heading::new("Heading lg").size(typography::HeadingSize::Lg))
.child(Heading::new("Heading md").size(typography::HeadingSize::Md))
.child(Heading::new("Heading sm").size(typography::HeadingSize::Sm))
.child(Heading::new("Heading xs"))
.child(Text::new("Text lg — regular on the text role"))
.child(Text::new("Text md").size(typography::TextSize::Md))
.child(
Text::new("Text sm bold")
.size(typography::TextSize::Sm)
.weight(typography::Weight::Bold),
)
.child(Text::new("Text xs").size(typography::TextSize::Xs))
.child(span_demo)
}
fn tags_rows() -> gpui::Div {
let variants = [
("default", tag::Variant::Default),
("inactive", tag::Variant::Inactive),
("reachable", tag::Variant::Reachable),
("remediation", tag::Variant::Remediation),
("role", tag::Variant::Role),
("technique", tag::Variant::Technique),
];
let mut neutral = div().flex().items_center().gap(px(8.0));
for (label, variant) in variants {
neutral = neutral.child(Tag::new(label).variant(variant));
}
let mut semantic = div().flex().items_center().gap(px(8.0));
for (label, variant) in [
("error", tag::Variant::Error),
("warning", tag::Variant::Warning),
("success", tag::Variant::Success),
("info", tag::Variant::Info),
] {
for (suffix, priority) in [
("high", tag::Priority::High),
("medium", tag::Priority::Medium),
("low", tag::Priority::Low),
] {
semantic = semantic.child(
Tag::new(format!("{label} {suffix}"))
.variant(variant)
.priority(priority),
);
}
}
div()
.flex()
.flex_col()
.gap(px(8.0))
.child(neutral)
.child(semantic)
.child(tag_features_row())
}
fn tag_features_row() -> gpui::Div {
div()
.flex()
.items_center()
.gap(px(8.0))
.child(Tag::new("with icon").icon("user"))
.child(Tag::new("value").title("filter: ").filter_values(" +2"))
.child(Tag::new("a very long label that should truncate here"))
.child(Tag::new("closable").on_close("tag-close", |_, _, _| {}))
.child(
Tag::new("disabled close")
.disabled(true)
.on_close("tag-close-off", |_, _, _| {}),
)
.child(Tag::new("linked").link("tag-link", "https://fluidattacks.com"))
}
fn buttons_row() -> gpui::Div {
let variants = [
("primary", button::Variant::Primary),
("secondary", button::Variant::Secondary),
("tertiary", button::Variant::Tertiary),
("ghost", button::Variant::Ghost),
("link", button::Variant::Link),
];
let mut row = div().flex().items_center().gap(px(8.0));
for (label, variant) in variants {
let button = Button::new(label, label).variant(variant);
let button = if matches!(variant, button::Variant::Link) {
button.href("https://fluidattacks.com")
} else {
button
};
row = row.child(button);
}
row = row.child(
Button::new("disabled", "disabled")
.variant(button::Variant::Primary)
.disabled(true),
);
div()
.flex()
.flex_col()
.gap(px(8.0))
.child(row)
.child(button_features_row())
}
fn button_features_row() -> gpui::Div {
div()
.flex()
.items_center()
.gap(px(8.0))
.child(Button::new("btn-icon", "with icon").icon("plus"))
.child(Button::new("btn-right", "next").right_icon("chevron-right"))
.child(Button::new("btn-underline", "underlined").underline(true))
.child(
Button::new("btn-dot", "ghost dot")
.variant(button::Variant::Ghost)
.dot_notification(true),
)
.child(
Button::new("btn-count", "ghost count")
.variant(button::Variant::Ghost)
.notification(12),
)
.child(Button::new("btn-tag", "with tag").tag("3"))
.child(
Button::new("btn-tooltip", "with tooltip")
.variant(button::Variant::Secondary)
.tooltip("btn-tooltip-tip", "buttons carry tips now"),
)
.child(
Button::new("btn-wide", "wide start")
.variant(button::Variant::Secondary)
.width(180.0)
.justify(button::Justify::Start),
)
}
impl Gallery {
fn select_boxed_tab(cx: &Context<Self>, index: usize) -> TabHandler {
Box::new(cx.listener(move |this, _event, _window, cx| {
this.boxed_tab = index;
cx.notify();
}))
}
fn select_tab(cx: &Context<Self>, index: usize) -> TabHandler {
Box::new(cx.listener(move |this, _event, _window, cx| {
this.tab = index;
cx.notify();
}))
}
fn boxed_tabs_row(&self, cx: &Context<Self>) -> impl IntoElement {
Tabs::new()
.boxed(true)
.item(
TabItem::new("chips", "with chip")
.active(self.boxed_tab == 0)
.tag("3")
.on_click(Self::select_boxed_tab(cx, 0)),
)
.item(
TabItem::new("signed", "with sign")
.active(self.boxed_tab == 1)
.notification(NotificationSign::new().count(7))
.tooltip("tab-tip", "tabs too")
.on_click(Self::select_boxed_tab(cx, 1)),
)
}
#[must_use]
fn horizontal_scroll_row(&self) -> gpui::Div {
let mut strip = div()
.id("wide-strip")
.flex()
.gap(px(8.0))
.overflow_x_scroll()
.track_scroll(&self.strip_scroll)
.pb(px(12.0));
for index in 0..30 {
strip = strip.child(Tag::new(format!("chip {index}")));
}
div()
.relative()
.w(px(360.0))
.on_scroll_wheel(fluidattacks_designs::stop_dominant_axis(
fluidattacks_designs::ScrollAxis::Horizontal,
))
.child(strip)
.child(
Scrollbar::horizontal("strip-scrollbar", &self.strip_scroll)
.track_transparent(),
)
}
fn table_controls(&self, cx: &Context<Self>) -> gpui::Div {
let filtering = if self.narrowing == Narrowing::Criticals {
"show every severity"
} else {
"criticals only"
};
div()
.flex()
.items_center()
.gap(px(8.0))
.child(
Button::new("tbl-filter", filtering)
.variant(button::Variant::Secondary)
.on_click(cx.listener(Self::toggle_criticals)),
)
.child(
Button::new("tbl-columns", "toggle the status column")
.variant(button::Variant::Secondary)
.on_click(cx.listener(Self::toggle_status_column)),
)
.child(
Button::new("tbl-jump", "jump to the last row")
.variant(button::Variant::Secondary)
.on_click(cx.listener(Self::jump_to_last)),
)
.child(
Button::new("tbl-empty", "filter to nothing")
.variant(button::Variant::Secondary)
.on_click(cx.listener(Self::filter_to_nothing)),
)
}
fn filter_to_nothing(
&mut self,
_event: &gpui::ClickEvent,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.narrowing = Narrowing::Nothing;
self.table
.update(cx, |state, inner| narrow(state, Narrowing::Nothing, inner));
cx.notify();
}
fn toggle_criticals(
&mut self,
_event: &gpui::ClickEvent,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.narrowing = if self.narrowing == Narrowing::Criticals {
Narrowing::All
} else {
Narrowing::Criticals
};
let only = self.narrowing;
self.table
.update(cx, move |state, inner| narrow(state, only, inner));
cx.notify();
}
fn toggle_status_column(
&mut self,
_event: &gpui::ClickEvent,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.table.update(cx, |state, cx| {
let shown = state.is_column_visible("status");
state.set_column_visible("status", !shown, cx);
});
}
fn jump_to_last(
&mut self,
_event: &gpui::ClickEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
let narrowing = self.narrowing;
self.table.update(cx, move |state, inner| {
jump_to_end(state, narrowing, window, inner);
});
}
fn table_section(&self, cx: &Context<Self>) -> impl IntoElement {
div().flex().flex_col().gap(px(8.0)).child(
Table::new(&self.table)
.height(320.0)
.search(true)
.control(self.table_controls(cx))
.pagination(self.fetch_into())
.empty("No findings", "Nothing matched this filter."),
)
}
fn fetch_into(&self) -> impl Fn(Page, &mut App) + 'static {
let table = self.table.clone();
let narrowing = self.narrowing;
move |page, app| serve_into(&table, page, narrowing, app)
}
fn tabs_row(&self, cx: &Context<Self>) -> impl IntoElement {
let mut bar = Tabs::new();
for (index, label) in ["inbox", "blocked", "settings"].into_iter().enumerate() {
let select = Self::select_tab(cx, index);
bar = bar.tab(label, label, self.tab == index, select);
}
bar.variant(if self.tab == 2 {
tabs::Variant::Secondary
} else {
tabs::Variant::Primary
})
}
}
fn mode_switch(cx: &Context<Gallery>) -> impl IntoElement {
let mode = cx.theme().mode;
div()
.flex()
.flex_none()
.items_center()
.justify_end()
.px(px(24.0))
.pt(px(24.0))
.child(
Button::new("mode-switch", mode.toggle_label())
.variant(button::Variant::Tertiary)
.icon(mode.toggle_icon())
.icon_style(fluidattacks_designs::tokens::icon::Style::Light)
.tooltip("mode-switch-tip", "Repaint the catalog in the other mode")
.on_click(cx.listener(|_, _, window, cx| {
let next = cx.theme().mode.flipped();
Theme::init(next, cx, &[]);
window.refresh();
cx.notify();
})),
)
}
impl Render for Gallery {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.size_full()
.flex()
.flex_col()
.bg(rgb(cx.theme().colors.surface))
.child(mode_switch(cx))
.child(
Container::new()
.scroll("gallery", Scroll::Y, &self.scroll)
.flex()
.flex_col()
.gap(px(24.0))
.w_full()
.flex_1()
.min_h_0()
.p(px(24.0))
.child(typography_column())
.child(self.selects_row(cx))
.child(buttons_row())
.child(tags_rows())
.child(self.tabs_row(cx))
.child(self.boxed_tabs_row(cx))
.child(avatars_row())
.child(loading_row())
.child(icons_row())
.child(images_row())
.child(Self::image_views_row(cx))
.child(self.inputs_row())
.child(self.text_areas_row())
.child(self.checkboxes_row(cx))
.child(self.pickers_row(cx))
.child(self.modals_row(cx))
.child(alerts_row(cx))
.child(alert_dialogs_row(cx))
.child(notification_signs_row())
.child(tooltip_row())
.child(self.horizontal_scroll_row())
.child(self.table_section(cx))
.child(empty_state_row())
.child(self.lists_row(cx)),
)
.children(self.dialog_layer(cx))
}
}
fn report(gallery: &mut Gallery, said: SharedString, cx: &mut Context<Gallery>) {
gallery.picked = said;
cx.notify();
}
fn describe(picked: &Picked) -> SharedString {
if picked.cancelled() {
return SharedString::new_static("cancelled");
}
let listed: Vec<String> = picked
.paths()
.iter()
.map(|path| path.display().to_string())
.collect();
SharedString::from(format!("{} chosen: {}", listed.len(), listed.join(", ")))
}
fn quit_on_last_close(cx: &App) {
cx.on_window_closed(|cx, _| {
if cx.windows().is_empty() {
cx.quit();
}
})
.detach();
}
fn faces() -> Vec<std::borrow::Cow<'static, [u8]>> {
let Ok(dir) = std::env::var("FLUIDATTACKS_DESIGNS_FONTS") else {
return Vec::new();
};
let Ok(entries) = std::fs::read_dir(dir) else {
return Vec::new();
};
entries
.flatten()
.filter(|entry| entry.path().extension().is_some_and(|ext| ext == "ttf"))
.filter_map(|entry| std::fs::read(entry.path()).ok())
.map(std::borrow::Cow::Owned)
.collect()
}
pub fn run() {
let assets = Assets::new(&[]);
application().with_assets(assets).run(|cx: &mut App| {
Theme::init(theme::Mode::default(), cx, &faces());
let bounds = Bounds::centered(None, size(px(900.0), px(760.0)), cx);
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..WindowOptions::default()
};
let opened =
cx.open_window(options, |window, cx| cx.new(|cx| Gallery::new(window, cx)));
if opened.is_err() {
cx.quit();
return;
}
quit_on_last_close(cx);
cx.activate(true);
});
}
}
#[cfg(target_os = "macos")]
fn main() {
gallery::run();
}
#[cfg(not(target_os = "macos"))]
fn main() {}