updated the readme, and fixed the tui logic to display the running

operations progress better, also fixed a bug in project promotion where
the project files path and notes path wasn't being added right.
This commit is contained in:
2026-09-02 15:15:27 -05:00
parent dd8fee4297
commit 96672190c4
3 changed files with 61 additions and 19 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ Other command flag options are:
- -n the name to set for the client or server (will be reported to the other servers/clients)
## Attack Client
The attack client runs in a TUI. The TUI is separated into 2 parts, the top part and the bottom part. The top part contains three sections. From left to right the sections are project list, output box, info box. The project box contains a list of projects that are currently being tracked. You can select from the list by holding control and pressing the up and down arrow keys. The output box shows the output of commands you run, and the info section displays information about the selected project and tetanus's config. By default scrolling in any part of the TUI will scroll the output box, unless your mouse cursor is over the info box then the info box will be scrolled. If the output box is at the bottom of its scrolling then it will keep up with new output that comes in, if it's anywhere else (like you've scrolled up to read something), then it will not autmoatically follow, allowing you to review data easily without being interrupted by new input coming in.
The attack client runs in a TUI. The TUI is separated into 2 parts, the top part and the bottom part. The top part contains three, sometimes four sections. From left to right the sections are project list, output box, info box. The project box contains a list of projects that are currently being tracked. You can select from the list by holding control and pressing the up and down arrow keys. The output box shows the output of commands you run, and the info section displays information about the selected project and tetanus's config. If there are any tracked long running operations such as rustbusting the infobox will be split into two sections, a top and bottom section. The information previously shown will be displayed in the top section, and a list of long running operations will be displayed in the bottom section, along with a percentage of completeness. By default scrolling in any part of the TUI will scroll the output box, unless your mouse cursor is over the info box then the info box will be scrolled. If the output box is at the bottom of its scrolling then it will keep up with new output that comes in, if it's anywhere else (like you've scrolled up to read something), then it will not autmoatically follow, allowing you to review data easily without being interrupted by new input coming in.
The bottom part is the Command input box. This is where you type commands to run. Pressing enter will run the command in the box and clear the box. Pressing arrow up or down cycles through previous commands run like it does in a terminal.
+37 -9
View File
@@ -282,25 +282,53 @@ pub fn run_tui(
.wrap(ratatui::widgets::Wrap { trim: false });
f.render_widget(info_paragraph, progress_section[0]);
let mut progress_lines = Vec::new();
let mut color_differnt = false;
state.progress_bars.iter().for_each(|progress| {
if let Ok(status) = progress.read() {
let percentage = status.complete / status.total * 100;
if percentage == 100 {
progress_lines.push(Line::from(Span::styled(
format!("{}:)", status.name),
Style::default().fg(Color::Green),
)));
progress_lines.push(Line::from(Span::styled(
format!(
"{} - {}/{} ({}%)",
status.name, status.complete, status.total, percentage
" {}/{} ({}%)",
status.complete, status.total, percentage
),
Style::default().fg(Color::Green),
)));
} else {
progress_lines.push(Line::from(Span::styled(
format!(
"{} - {}/{} ({}%)",
status.name, status.complete, status.total, percentage
),
Style::default().fg(Color::Magenta),
)));
match color_differnt {
true => {
progress_lines.push(Line::from(Span::styled(
format!("{}:)", status.name),
Style::default().fg(Color::Cyan),
)));
progress_lines.push(Line::from(Span::styled(
format!(
" {}/{} ({}%)",
status.complete, status.total, percentage
),
Style::default().fg(Color::Cyan),
)));
color_differnt = false;
}
false => {
progress_lines.push(Line::from(Span::styled(
format!("{}:)", status.name),
Style::default().fg(Color::Magenta),
)));
progress_lines.push(Line::from(Span::styled(
format!(
" {}/{} ({}%)",
status.complete, status.total, percentage
),
Style::default().fg(Color::Magenta),
)));
color_differnt = true;
}
}
}
}
});
+23 -9
View File
@@ -252,14 +252,14 @@ impl AppState {
.to_string(),
);
self.help.push("parse_scope\nparse the projects general.md notes file for the scope copied from the workbook\n".to_string());
self.help.push("parse_portscan | pps\nParse a services.tsv file exported from cobalt stirke in your current project's files directory".to_string());
self.help.push("parse_portscan | pps\nParse a services.tsv file exported from cobalt stirke in your current project's files directory\n".to_string());
self.help
.push("stop_db\nrestart this project's distrobox".to_string());
self.help.push("rustwitness\nRun a scan on the hosts in the urls.txt file in yoru project files directory that captures screenshots of those urls, optionally through a proxy.".to_string());
self.help.push("portscan\nRun a simple TCP portscan on either the scope for the project, or a given list of ips.\nportscan ip,ip,ip,ip port,port,port,port or portscan port,port,port".to_string());
self.help.push("rustbuster\nRun the Subdomain/Subdirectory bruteforcetool RustBuster (included in tetanus now!) against a given domain name or url.\nrustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist".to_string());
self.help.push("rustwitness\nRun a scan on the hosts in the urls.txt file in yoru project files directory that captures screenshots of those urls, optionally through a proxy.\n".to_string());
self.help.push("portscan\nRun a simple TCP portscan on either the scope for the project, or a given list of ips.\nportscan ip,ip,ip,ip port,port,port,port or portscan port,port,port\n".to_string());
self.help.push("rustbuster\nRun the Subdomain/Subdirectory bruteforcetool RustBuster (included in tetanus now!) against a given domain name or url.\nrustbuster target,target,target subs=/path/to/subwordlist dirs=/path/to/dirwordlist\n".to_string());
self.help
.push("clear_operations\nClear the progress section of the tool.".to_string());
.push("clear_operations\nClear the completed operation statuses.\n".to_string());
self.help.push("exit\nquit the tool\n".to_string());
self.initialize_modules();
return Ok(());
@@ -1234,9 +1234,14 @@ impl AppState {
format!("{} targets found scanning...", num_dir + num_sub),
)));
let new_progress = Arc::new(RwLock::new(ToolProgress {
name: "rustbuster".to_string(),
name: format!(
"rustbuster - {} {}",
self.projects[self.selected_project].org_name.clone(),
self.projects[self.selected_project].name.clone()
),
total: num_dir + num_sub,
complete: 0,
finished: false,
}));
self.progress_bars.push(new_progress.clone());
self.workers.spawn(move || {
@@ -1314,6 +1319,9 @@ impl AppState {
}
}
});
if let Ok(mut lock) = new_progress.write() {
lock.finished = true;
}
drop(project_clone);
});
} else {
@@ -1349,7 +1357,10 @@ impl AppState {
}
}
"clear_operations" => {
self.progress_bars.clear();
self.progress_bars.retain(|pb| {
let lock = pb.read().unwrap();
!lock.finished
});
}
"exit" => {
self.save_all()?;
@@ -1655,9 +1666,9 @@ impl AppState {
)))?;
let return_string = String::from("[success] Project promoted!");
let mut new_project = self.projects[self.selected_project].clone();
let new_files_path = PathBuf::from(self.config.get("current_files").unwrap())
let mut new_files_path = PathBuf::from(self.config.get("current_files").unwrap())
.join(format!("{}", new_project.org_name));
let new_notes_path = PathBuf::from(self.config.get("current_notes").unwrap())
let mut new_notes_path = PathBuf::from(self.config.get("current_notes").unwrap())
.join(format!("{}", new_project.org_name));
let mut options = CopyOptions::new();
create_dir_all(&new_files_path)?;
@@ -1671,6 +1682,8 @@ impl AppState {
)))?;
remove_dir_all(&new_project.files)?;
remove_dir_all(&new_project.notes)?;
new_files_path.push(&new_project.name);
new_notes_path.push(&new_project.name);
new_project.files(new_files_path.clone());
new_project.notes(new_notes_path.clone());
new_project.current = true;
@@ -3312,4 +3325,5 @@ struct ToolProgress {
name: String,
total: usize,
complete: usize,
finished: bool,
}