feat: log search functionality, closes #72
This commit is contained in:
@@ -117,6 +117,7 @@ In application controls, these, amongst many other settings, can be customized w
|
|||||||
| ```( 1-9 )``` | Sort containers by heading, clicking on headings also sorts the selected column. |
|
| ```( 1-9 )``` | Sort containers by heading, clicking on headings also sorts the selected column. |
|
||||||
| ```( 0 )``` | Stop sorting.|
|
| ```( 0 )``` | Stop sorting.|
|
||||||
| ```( F1 )``` or ```( / )``` | Enter filter mode. |
|
| ```( F1 )``` or ```( / )``` | Enter filter mode. |
|
||||||
|
| ```( # )``` | Enter log search mode. |
|
||||||
| ```( - ) ``` or ```(=)``` | Reduce or increase the height of the logs panel.|
|
| ```( - ) ``` or ```(=)``` | Reduce or increase the height of the logs panel.|
|
||||||
| ```( \ )``` | Toggle the visibility of the logs panel.|
|
| ```( \ )``` | Toggle the visibility of the logs panel.|
|
||||||
| ```( e )``` | Exec into the selected container - not available on Windows.|
|
| ```( e )``` | Exec into the selected container - not available on Windows.|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"use_cli": false,
|
"use_cli": false,
|
||||||
// Show the logs section - this can be changed during operation with the log_section_toggle key
|
// Show the logs section - this can be changed during operation with the log_section_toggle key
|
||||||
"show_logs": true,
|
"show_logs": true,
|
||||||
|
// Use case-sensitive matching for logs
|
||||||
|
"log_search_case_sensitive": true,
|
||||||
//////////////////
|
//////////////////
|
||||||
// Custom Keymap //
|
// Custom Keymap //
|
||||||
//////////////////
|
//////////////////
|
||||||
@@ -67,6 +69,10 @@
|
|||||||
"/",
|
"/",
|
||||||
"F1"
|
"F1"
|
||||||
],
|
],
|
||||||
|
// Enter log search mode
|
||||||
|
"log_search_mode": [
|
||||||
|
"#"
|
||||||
|
],
|
||||||
// Quit at anytime
|
// Quit at anytime
|
||||||
"quit": [
|
"quit": [
|
||||||
"q"
|
"q"
|
||||||
@@ -85,7 +91,9 @@
|
|||||||
"end"
|
"end"
|
||||||
],
|
],
|
||||||
// Modifier to scroll by 10 lines instead of one, used in conjunction with scroll_up/scroll_down
|
// Modifier to scroll by 10 lines instead of one, used in conjunction with scroll_up/scroll_down
|
||||||
"scroll_many": ["control"],
|
"scroll_many": [
|
||||||
|
"control"
|
||||||
|
],
|
||||||
// Scroll up to the start of a list
|
// Scroll up to the start of a list
|
||||||
"scroll_start": [
|
"scroll_start": [
|
||||||
"home"
|
"home"
|
||||||
@@ -285,6 +293,17 @@
|
|||||||
// Highlighted text color
|
// Highlighted text color
|
||||||
"highlight": "magenta"
|
"highlight": "magenta"
|
||||||
},
|
},
|
||||||
|
// The log search panel
|
||||||
|
"log_search": {
|
||||||
|
// Background color of panel
|
||||||
|
"background": "reset",
|
||||||
|
// color of text
|
||||||
|
"text": "gray",
|
||||||
|
// text color of the buttons text
|
||||||
|
"button_text": "black",
|
||||||
|
// Highlighted text color
|
||||||
|
"highlight": "magenta"
|
||||||
|
},
|
||||||
// The logs panel, will only be applied if color_logs is false
|
// The logs panel, will only be applied if color_logs is false
|
||||||
"logs": {
|
"logs": {
|
||||||
// Background color of panel
|
// Background color of panel
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ use_cli = false
|
|||||||
# Show the logs section - this can be changed during operation with the log_section_toggle key
|
# Show the logs section - this can be changed during operation with the log_section_toggle key
|
||||||
show_logs = true
|
show_logs = true
|
||||||
|
|
||||||
|
# Use case-sensitive matching for logs
|
||||||
|
log_search_case_sensitive = true
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Custom Keymap #
|
# Custom Keymap #
|
||||||
#################
|
#################
|
||||||
@@ -72,6 +75,10 @@ delete_confirm = ["y"]
|
|||||||
exec = ["e"]
|
exec = ["e"]
|
||||||
# Enter filter mode
|
# Enter filter mode
|
||||||
filter_mode = ["/", "F1"]
|
filter_mode = ["/", "F1"]
|
||||||
|
|
||||||
|
# Enter log search mode
|
||||||
|
log_search_mode = ["#"]
|
||||||
|
|
||||||
# Quit at anytime
|
# Quit at anytime
|
||||||
quit = ["q"]
|
quit = ["q"]
|
||||||
# Save logs of selected container to file on disk
|
# Save logs of selected container to file on disk
|
||||||
@@ -115,6 +122,9 @@ log_section_height_decrease = ["-"]
|
|||||||
log_section_height_increase = ["+"]
|
log_section_height_increase = ["+"]
|
||||||
# Toggle visibility of the log section
|
# Toggle visibility of the log section
|
||||||
log_section_toggle = ["\\"]
|
log_section_toggle = ["\\"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Force a complete clear & redraw of the screen
|
# Force a complete clear & redraw of the screen
|
||||||
force_redraw = ["f"]
|
force_redraw = ["f"]
|
||||||
|
|
||||||
@@ -192,6 +202,17 @@ selected_filter_text = "black"
|
|||||||
highlight = "magenta"
|
highlight = "magenta"
|
||||||
|
|
||||||
|
|
||||||
|
# The log search panel
|
||||||
|
[colors.log_search]
|
||||||
|
# Background color of panel
|
||||||
|
background = "reset"
|
||||||
|
# color of text
|
||||||
|
text = "gray"
|
||||||
|
# text color of the buttons text
|
||||||
|
button_text = "black"
|
||||||
|
# Highlighted text color
|
||||||
|
highlight = "magenta"
|
||||||
|
|
||||||
# The color the of Docker commands available for each container
|
# The color the of Docker commands available for each container
|
||||||
[colors.commands]
|
[colors.commands]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
|
|||||||
+330
-15
@@ -22,6 +22,12 @@ const ONE_KB: f64 = 1000.0;
|
|||||||
const ONE_MB: f64 = ONE_KB * 1000.0;
|
const ONE_MB: f64 = ONE_KB * 1000.0;
|
||||||
const ONE_GB: f64 = ONE_MB * 1000.0;
|
const ONE_GB: f64 = ONE_MB * 1000.0;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||||
|
pub enum ScrollDirection {
|
||||||
|
Next,
|
||||||
|
Previous,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||||
pub struct ContainerId(String);
|
pub struct ContainerId(String);
|
||||||
|
|
||||||
@@ -177,7 +183,14 @@ impl<T> StatefulList<T> {
|
|||||||
self.state.select(Some(0));
|
self.state.select(Some(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self) {
|
pub fn scroll(&mut self, scroll: &ScrollDirection) {
|
||||||
|
match scroll {
|
||||||
|
ScrollDirection::Next => self.next(),
|
||||||
|
ScrollDirection::Previous => self.previous(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) {
|
||||||
if !self.items.is_empty() {
|
if !self.items.is_empty() {
|
||||||
self.state.select(Some(
|
self.state.select(Some(
|
||||||
self.state.selected().map_or(
|
self.state.selected().map_or(
|
||||||
@@ -190,7 +203,7 @@ impl<T> StatefulList<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous(&mut self) {
|
fn previous(&mut self) {
|
||||||
if !self.items.is_empty() {
|
if !self.items.is_empty() {
|
||||||
self.state.select(Some(
|
self.state.select(Some(
|
||||||
self.state
|
self.state
|
||||||
@@ -600,6 +613,8 @@ impl LogsTz {
|
|||||||
pub struct Logs {
|
pub struct Logs {
|
||||||
lines: StatefulList<Text<'static>>,
|
lines: StatefulList<Text<'static>>,
|
||||||
tz: HashSet<LogsTz>,
|
tz: HashSet<LogsTz>,
|
||||||
|
search_results: Vec<usize>,
|
||||||
|
search_term: Option<String>,
|
||||||
offset: u16,
|
offset: u16,
|
||||||
max_log_len: usize,
|
max_log_len: usize,
|
||||||
adjusted_max_width: usize,
|
adjusted_max_width: usize,
|
||||||
@@ -614,6 +629,8 @@ impl Default for Logs {
|
|||||||
lines,
|
lines,
|
||||||
tz: HashSet::new(),
|
tz: HashSet::new(),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
search_term: None,
|
||||||
|
search_results: vec![],
|
||||||
adjusted_max_width: 0,
|
adjusted_max_width: 0,
|
||||||
adjust_max_width_text_len: 0,
|
adjust_max_width_text_len: 0,
|
||||||
max_log_len: 0,
|
max_log_len: 0,
|
||||||
@@ -621,12 +638,189 @@ impl Default for Logs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||||
|
pub enum LogsButton {
|
||||||
|
Both,
|
||||||
|
Next,
|
||||||
|
Previous,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
|
||||||
|
pub struct LogSearch {
|
||||||
|
pub term: Option<String>,
|
||||||
|
pub result: Option<String>,
|
||||||
|
pub buttons: Option<LogsButton>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// LogSearch is used in FrameData
|
||||||
|
impl From<&Logs> for LogSearch {
|
||||||
|
fn from(l: &Logs) -> Self {
|
||||||
|
let buttons = l.lines.state.selected().as_ref().and_then(|x| {
|
||||||
|
let show_next = l.search_results.iter().any(|n| n > x);
|
||||||
|
let show_previous = l.search_results.iter().any(|n| n < x);
|
||||||
|
match (show_next, show_previous) {
|
||||||
|
(true, true) => Some(LogsButton::Both),
|
||||||
|
(true, false) => Some(LogsButton::Next),
|
||||||
|
(false, true) => Some(LogsButton::Previous),
|
||||||
|
(false, false) => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
term: l.search_term.clone(),
|
||||||
|
result: l.get_search_result(),
|
||||||
|
buttons,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Logs {
|
impl Logs {
|
||||||
|
pub fn gen_log_search(&self) -> LogSearch {
|
||||||
|
LogSearch::from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll to the next or previous search result, accounts for when currently selected line isn't in the results vec
|
||||||
|
pub fn search_scroll(&mut self, sd: &ScrollDirection) -> Option<()> {
|
||||||
|
if let Some(current_selected) = self.lines.state.selected() {
|
||||||
|
if let Some(current_position) = self
|
||||||
|
.search_results
|
||||||
|
.iter()
|
||||||
|
.position(|i| i == ¤t_selected)
|
||||||
|
{
|
||||||
|
if let Some(new_index) = match sd {
|
||||||
|
ScrollDirection::Next => current_position.checked_add(1),
|
||||||
|
ScrollDirection::Previous => current_position.checked_sub(1),
|
||||||
|
} {
|
||||||
|
if let Some(f) = self.search_results.get(new_index) {
|
||||||
|
self.lines.state.select(Some(*f));
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let range = match sd {
|
||||||
|
ScrollDirection::Previous => (0..=current_selected).rev().collect::<Vec<_>>(),
|
||||||
|
ScrollDirection::Next => (current_selected
|
||||||
|
..=self
|
||||||
|
.search_results
|
||||||
|
.last()
|
||||||
|
.map_or_else(|| current_selected, |i| *i))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
};
|
||||||
|
for i in range {
|
||||||
|
if self.search_results.contains(&i) {
|
||||||
|
self.lines.state.select(Some(i));
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a string x/y, where y is total matches found, and x is current ordered selected line
|
||||||
|
/// WIll be padded by max chars of total matches
|
||||||
|
fn get_search_result(&self) -> Option<String> {
|
||||||
|
if self.search_results.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(self.lines.state.selected().map_or_else(
|
||||||
|
|| format!("{}", self.search_results.len()),
|
||||||
|
|current_index| {
|
||||||
|
self.search_results
|
||||||
|
.iter()
|
||||||
|
.position(|i| i == ¤t_index)
|
||||||
|
.map_or_else(
|
||||||
|
|| format!("{}", self.search_results.len()),
|
||||||
|
|index| {
|
||||||
|
let len = format!("{}", self.search_results.len());
|
||||||
|
let len_width = len.chars().count();
|
||||||
|
format!("{:>len_width$}/{len:>len_width$}", index + 1)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search through the logs for a matching string
|
||||||
|
pub fn search(&mut self, case_sensitive: bool) {
|
||||||
|
if let Some(search_term) = self.search_term.as_ref() {
|
||||||
|
let term = if case_sensitive {
|
||||||
|
search_term.to_owned()
|
||||||
|
} else {
|
||||||
|
search_term.to_lowercase()
|
||||||
|
};
|
||||||
|
self.search_results = self
|
||||||
|
.lines
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(index, a)| {
|
||||||
|
a.lines
|
||||||
|
.iter()
|
||||||
|
.any(|b| {
|
||||||
|
b.spans.iter().any(|c| {
|
||||||
|
if case_sensitive {
|
||||||
|
c.content.contains(&term)
|
||||||
|
} else {
|
||||||
|
c.content.to_lowercase().contains(&term)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then_some(index)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !self.search_results.is_empty() {
|
||||||
|
if let Some(currently_selected) = self.lines.state.selected()
|
||||||
|
&& !self.search_results.contains(¤tly_selected)
|
||||||
|
{
|
||||||
|
self.lines.state.select(self.search_results.last().copied());
|
||||||
|
self.offset = 0;
|
||||||
|
} else {
|
||||||
|
self.lines.state.select(self.search_results.last().copied());
|
||||||
|
self.offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.search_results.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a single char into the filter term
|
||||||
|
pub fn search_term_push(&mut self, c: char, case_sensitive: bool) {
|
||||||
|
if let Some(term) = self.search_term.as_mut() {
|
||||||
|
term.push(c);
|
||||||
|
} else {
|
||||||
|
self.search_term = Some(format!("{c}"));
|
||||||
|
}
|
||||||
|
self.search(case_sensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete the final char of the filter term
|
||||||
|
pub fn search_term_pop(&mut self, case_sensitive: bool) {
|
||||||
|
if let Some(term) = self.search_term.as_mut() {
|
||||||
|
term.pop();
|
||||||
|
if term.is_empty() {
|
||||||
|
self.search_term = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.search(case_sensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the filter completely
|
||||||
|
pub fn search_term_clear(&mut self) {
|
||||||
|
self.search_term = None;
|
||||||
|
self.search_results.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// Only allow a new log line to be inserted if the log timestamp isn't in the tz HashSet
|
/// Only allow a new log line to be inserted if the log timestamp isn't in the tz HashSet
|
||||||
pub fn insert(&mut self, line: Text<'static>, tz: LogsTz) {
|
pub fn insert(&mut self, line: Text<'static>, tz: LogsTz, case_sensitive: bool) {
|
||||||
if self.tz.insert(tz) {
|
if self.tz.insert(tz) {
|
||||||
self.max_log_len = self.max_log_len.max(line.width());
|
self.max_log_len = self.max_log_len.max(line.width());
|
||||||
self.lines.items.push(line);
|
self.lines.items.push(line);
|
||||||
|
// Maybe - Ideally we'd re-render here
|
||||||
|
if self.search_term.is_some() {
|
||||||
|
self.search(case_sensitive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,21 +942,27 @@ impl Logs {
|
|||||||
self.offset = self.offset.saturating_sub(1);
|
self.offset = self.offset.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scroll lines down by one
|
||||||
pub fn next(&mut self) {
|
pub fn next(&mut self) {
|
||||||
self.lines.next();
|
self.lines.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scroll lines up by one
|
||||||
pub fn previous(&mut self) {
|
pub fn previous(&mut self) {
|
||||||
self.lines.previous();
|
self.lines.previous();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Go to the end of the lines
|
||||||
pub fn end(&mut self) {
|
pub fn end(&mut self) {
|
||||||
self.lines.end();
|
self.lines.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Go to the start of the lines
|
||||||
pub fn start(&mut self) {
|
pub fn start(&mut self) {
|
||||||
self.lines.start();
|
self.lines.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get total number of log lines
|
||||||
pub const fn len(&self) -> usize {
|
pub const fn len(&self) -> usize {
|
||||||
self.lines.items.len()
|
self.lines.items.len()
|
||||||
}
|
}
|
||||||
@@ -938,7 +1138,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{ContainerImage, Logs, LogsTz, RunningState},
|
app_data::{ContainerImage, LogSearch, Logs, LogsTz, RunningState},
|
||||||
ui::log_sanitizer,
|
ui::log_sanitizer,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1075,9 +1275,9 @@ mod tests {
|
|||||||
let mut logs = Logs::default();
|
let mut logs = Logs::default();
|
||||||
let line = log_sanitizer::remove_ansi(input);
|
let line = log_sanitizer::remove_ansi(input);
|
||||||
|
|
||||||
logs.insert(Text::from(line.clone()), tz.clone());
|
logs.insert(Text::from(line.clone()), tz.clone(), true);
|
||||||
logs.insert(Text::from(line.clone()), tz.clone());
|
logs.insert(Text::from(line.clone()), tz.clone(), true);
|
||||||
logs.insert(Text::from(line), tz);
|
logs.insert(Text::from(line), tz, true);
|
||||||
|
|
||||||
assert_eq!(logs.lines.items.len(), 1);
|
assert_eq!(logs.lines.items.len(), 1);
|
||||||
|
|
||||||
@@ -1085,9 +1285,9 @@ mod tests {
|
|||||||
let (tz, _) = LogsTz::splitter(input);
|
let (tz, _) = LogsTz::splitter(input);
|
||||||
let line = log_sanitizer::remove_ansi(input);
|
let line = log_sanitizer::remove_ansi(input);
|
||||||
|
|
||||||
logs.insert(Text::from(line.clone()), tz.clone());
|
logs.insert(Text::from(line.clone()), tz.clone(), true);
|
||||||
logs.insert(Text::from(line.clone()), tz.clone());
|
logs.insert(Text::from(line.clone()), tz.clone(), true);
|
||||||
logs.insert(Text::from(line), tz);
|
logs.insert(Text::from(line), tz, true);
|
||||||
|
|
||||||
assert_eq!(logs.lines.items.len(), 2);
|
assert_eq!(logs.lines.items.len(), 2);
|
||||||
}
|
}
|
||||||
@@ -1150,15 +1350,15 @@ mod tests {
|
|||||||
|
|
||||||
let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned();
|
let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned();
|
||||||
let (tz, _) = LogsTz::splitter(&input);
|
let (tz, _) = LogsTz::splitter(&input);
|
||||||
logs.insert(Text::from(input), tz);
|
logs.insert(Text::from(input), tz, true);
|
||||||
|
|
||||||
let input = "2023-01-14T19:13:31.783138328Z Hello world some line".to_owned();
|
let input = "2023-01-14T19:13:31.783138328Z Hello world some line".to_owned();
|
||||||
let (tz, _) = LogsTz::splitter(&input);
|
let (tz, _) = LogsTz::splitter(&input);
|
||||||
logs.insert(Text::from(input), tz);
|
logs.insert(Text::from(input), tz, true);
|
||||||
|
|
||||||
let input = "2023-01-14T19:13:32.783138328Z Hello world".to_owned();
|
let input = "2023-01-14T19:13:32.783138328Z Hello world".to_owned();
|
||||||
let (tz, _) = LogsTz::splitter(&input);
|
let (tz, _) = LogsTz::splitter(&input);
|
||||||
logs.insert(Text::from(input), tz);
|
logs.insert(Text::from(input), tz, true);
|
||||||
|
|
||||||
logs.offset = 43;
|
logs.offset = 43;
|
||||||
let result = logs.get_visible_logs(
|
let result = logs.get_visible_logs(
|
||||||
@@ -1188,14 +1388,14 @@ mod tests {
|
|||||||
|
|
||||||
let input = "short".to_owned();
|
let input = "short".to_owned();
|
||||||
let (tz, _) = LogsTz::splitter(&input);
|
let (tz, _) = LogsTz::splitter(&input);
|
||||||
logs.insert(Text::from(input), tz);
|
logs.insert(Text::from(input), tz, true);
|
||||||
|
|
||||||
let result = logs.get_scroll_title(10);
|
let result = logs.get_scroll_title(10);
|
||||||
assert!(result.is_none());
|
assert!(result.is_none());
|
||||||
|
|
||||||
let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned();
|
let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned();
|
||||||
let (tz, _) = LogsTz::splitter(&input);
|
let (tz, _) = LogsTz::splitter(&input);
|
||||||
logs.insert(Text::from(input), tz);
|
logs.insert(Text::from(input), tz, true);
|
||||||
|
|
||||||
let result = logs.get_scroll_title(10);
|
let result = logs.get_scroll_title(10);
|
||||||
assert_eq!(result, Some(" 0/51 → ".to_owned()));
|
assert_eq!(result, Some(" 0/51 → ".to_owned()));
|
||||||
@@ -1211,4 +1411,119 @@ mod tests {
|
|||||||
let result = logs.get_scroll_title(10);
|
let result = logs.get_scroll_title(10);
|
||||||
assert_eq!(result, Some(" ← 51/51 ".to_owned()));
|
assert_eq!(result, Some(" ← 51/51 ".to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Test the log search
|
||||||
|
fn test_logsearch() {
|
||||||
|
let mut logs = Logs::default();
|
||||||
|
|
||||||
|
for i in 1..=10 {
|
||||||
|
let input = if i % 2 == 0 {
|
||||||
|
format!("{i}, hello world some long line {i}")
|
||||||
|
} else {
|
||||||
|
format!("{i}, Hello world some long line {i}")
|
||||||
|
};
|
||||||
|
let (tz, _) = LogsTz::splitter(&input);
|
||||||
|
logs.insert(Text::from(input), tz, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
logs.search_term_push('H', true);
|
||||||
|
assert_eq!(logs.search_results, [0, 2, 4, 6, 8]);
|
||||||
|
logs.search_term_clear();
|
||||||
|
logs.search_term_push('H', false);
|
||||||
|
assert_eq!(logs.search_results, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Test the LogSearch::From() methods
|
||||||
|
fn test_logsearch_from() {
|
||||||
|
let mut logs = Logs::default();
|
||||||
|
|
||||||
|
for i in 1..=10 {
|
||||||
|
let input = format!("{i}, Hello world some long line {i}");
|
||||||
|
let (tz, _) = LogsTz::splitter(&input);
|
||||||
|
logs.insert(Text::from(input), tz, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let log_search = LogSearch::from(&logs);
|
||||||
|
assert_eq!(
|
||||||
|
log_search,
|
||||||
|
LogSearch {
|
||||||
|
term: None,
|
||||||
|
result: None,
|
||||||
|
buttons: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
logs.search_term_push('H', true);
|
||||||
|
let log_search = LogSearch::from(&logs);
|
||||||
|
assert_eq!(
|
||||||
|
log_search,
|
||||||
|
LogSearch {
|
||||||
|
term: Some("H".to_owned()),
|
||||||
|
result: Some("10/10".to_owned()),
|
||||||
|
buttons: Some(crate::app_data::LogsButton::Previous)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
logs.previous();
|
||||||
|
|
||||||
|
let log_search = LogSearch::from(&logs);
|
||||||
|
assert_eq!(
|
||||||
|
log_search,
|
||||||
|
LogSearch {
|
||||||
|
term: Some("H".to_owned()),
|
||||||
|
result: Some(" 9/10".to_owned()),
|
||||||
|
buttons: Some(crate::app_data::LogsButton::Both)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
logs.start();
|
||||||
|
|
||||||
|
let log_search = LogSearch::from(&logs);
|
||||||
|
assert_eq!(
|
||||||
|
log_search,
|
||||||
|
LogSearch {
|
||||||
|
term: Some("H".to_owned()),
|
||||||
|
result: Some(" 1/10".to_owned()),
|
||||||
|
buttons: Some(crate::app_data::LogsButton::Next)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
logs.search_term_push('H', true);
|
||||||
|
let log_search = LogSearch::from(&logs);
|
||||||
|
assert_eq!(
|
||||||
|
log_search,
|
||||||
|
LogSearch {
|
||||||
|
term: Some("HH".to_owned()),
|
||||||
|
result: None,
|
||||||
|
buttons: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
logs.search_term_clear();
|
||||||
|
logs.search_term_push('2', true);
|
||||||
|
let log_search = LogSearch::from(&logs);
|
||||||
|
assert_eq!(logs.lines.state.selected(), Some(1));
|
||||||
|
assert_eq!(
|
||||||
|
log_search,
|
||||||
|
LogSearch {
|
||||||
|
term: Some("2".to_owned()),
|
||||||
|
result: Some("1/1".to_owned()),
|
||||||
|
buttons: None
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
logs.next();
|
||||||
|
|
||||||
|
let log_search = LogSearch::from(&logs);
|
||||||
|
assert_eq!(
|
||||||
|
log_search,
|
||||||
|
LogSearch {
|
||||||
|
term: Some("2".to_owned()),
|
||||||
|
result: Some("1".to_owned()),
|
||||||
|
buttons: Some(crate::app_data::LogsButton::Previous)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+79
-58
@@ -171,6 +171,19 @@ impl AppData {
|
|||||||
(self.filter.by, self.filter.term.as_ref())
|
(self.filter.by, self.filter.term.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn log_search_scroll(&mut self, np: &ScrollDirection) {
|
||||||
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
|
if i.logs.search_scroll(np).is_some() {
|
||||||
|
self.rerender.update_draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_log_search(&self) -> Option<LogSearch> {
|
||||||
|
self.get_selected_container()
|
||||||
|
.map(|i| i.logs.gen_log_search())
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if a given container can be inserted into the "visible" list, based on current filter term and filter_by
|
/// Check if a given container can be inserted into the "visible" list, based on current filter term and filter_by
|
||||||
fn can_insert(&self, container: &ContainerItem) -> bool {
|
fn can_insert(&self, container: &ContainerItem) -> bool {
|
||||||
self.filter.term.as_ref().is_none_or(|term| {
|
self.filter.term.as_ref().is_none_or(|term| {
|
||||||
@@ -224,6 +237,31 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn logs_search_clear(&mut self) {
|
||||||
|
if let Some(selected_container) = self.get_mut_selected_container() {
|
||||||
|
selected_container.logs.search_term_clear();
|
||||||
|
self.rerender.update_draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a single char into the filter term
|
||||||
|
pub fn log_search_push(&mut self, c: char) {
|
||||||
|
let cs = self.config.log_search_case_sensitive;
|
||||||
|
if let Some(selected_container) = self.get_mut_selected_container() {
|
||||||
|
selected_container.logs.search_term_push(c, cs);
|
||||||
|
self.rerender.update_draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete the final char of the filter term
|
||||||
|
pub fn log_search_pop(&mut self) {
|
||||||
|
let cs = self.config.log_search_case_sensitive;
|
||||||
|
if let Some(selected_container) = self.get_mut_selected_container() {
|
||||||
|
selected_container.logs.search_term_pop(cs);
|
||||||
|
self.rerender.update_draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Re-filter the containers, used after the filter.by has been changed
|
/// Re-filter the containers, used after the filter.by has been changed
|
||||||
fn re_filter(&mut self) {
|
fn re_filter(&mut self) {
|
||||||
self.containers.items.append(&mut self.hidden_containers);
|
self.containers.items.append(&mut self.hidden_containers);
|
||||||
@@ -448,15 +486,8 @@ impl AppData {
|
|||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select the next container
|
pub fn containers_scroll(&mut self, scroll: &ScrollDirection) {
|
||||||
pub fn containers_next(&mut self) {
|
self.containers.scroll(scroll);
|
||||||
self.containers.next();
|
|
||||||
self.rerender.update_draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// select the previous container
|
|
||||||
pub fn containers_previous(&mut self) {
|
|
||||||
self.containers.previous();
|
|
||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,17 +607,10 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Change selected choice of docker commands of selected container
|
/// Change selected choice of docker commands of selected container
|
||||||
pub fn docker_controls_next(&mut self) {
|
pub fn docker_controls_scroll(&mut self, scroll: &ScrollDirection) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.docker_controls.next();
|
i.docker_controls.scroll(scroll);
|
||||||
self.rerender.update_draw();
|
// i.docker_controls.next();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change selected choice of docker commands of selected container
|
|
||||||
pub fn docker_controls_previous(&mut self) {
|
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
|
||||||
i.docker_controls.previous();
|
|
||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -643,34 +667,30 @@ impl AppData {
|
|||||||
.and_then(|i| i.logs.get_scroll_title(width))
|
.and_then(|i| i.logs.get_scroll_title(width))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increase the logs offset, basically moving an invisible cursor back
|
pub fn logs_horizontal_scroll(&mut self, sd: &ScrollDirection, width: u16) {
|
||||||
pub fn log_back(&mut self) {
|
match sd {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
ScrollDirection::Next => {
|
||||||
i.logs.back();
|
|
||||||
self.rerender.update_draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increase the logs offset, basically moving an invisible cursor forward
|
|
||||||
pub fn log_forward(&mut self, width: u16) {
|
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.logs.forward(width);
|
i.logs.forward(width);
|
||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ScrollDirection::Previous => {
|
||||||
/// select next selected log line
|
|
||||||
pub fn log_next(&mut self) {
|
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.logs.next();
|
i.logs.back();
|
||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// select previous selected log line
|
/// select next selected log line
|
||||||
pub fn log_previous(&mut self) {
|
pub fn log_scroll(&mut self, scroll: &ScrollDirection) {
|
||||||
if let Some(i) = self.get_mut_selected_container() {
|
if let Some(i) = self.get_mut_selected_container() {
|
||||||
i.logs.previous();
|
match scroll {
|
||||||
|
ScrollDirection::Next => i.logs.next(),
|
||||||
|
ScrollDirection::Previous => i.logs.previous(),
|
||||||
|
}
|
||||||
self.rerender.update_draw();
|
self.rerender.update_draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -857,7 +877,7 @@ impl AppData {
|
|||||||
// If removed container is currently selected, then change selected to previous
|
// If removed container is currently selected, then change selected to previous
|
||||||
// This will default to 0 in any edge cases
|
// This will default to 0 in any edge cases
|
||||||
if self.containers.state.selected().is_some() {
|
if self.containers.state.selected().is_some() {
|
||||||
self.containers.previous();
|
self.containers.scroll(&ScrollDirection::Previous);
|
||||||
}
|
}
|
||||||
// Check is some, else can cause out of bounds error, if containers get removed before a docker update
|
// Check is some, else can cause out of bounds error, if containers get removed before a docker update
|
||||||
if self.containers.items.get(index).is_some() {
|
if self.containers.items.get(index).is_some() {
|
||||||
@@ -959,6 +979,8 @@ impl AppData {
|
|||||||
let format = self.config.timestamp_format.clone();
|
let format = self.config.timestamp_format.clone();
|
||||||
let config_tz = self.config.timezone.clone();
|
let config_tz = self.config.timezone.clone();
|
||||||
|
|
||||||
|
let cs = self.config.log_search_case_sensitive;
|
||||||
|
|
||||||
let show_timestamp = self.config.show_timestamp;
|
let show_timestamp = self.config.show_timestamp;
|
||||||
|
|
||||||
if let Some(container) = self.get_any_container_by_id(id) {
|
if let Some(container) = self.get_any_container_by_id(id) {
|
||||||
@@ -985,7 +1007,7 @@ impl AppData {
|
|||||||
} else {
|
} else {
|
||||||
log_sanitizer::remove_ansi(&i)
|
log_sanitizer::remove_ansi(&i)
|
||||||
};
|
};
|
||||||
container.logs.insert(Text::from(lines), log_tz);
|
container.logs.insert(Text::from(lines), log_tz, cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the logs selected row for each container
|
// Set the logs selected row for each container
|
||||||
@@ -1422,7 +1444,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Calling previous when at start has no effect
|
// Calling previous when at start has no effect
|
||||||
app_data.containers_previous();
|
app_data.containers_scroll(&ScrollDirection::Previous);
|
||||||
let result = app_data.get_selected_container_id();
|
let result = app_data.get_selected_container_id();
|
||||||
assert_eq!(result, Some(ContainerId::from("1")));
|
assert_eq!(result, Some(ContainerId::from("1")));
|
||||||
let result = app_data.get_selected_container_id_state_name();
|
let result = app_data.get_selected_container_id_state_name();
|
||||||
@@ -1445,7 +1467,7 @@ mod tests {
|
|||||||
|
|
||||||
// Advance list state by 1
|
// Advance list state by 1
|
||||||
app_data.containers_start();
|
app_data.containers_start();
|
||||||
app_data.containers_next();
|
app_data.containers.scroll(&ScrollDirection::Next);
|
||||||
|
|
||||||
let result = app_data.get_container_state();
|
let result = app_data.get_container_state();
|
||||||
assert_eq!(result.selected(), Some(1));
|
assert_eq!(result.selected(), Some(1));
|
||||||
@@ -1489,7 +1511,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Calling previous when at end has no effect
|
// Calling previous when at end has no effect
|
||||||
app_data.containers_next();
|
app_data.containers.scroll(&ScrollDirection::Next);
|
||||||
let result = app_data.get_selected_container_id();
|
let result = app_data.get_selected_container_id();
|
||||||
assert_eq!(result, Some(ContainerId::from("3")));
|
assert_eq!(result, Some(ContainerId::from("3")));
|
||||||
let result = app_data.get_selected_container_id_state_name();
|
let result = app_data.get_selected_container_id_state_name();
|
||||||
@@ -1510,7 +1532,7 @@ mod tests {
|
|||||||
let mut app_data = gen_appdata(&containers);
|
let mut app_data = gen_appdata(&containers);
|
||||||
|
|
||||||
app_data.containers_end();
|
app_data.containers_end();
|
||||||
app_data.containers_previous();
|
app_data.containers.scroll(&ScrollDirection::Previous);
|
||||||
let result = app_data.get_container_state();
|
let result = app_data.get_container_state();
|
||||||
assert_eq!(result.selected(), Some(1));
|
assert_eq!(result.selected(), Some(1));
|
||||||
assert_eq!(result.offset(), 0);
|
assert_eq!(result.offset(), 0);
|
||||||
@@ -1526,7 +1548,7 @@ mod tests {
|
|||||||
assert_eq!(result, None);
|
assert_eq!(result, None);
|
||||||
|
|
||||||
app_data.containers.start();
|
app_data.containers.start();
|
||||||
app_data.containers.next();
|
app_data.containers.scroll(&ScrollDirection::Next);
|
||||||
|
|
||||||
let result = app_data.get_selected_container();
|
let result = app_data.get_selected_container();
|
||||||
assert_eq!(result, Some(&containers[1]));
|
assert_eq!(result, Some(&containers[1]));
|
||||||
@@ -1613,7 +1635,7 @@ mod tests {
|
|||||||
let mut app_data = gen_appdata(&containers);
|
let mut app_data = gen_appdata(&containers);
|
||||||
app_data.containers_start();
|
app_data.containers_start();
|
||||||
app_data.docker_controls_start();
|
app_data.docker_controls_start();
|
||||||
app_data.docker_controls_next();
|
app_data.docker_controls_scroll(&ScrollDirection::Next);
|
||||||
|
|
||||||
let result = app_data.selected_docker_controls();
|
let result = app_data.selected_docker_controls();
|
||||||
assert_eq!(result, Some(DockerCommand::Restart));
|
assert_eq!(result, Some(DockerCommand::Restart));
|
||||||
@@ -1631,7 +1653,7 @@ mod tests {
|
|||||||
assert_eq!(result, Some(DockerCommand::Delete));
|
assert_eq!(result, Some(DockerCommand::Delete));
|
||||||
|
|
||||||
// Next has no effect when at end
|
// Next has no effect when at end
|
||||||
app_data.docker_controls_next();
|
app_data.docker_controls_scroll(&ScrollDirection::Next);
|
||||||
let result = app_data.selected_docker_controls();
|
let result = app_data.selected_docker_controls();
|
||||||
assert_eq!(result, Some(DockerCommand::Delete));
|
assert_eq!(result, Some(DockerCommand::Delete));
|
||||||
}
|
}
|
||||||
@@ -1643,14 +1665,14 @@ mod tests {
|
|||||||
let mut app_data = gen_appdata(&containers);
|
let mut app_data = gen_appdata(&containers);
|
||||||
app_data.containers_start();
|
app_data.containers_start();
|
||||||
app_data.docker_controls_end();
|
app_data.docker_controls_end();
|
||||||
app_data.docker_controls_previous();
|
app_data.docker_controls_scroll(&ScrollDirection::Previous);
|
||||||
|
|
||||||
let result = app_data.selected_docker_controls();
|
let result = app_data.selected_docker_controls();
|
||||||
assert_eq!(result, Some(DockerCommand::Stop));
|
assert_eq!(result, Some(DockerCommand::Stop));
|
||||||
|
|
||||||
// previous has no effect when at start
|
// previous has no effect when at start
|
||||||
app_data.docker_controls_start();
|
app_data.docker_controls_start();
|
||||||
app_data.docker_controls_previous();
|
app_data.docker_controls_scroll(&ScrollDirection::Previous);
|
||||||
let result = app_data.selected_docker_controls();
|
let result = app_data.selected_docker_controls();
|
||||||
assert_eq!(result, Some(DockerCommand::Pause));
|
assert_eq!(result, Some(DockerCommand::Pause));
|
||||||
}
|
}
|
||||||
@@ -1914,7 +1936,7 @@ mod tests {
|
|||||||
assert_eq!(result, " 3/3 - container_1 - image_1");
|
assert_eq!(result, " 3/3 - container_1 - image_1");
|
||||||
|
|
||||||
// Change log state to no longer be at the end
|
// Change log state to no longer be at the end
|
||||||
app_data.log_previous();
|
app_data.log_scroll(&ScrollDirection::Previous);
|
||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 2/3 - container_1 - image_1");
|
assert_eq!(result, " 2/3 - container_1 - image_1");
|
||||||
}
|
}
|
||||||
@@ -1935,7 +1957,7 @@ mod tests {
|
|||||||
assert_eq!(result, " - container_1 - image_1");
|
assert_eq!(result, " - container_1 - image_1");
|
||||||
|
|
||||||
// change container
|
// change container
|
||||||
app_data.containers_next();
|
app_data.containers_scroll(&ScrollDirection::Next);
|
||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " - container_2 - image_2");
|
assert_eq!(result, " - container_2 - image_2");
|
||||||
|
|
||||||
@@ -1946,7 +1968,7 @@ mod tests {
|
|||||||
assert_eq!(result, " 3/3 - container_2 - image_2");
|
assert_eq!(result, " 3/3 - container_2 - image_2");
|
||||||
|
|
||||||
// Change log state to no longer be at the end
|
// Change log state to no longer be at the end
|
||||||
app_data.log_previous();
|
app_data.log_scroll(&ScrollDirection::Previous);
|
||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 2/3 - container_2 - image_2");
|
assert_eq!(result, " 2/3 - container_2 - image_2");
|
||||||
}
|
}
|
||||||
@@ -2053,8 +2075,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 1/3 - container_1 - image_1");
|
assert_eq!(result, " 1/3 - container_1 - image_1");
|
||||||
|
|
||||||
app_data.log_next();
|
app_data.log_scroll(&ScrollDirection::Next);
|
||||||
|
|
||||||
let result = app_data.get_log_state();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
assert_eq!(result.as_ref().unwrap().selected(), Some(1));
|
assert_eq!(result.as_ref().unwrap().selected(), Some(1));
|
||||||
@@ -2063,7 +2084,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 2/3 - container_1 - image_1");
|
assert_eq!(result, " 2/3 - container_1 - image_1");
|
||||||
|
|
||||||
app_data.log_next();
|
app_data.log_scroll(&ScrollDirection::Next);
|
||||||
let result = app_data.get_log_state();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
|
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
|
||||||
@@ -2071,7 +2092,7 @@ mod tests {
|
|||||||
|
|
||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 3/3 - container_1 - image_1");
|
assert_eq!(result, " 3/3 - container_1 - image_1");
|
||||||
app_data.log_next();
|
app_data.log_scroll(&ScrollDirection::Next);
|
||||||
|
|
||||||
let result = app_data.get_log_state();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
@@ -2102,7 +2123,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 3/3 - container_1 - image_1");
|
assert_eq!(result, " 3/3 - container_1 - image_1");
|
||||||
|
|
||||||
app_data.log_previous();
|
app_data.log_scroll(&ScrollDirection::Previous);
|
||||||
|
|
||||||
let result = app_data.get_log_state();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
@@ -2111,7 +2132,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 2/3 - container_1 - image_1");
|
assert_eq!(result, " 2/3 - container_1 - image_1");
|
||||||
|
|
||||||
app_data.log_previous();
|
app_data.log_scroll(&ScrollDirection::Previous);
|
||||||
let result = app_data.get_log_state();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
||||||
@@ -2119,7 +2140,7 @@ mod tests {
|
|||||||
let result = app_data.get_log_title();
|
let result = app_data.get_log_title();
|
||||||
assert_eq!(result, " 1/3 - container_1 - image_1");
|
assert_eq!(result, " 1/3 - container_1 - image_1");
|
||||||
|
|
||||||
app_data.log_previous();
|
app_data.log_scroll(&ScrollDirection::Previous);
|
||||||
let result = app_data.get_log_state();
|
let result = app_data.get_log_state();
|
||||||
assert!(result.is_some());
|
assert!(result.is_some());
|
||||||
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
||||||
@@ -2413,7 +2434,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..=500 {
|
for _ in 0..=500 {
|
||||||
app_data.log_next();
|
app_data.log_scroll(&ScrollDirection::Next);
|
||||||
}
|
}
|
||||||
let result = app_data.get_logs(
|
let result = app_data.get_logs(
|
||||||
Size {
|
Size {
|
||||||
|
|||||||
@@ -89,6 +89,24 @@ impl From<Option<ConfigColors>> for AppColors {
|
|||||||
Self::map_color(fc.text.as_deref(), &mut app_colors.filter.text);
|
Self::map_color(fc.text.as_deref(), &mut app_colors.filter.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log search
|
||||||
|
if let Some(ls) = config_colors.log_search {
|
||||||
|
Self::map_color(
|
||||||
|
ls.background.as_deref(),
|
||||||
|
&mut app_colors.log_search.background,
|
||||||
|
);
|
||||||
|
Self::map_color(
|
||||||
|
ls.highlight.as_deref(),
|
||||||
|
&mut app_colors.log_search.highlight,
|
||||||
|
);
|
||||||
|
|
||||||
|
Self::map_color(
|
||||||
|
ls.button_text.as_deref(),
|
||||||
|
&mut app_colors.log_search.button_text,
|
||||||
|
);
|
||||||
|
Self::map_color(ls.text.as_deref(), &mut app_colors.log_search.text);
|
||||||
|
}
|
||||||
|
|
||||||
// Help Popup
|
// Help Popup
|
||||||
if let Some(hp) = config_colors.popup_help {
|
if let Some(hp) = config_colors.popup_help {
|
||||||
Self::map_color(
|
Self::map_color(
|
||||||
@@ -238,6 +256,7 @@ optional_config_struct!(
|
|||||||
ConfigContainers, background, icon, text, text_rx, text_tx;
|
ConfigContainers, background, icon, text, text_rx, text_tx;
|
||||||
ConfigContainerState, background, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown;
|
ConfigContainerState, background, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown;
|
||||||
ConfigFilter, background, text, selected_filter_background, selected_filter_text, highlight;
|
ConfigFilter, background, text, selected_filter_background, selected_filter_text, highlight;
|
||||||
|
ConfigLogSearch, background, text, button_text, highlight;
|
||||||
ConfigHeadersBar, background, loading_spinner, text, text_selected;
|
ConfigHeadersBar, background, loading_spinner, text, text_selected;
|
||||||
ConfigLogs, background, text
|
ConfigLogs, background, text
|
||||||
);
|
);
|
||||||
@@ -251,6 +270,7 @@ config_struct!(
|
|||||||
Containers, background, icon, text, text_rx, text_tx;
|
Containers, background, icon, text, text_rx, text_tx;
|
||||||
ContainerState, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown;
|
ContainerState, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown;
|
||||||
Filter, background, text, selected_filter_background, selected_filter_text, highlight;
|
Filter, background, text, selected_filter_background, selected_filter_text, highlight;
|
||||||
|
LogSearch, background, text, button_text, highlight;
|
||||||
HeadersBar, background, text_selected, loading_spinner, text;
|
HeadersBar, background, text_selected, loading_spinner, text;
|
||||||
Logs, background, text;
|
Logs, background, text;
|
||||||
PopupDelete, background, text, text_highlight;
|
PopupDelete, background, text, text_highlight;
|
||||||
@@ -269,6 +289,7 @@ pub struct ConfigColors {
|
|||||||
container_state: Option<ConfigContainerState>,
|
container_state: Option<ConfigContainerState>,
|
||||||
containers: Option<ConfigContainers>,
|
containers: Option<ConfigContainers>,
|
||||||
filter: Option<ConfigFilter>,
|
filter: Option<ConfigFilter>,
|
||||||
|
log_search: Option<ConfigLogSearch>,
|
||||||
headers_bar: Option<ConfigHeadersBar>,
|
headers_bar: Option<ConfigHeadersBar>,
|
||||||
logs: Option<ConfigLogs>,
|
logs: Option<ConfigLogs>,
|
||||||
popup_delete: Option<ConfigBackgroundTextHighlight>,
|
popup_delete: Option<ConfigBackgroundTextHighlight>,
|
||||||
@@ -397,6 +418,18 @@ impl Filter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Default colours for the log search
|
||||||
|
impl LogSearch {
|
||||||
|
const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
background: Color::Reset,
|
||||||
|
highlight: Color::Magenta,
|
||||||
|
button_text: Color::Black,
|
||||||
|
text: Color::Gray,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Default colours for the logs panel, only applied if color_logs is false
|
/// Default colours for the logs panel, only applied if color_logs is false
|
||||||
impl Logs {
|
impl Logs {
|
||||||
const fn new() -> Self {
|
const fn new() -> Self {
|
||||||
@@ -458,6 +491,7 @@ pub struct AppColors {
|
|||||||
pub commands: Commands,
|
pub commands: Commands,
|
||||||
pub container_state: ContainerState,
|
pub container_state: ContainerState,
|
||||||
pub containers: Containers,
|
pub containers: Containers,
|
||||||
|
pub log_search: LogSearch,
|
||||||
pub filter: Filter,
|
pub filter: Filter,
|
||||||
pub headers_bar: HeadersBar,
|
pub headers_bar: HeadersBar,
|
||||||
pub logs: Logs,
|
pub logs: Logs,
|
||||||
@@ -477,6 +511,7 @@ impl AppColors {
|
|||||||
commands: Commands::new(),
|
commands: Commands::new(),
|
||||||
container_state: ContainerState::new(),
|
container_state: ContainerState::new(),
|
||||||
containers: Containers::new(),
|
containers: Containers::new(),
|
||||||
|
log_search: LogSearch::new(),
|
||||||
filter: Filter::new(),
|
filter: Filter::new(),
|
||||||
headers_bar: HeadersBar::new(),
|
headers_bar: HeadersBar::new(),
|
||||||
logs: Logs::new(),
|
logs: Logs::new(),
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ use_cli = false
|
|||||||
# Show the logs section - this can be changed during operation with the log_section_toggle key
|
# Show the logs section - this can be changed during operation with the log_section_toggle key
|
||||||
show_logs = true
|
show_logs = true
|
||||||
|
|
||||||
|
# Use case-sensitive matching for logs
|
||||||
|
log_search_case_sensitive = true
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Custom Keymap #
|
# Custom Keymap #
|
||||||
#################
|
#################
|
||||||
@@ -72,6 +75,10 @@ delete_confirm = ["y"]
|
|||||||
exec = ["e"]
|
exec = ["e"]
|
||||||
# Enter filter mode
|
# Enter filter mode
|
||||||
filter_mode = ["/", "F1"]
|
filter_mode = ["/", "F1"]
|
||||||
|
|
||||||
|
# Enter log search mode
|
||||||
|
log_search_mode = ["#"]
|
||||||
|
|
||||||
# Quit at anytime
|
# Quit at anytime
|
||||||
quit = ["q"]
|
quit = ["q"]
|
||||||
# Save logs of selected container to file on disk
|
# Save logs of selected container to file on disk
|
||||||
@@ -115,6 +122,9 @@ log_section_height_decrease = ["-"]
|
|||||||
log_section_height_increase = ["+"]
|
log_section_height_increase = ["+"]
|
||||||
# Toggle visibility of the log section
|
# Toggle visibility of the log section
|
||||||
log_section_toggle = ["\\"]
|
log_section_toggle = ["\\"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Force a complete clear & redraw of the screen
|
# Force a complete clear & redraw of the screen
|
||||||
force_redraw = ["f"]
|
force_redraw = ["f"]
|
||||||
|
|
||||||
@@ -192,6 +202,17 @@ selected_filter_text = "black"
|
|||||||
highlight = "magenta"
|
highlight = "magenta"
|
||||||
|
|
||||||
|
|
||||||
|
# The log search panel
|
||||||
|
[colors.log_search]
|
||||||
|
# Background color of panel
|
||||||
|
background = "reset"
|
||||||
|
# color of text
|
||||||
|
text = "gray"
|
||||||
|
# text color of the buttons text
|
||||||
|
button_text = "black"
|
||||||
|
# Highlighted text color
|
||||||
|
highlight = "magenta"
|
||||||
|
|
||||||
# The color the of Docker commands available for each container
|
# The color the of Docker commands available for each container
|
||||||
[colors.commands]
|
[colors.commands]
|
||||||
# Background color of panel
|
# Background color of panel
|
||||||
|
|||||||
+42
-37
@@ -37,16 +37,17 @@ macro_rules! config_struct {
|
|||||||
optional_config_struct!(
|
optional_config_struct!(
|
||||||
ConfigKeymap,
|
ConfigKeymap,
|
||||||
clear,
|
clear,
|
||||||
force_redraw,
|
|
||||||
delete_deny,
|
|
||||||
delete_confirm,
|
delete_confirm,
|
||||||
|
delete_deny,
|
||||||
exec,
|
exec,
|
||||||
filter_mode,
|
filter_mode,
|
||||||
log_section_height_increase,
|
force_redraw,
|
||||||
log_section_height_decrease,
|
|
||||||
log_section_toggle,
|
|
||||||
log_scroll_forward,
|
|
||||||
log_scroll_back,
|
log_scroll_back,
|
||||||
|
log_scroll_forward,
|
||||||
|
log_search_mode,
|
||||||
|
log_section_height_decrease,
|
||||||
|
log_section_height_increase,
|
||||||
|
log_section_toggle,
|
||||||
quit,
|
quit,
|
||||||
save_logs,
|
save_logs,
|
||||||
scroll_down,
|
scroll_down,
|
||||||
@@ -55,14 +56,14 @@ optional_config_struct!(
|
|||||||
scroll_up,
|
scroll_up,
|
||||||
select_next_panel,
|
select_next_panel,
|
||||||
select_previous_panel,
|
select_previous_panel,
|
||||||
sort_by_name,
|
|
||||||
sort_by_state,
|
|
||||||
sort_by_status,
|
|
||||||
sort_by_cpu,
|
sort_by_cpu,
|
||||||
sort_by_memory,
|
|
||||||
sort_by_id,
|
sort_by_id,
|
||||||
sort_by_image,
|
sort_by_image,
|
||||||
|
sort_by_memory,
|
||||||
|
sort_by_name,
|
||||||
sort_by_rx,
|
sort_by_rx,
|
||||||
|
sort_by_state,
|
||||||
|
sort_by_status,
|
||||||
sort_by_tx,
|
sort_by_tx,
|
||||||
sort_reset,
|
sort_reset,
|
||||||
toggle_help,
|
toggle_help,
|
||||||
@@ -72,16 +73,17 @@ optional_config_struct!(
|
|||||||
config_struct!(
|
config_struct!(
|
||||||
Keymap,
|
Keymap,
|
||||||
clear,
|
clear,
|
||||||
delete_deny,
|
|
||||||
delete_confirm,
|
delete_confirm,
|
||||||
|
delete_deny,
|
||||||
exec,
|
exec,
|
||||||
filter_mode,
|
filter_mode,
|
||||||
force_redraw,
|
force_redraw,
|
||||||
log_section_height_increase,
|
|
||||||
log_section_height_decrease,
|
|
||||||
log_section_toggle,
|
|
||||||
log_scroll_forward,
|
|
||||||
log_scroll_back,
|
log_scroll_back,
|
||||||
|
log_scroll_forward,
|
||||||
|
log_search_mode,
|
||||||
|
log_section_height_decrease,
|
||||||
|
log_section_height_increase,
|
||||||
|
log_section_toggle,
|
||||||
quit,
|
quit,
|
||||||
save_logs,
|
save_logs,
|
||||||
scroll_down,
|
scroll_down,
|
||||||
@@ -90,14 +92,14 @@ config_struct!(
|
|||||||
scroll_up,
|
scroll_up,
|
||||||
select_next_panel,
|
select_next_panel,
|
||||||
select_previous_panel,
|
select_previous_panel,
|
||||||
sort_by_name,
|
|
||||||
sort_by_state,
|
|
||||||
sort_by_status,
|
|
||||||
sort_by_cpu,
|
sort_by_cpu,
|
||||||
sort_by_memory,
|
|
||||||
sort_by_id,
|
sort_by_id,
|
||||||
sort_by_image,
|
sort_by_image,
|
||||||
|
sort_by_memory,
|
||||||
|
sort_by_name,
|
||||||
sort_by_rx,
|
sort_by_rx,
|
||||||
|
sort_by_state,
|
||||||
|
sort_by_status,
|
||||||
sort_by_tx,
|
sort_by_tx,
|
||||||
sort_reset,
|
sort_reset,
|
||||||
toggle_help,
|
toggle_help,
|
||||||
@@ -113,19 +115,18 @@ impl Keymap {
|
|||||||
exec: (KeyCode::Char('e'), None),
|
exec: (KeyCode::Char('e'), None),
|
||||||
filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))),
|
filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))),
|
||||||
force_redraw: (KeyCode::Char('f'), None),
|
force_redraw: (KeyCode::Char('f'), None),
|
||||||
|
log_scroll_back: (KeyCode::Left, None),
|
||||||
|
log_scroll_forward: (KeyCode::Right, None),
|
||||||
|
log_search_mode: (KeyCode::Char('#'), None),
|
||||||
log_section_height_decrease: (KeyCode::Char('-'), None),
|
log_section_height_decrease: (KeyCode::Char('-'), None),
|
||||||
log_section_height_increase: (KeyCode::Char('='), None),
|
log_section_height_increase: (KeyCode::Char('='), None),
|
||||||
log_section_toggle: (KeyCode::Char('\\'), None),
|
log_section_toggle: (KeyCode::Char('\\'), None),
|
||||||
log_scroll_back: (KeyCode::Left, None),
|
|
||||||
log_scroll_forward: (KeyCode::Right, None),
|
|
||||||
quit: (KeyCode::Char('q'), None),
|
quit: (KeyCode::Char('q'), None),
|
||||||
save_logs: (KeyCode::Char('s'), None),
|
save_logs: (KeyCode::Char('s'), None),
|
||||||
// TODO rename in next release
|
|
||||||
scroll_down: (KeyCode::Down, Some(KeyCode::Char('j'))),
|
scroll_down: (KeyCode::Down, Some(KeyCode::Char('j'))),
|
||||||
scroll_end: (KeyCode::End, None),
|
scroll_end: (KeyCode::End, None),
|
||||||
scroll_start: (KeyCode::Home, None),
|
|
||||||
scroll_many: KeyModifiers::CONTROL,
|
scroll_many: KeyModifiers::CONTROL,
|
||||||
// TODO rename in next release
|
scroll_start: (KeyCode::Home, None),
|
||||||
scroll_up: (KeyCode::Up, Some(KeyCode::Char('k'))),
|
scroll_up: (KeyCode::Up, Some(KeyCode::Char('k'))),
|
||||||
select_next_panel: (KeyCode::Tab, None),
|
select_next_panel: (KeyCode::Tab, None),
|
||||||
select_previous_panel: (KeyCode::BackTab, None),
|
select_previous_panel: (KeyCode::BackTab, None),
|
||||||
@@ -204,6 +205,7 @@ impl From<Option<ConfigKeymap>> for Keymap {
|
|||||||
update_keymap(ck.scroll_end, &mut keymap.scroll_end, &mut clash);
|
update_keymap(ck.scroll_end, &mut keymap.scroll_end, &mut clash);
|
||||||
update_keymap(ck.scroll_start, &mut keymap.scroll_start, &mut clash);
|
update_keymap(ck.scroll_start, &mut keymap.scroll_start, &mut clash);
|
||||||
update_keymap(ck.scroll_up, &mut keymap.scroll_up, &mut clash);
|
update_keymap(ck.scroll_up, &mut keymap.scroll_up, &mut clash);
|
||||||
|
update_keymap(ck.log_search_mode, &mut keymap.log_search_mode, &mut clash);
|
||||||
update_keymap(
|
update_keymap(
|
||||||
ck.log_scroll_forward,
|
ck.log_scroll_forward,
|
||||||
&mut keymap.log_scroll_forward,
|
&mut keymap.log_scroll_forward,
|
||||||
@@ -394,6 +396,7 @@ mod tests {
|
|||||||
filter_mode: None,
|
filter_mode: None,
|
||||||
force_redraw: None,
|
force_redraw: None,
|
||||||
log_scroll_back: None,
|
log_scroll_back: None,
|
||||||
|
log_search_mode: None,
|
||||||
log_scroll_forward: None,
|
log_scroll_forward: None,
|
||||||
log_section_height_decrease: None,
|
log_section_height_decrease: None,
|
||||||
log_section_height_increase: None,
|
log_section_height_increase: None,
|
||||||
@@ -434,14 +437,15 @@ mod tests {
|
|||||||
let input = ConfigKeymap {
|
let input = ConfigKeymap {
|
||||||
clear: gen_v(("a", "b")),
|
clear: gen_v(("a", "b")),
|
||||||
delete_confirm: gen_v(("c", "d")),
|
delete_confirm: gen_v(("c", "d")),
|
||||||
delete_deny: gen_v(("e", "fd")),
|
delete_deny: gen_v(("e", "f")),
|
||||||
exec: gen_v(("g", "h")),
|
exec: gen_v(("g", "h")),
|
||||||
filter_mode: gen_v(("i", "j")),
|
filter_mode: gen_v(("i", "j")),
|
||||||
force_redraw: gen_v(("k", "l")),
|
force_redraw: gen_v(("k", "l")),
|
||||||
|
log_scroll_back: gen_v(("s", "t")),
|
||||||
|
log_scroll_forward: gen_v(("q", "r")),
|
||||||
|
log_search_mode: gen_v(("1", "2")),
|
||||||
log_section_height_decrease: gen_v(("m", "n")),
|
log_section_height_decrease: gen_v(("m", "n")),
|
||||||
log_section_height_increase: gen_v(("o", "p")),
|
log_section_height_increase: gen_v(("o", "p")),
|
||||||
log_scroll_forward: gen_v(("q", "r")),
|
|
||||||
log_scroll_back: gen_v(("s", "t")),
|
|
||||||
log_section_toggle: gen_v(("u", "v")),
|
log_section_toggle: gen_v(("u", "v")),
|
||||||
quit: gen_v(("w", "x")),
|
quit: gen_v(("w", "x")),
|
||||||
save_logs: gen_v(("y", "z")),
|
save_logs: gen_v(("y", "z")),
|
||||||
@@ -470,37 +474,38 @@ mod tests {
|
|||||||
|
|
||||||
let expected = Keymap {
|
let expected = Keymap {
|
||||||
clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))),
|
clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))),
|
||||||
delete_deny: (KeyCode::Char('e'), None),
|
|
||||||
delete_confirm: (KeyCode::Char('c'), Some(KeyCode::Char('d'))),
|
delete_confirm: (KeyCode::Char('c'), Some(KeyCode::Char('d'))),
|
||||||
|
delete_deny: (KeyCode::Char('e'), Some(KeyCode::Char('f'))),
|
||||||
exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))),
|
exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))),
|
||||||
filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
|
filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))),
|
||||||
force_redraw: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
|
force_redraw: (KeyCode::Char('k'), Some(KeyCode::Char('l'))),
|
||||||
log_section_height_increase: (KeyCode::Char('o'), Some(KeyCode::Char('p'))),
|
|
||||||
log_section_height_decrease: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
|
|
||||||
log_section_toggle: (KeyCode::Char('u'), Some(KeyCode::Char('v'))),
|
|
||||||
log_scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))),
|
|
||||||
log_scroll_back: (KeyCode::Char('s'), Some(KeyCode::Char('t'))),
|
log_scroll_back: (KeyCode::Char('s'), Some(KeyCode::Char('t'))),
|
||||||
|
log_scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))),
|
||||||
|
log_search_mode: (KeyCode::Char('1'), Some(KeyCode::Char('2'))),
|
||||||
|
log_section_height_decrease: (KeyCode::Char('m'), Some(KeyCode::Char('n'))),
|
||||||
|
log_section_height_increase: (KeyCode::Char('o'), Some(KeyCode::Char('p'))),
|
||||||
|
log_section_toggle: (KeyCode::Char('u'), Some(KeyCode::Char('v'))),
|
||||||
quit: (KeyCode::Char('w'), Some(KeyCode::Char('x'))),
|
quit: (KeyCode::Char('w'), Some(KeyCode::Char('x'))),
|
||||||
save_logs: (KeyCode::Char('y'), Some(KeyCode::Char('z'))),
|
save_logs: (KeyCode::Char('y'), Some(KeyCode::Char('z'))),
|
||||||
scroll_down: (KeyCode::Char('3'), Some(KeyCode::Char('4'))),
|
scroll_down: (KeyCode::Char('3'), Some(KeyCode::Char('4'))),
|
||||||
scroll_end: (KeyCode::Char('5'), Some(KeyCode::Char('6'))),
|
scroll_end: (KeyCode::Char('5'), Some(KeyCode::Char('6'))),
|
||||||
|
scroll_many: KeyModifiers::ALT,
|
||||||
scroll_start: (KeyCode::Char('7'), Some(KeyCode::Char('8'))),
|
scroll_start: (KeyCode::Char('7'), Some(KeyCode::Char('8'))),
|
||||||
scroll_up: (KeyCode::F(1), Some(KeyCode::F(2))),
|
scroll_up: (KeyCode::F(1), Some(KeyCode::F(2))),
|
||||||
select_next_panel: (KeyCode::F(3), Some(KeyCode::F(4))),
|
select_next_panel: (KeyCode::F(3), Some(KeyCode::F(4))),
|
||||||
select_previous_panel: (KeyCode::F(5), Some(KeyCode::F(6))),
|
select_previous_panel: (KeyCode::F(5), Some(KeyCode::F(6))),
|
||||||
sort_by_name: (KeyCode::Up, Some(KeyCode::Down)),
|
|
||||||
sort_by_state: (KeyCode::Char('['), Some(KeyCode::Char(']'))),
|
|
||||||
sort_by_status: (KeyCode::Tab, None),
|
|
||||||
sort_by_cpu: (KeyCode::F(7), Some(KeyCode::F(8))),
|
sort_by_cpu: (KeyCode::F(7), Some(KeyCode::F(8))),
|
||||||
sort_by_memory: (KeyCode::Home, Some(KeyCode::End)),
|
|
||||||
sort_by_id: (KeyCode::F(9), Some(KeyCode::F(10))),
|
sort_by_id: (KeyCode::F(9), Some(KeyCode::F(10))),
|
||||||
sort_by_image: (KeyCode::F(11), Some(KeyCode::F(12))),
|
sort_by_image: (KeyCode::F(11), Some(KeyCode::F(12))),
|
||||||
|
sort_by_memory: (KeyCode::Home, Some(KeyCode::End)),
|
||||||
|
sort_by_name: (KeyCode::Up, Some(KeyCode::Down)),
|
||||||
sort_by_rx: (KeyCode::Left, Some(KeyCode::Right)),
|
sort_by_rx: (KeyCode::Left, Some(KeyCode::Right)),
|
||||||
|
sort_by_state: (KeyCode::Char('['), Some(KeyCode::Char(']'))),
|
||||||
|
sort_by_status: (KeyCode::Tab, None),
|
||||||
sort_by_tx: (KeyCode::PageDown, Some(KeyCode::PageUp)),
|
sort_by_tx: (KeyCode::PageDown, Some(KeyCode::PageUp)),
|
||||||
sort_reset: (KeyCode::Char(','), Some(KeyCode::Char('.'))),
|
sort_reset: (KeyCode::Char(','), Some(KeyCode::Char('.'))),
|
||||||
toggle_help: (KeyCode::Char('-'), Some(KeyCode::Char('='))),
|
toggle_help: (KeyCode::Char('-'), Some(KeyCode::Char('='))),
|
||||||
toggle_mouse_capture: (KeyCode::Char('\\'), Some(KeyCode::Char('/'))),
|
toggle_mouse_capture: (KeyCode::Char('\\'), Some(KeyCode::Char('/'))),
|
||||||
scroll_many: KeyModifiers::ALT,
|
|
||||||
};
|
};
|
||||||
assert_eq!(expected, result);
|
assert_eq!(expected, result);
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-6
@@ -13,6 +13,9 @@ pub use {color_parser::AppColors, keymap_parser::Keymap};
|
|||||||
mod parse_args;
|
mod parse_args;
|
||||||
mod parse_config_file;
|
mod parse_config_file;
|
||||||
|
|
||||||
|
// TODO use a global pub static oncelock for the config
|
||||||
|
// static CELL: OnceLock<usize> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
@@ -23,14 +26,15 @@ pub struct Config {
|
|||||||
pub host: Option<String>,
|
pub host: Option<String>,
|
||||||
pub in_container: bool,
|
pub in_container: bool,
|
||||||
pub keymap: Keymap,
|
pub keymap: Keymap,
|
||||||
|
pub log_search_case_sensitive: bool,
|
||||||
pub raw_logs: bool,
|
pub raw_logs: bool,
|
||||||
pub save_dir: Option<PathBuf>,
|
pub save_dir: Option<PathBuf>,
|
||||||
|
pub show_logs: bool,
|
||||||
pub show_self: bool,
|
pub show_self: bool,
|
||||||
pub show_std_err: bool,
|
pub show_std_err: bool,
|
||||||
pub show_timestamp: bool,
|
pub show_timestamp: bool,
|
||||||
pub timezone: Option<TimeZone>,
|
|
||||||
pub timestamp_format: String,
|
pub timestamp_format: String,
|
||||||
pub show_logs: bool,
|
pub timezone: Option<TimeZone>,
|
||||||
pub use_cli: bool,
|
pub use_cli: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,15 +48,16 @@ impl From<&Args> for Config {
|
|||||||
host: args.host.clone(),
|
host: args.host.clone(),
|
||||||
in_container: Self::check_if_in_container(),
|
in_container: Self::check_if_in_container(),
|
||||||
keymap: Keymap::new(),
|
keymap: Keymap::new(),
|
||||||
|
log_search_case_sensitive: true,
|
||||||
raw_logs: args.raw,
|
raw_logs: args.raw,
|
||||||
save_dir: Self::try_get_logs_dir(args.save_dir.as_ref()),
|
save_dir: Self::try_get_logs_dir(args.save_dir.as_ref()),
|
||||||
|
show_logs: true,
|
||||||
show_self: !args.show_self,
|
show_self: !args.show_self,
|
||||||
show_std_err: !args.no_std_err,
|
show_std_err: !args.no_std_err,
|
||||||
show_timestamp: !args.timestamp,
|
show_timestamp: !args.timestamp,
|
||||||
timezone: Self::parse_timezone(args.timezone.clone()),
|
|
||||||
timestamp_format: Self::parse_timestamp_format(None),
|
timestamp_format: Self::parse_timestamp_format(None),
|
||||||
|
timezone: Self::parse_timezone(args.timezone.clone()),
|
||||||
use_cli: args.use_cli,
|
use_cli: args.use_cli,
|
||||||
show_logs: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,15 +72,16 @@ impl From<ConfigFile> for Config {
|
|||||||
host: config_file.host,
|
host: config_file.host,
|
||||||
in_container: Self::check_if_in_container(),
|
in_container: Self::check_if_in_container(),
|
||||||
keymap: Keymap::from(config_file.keymap),
|
keymap: Keymap::from(config_file.keymap),
|
||||||
|
log_search_case_sensitive: config_file.log_search_case_sensitive.unwrap_or(true),
|
||||||
raw_logs: config_file.raw_logs.unwrap_or(false),
|
raw_logs: config_file.raw_logs.unwrap_or(false),
|
||||||
save_dir: Self::try_get_logs_dir(config_file.save_dir.as_ref()),
|
save_dir: Self::try_get_logs_dir(config_file.save_dir.as_ref()),
|
||||||
|
show_logs: config_file.show_logs.unwrap_or(true),
|
||||||
show_self: config_file.show_self.unwrap_or(false),
|
show_self: config_file.show_self.unwrap_or(false),
|
||||||
show_std_err: config_file.show_std_err.unwrap_or(true),
|
show_std_err: config_file.show_std_err.unwrap_or(true),
|
||||||
show_timestamp: config_file.show_timestamp.unwrap_or(true),
|
show_timestamp: config_file.show_timestamp.unwrap_or(true),
|
||||||
timezone: Self::parse_timezone(config_file.timezone),
|
|
||||||
timestamp_format: Self::parse_timestamp_format(config_file.timestamp_format),
|
timestamp_format: Self::parse_timestamp_format(config_file.timestamp_format),
|
||||||
|
timezone: Self::parse_timezone(config_file.timezone),
|
||||||
use_cli: config_file.use_cli.unwrap_or(false),
|
use_cli: config_file.use_cli.unwrap_or(false),
|
||||||
show_logs: config_file.show_logs.unwrap_or(true),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,15 +67,16 @@ pub struct ConfigFile {
|
|||||||
pub gui: Option<bool>,
|
pub gui: Option<bool>,
|
||||||
pub host: Option<String>,
|
pub host: Option<String>,
|
||||||
pub keymap: Option<ConfigKeymap>,
|
pub keymap: Option<ConfigKeymap>,
|
||||||
|
pub log_search_case_sensitive: Option<bool>,
|
||||||
pub raw_logs: Option<bool>,
|
pub raw_logs: Option<bool>,
|
||||||
pub save_dir: Option<String>,
|
pub save_dir: Option<String>,
|
||||||
|
pub show_logs: Option<bool>,
|
||||||
pub show_self: Option<bool>,
|
pub show_self: Option<bool>,
|
||||||
pub show_std_err: Option<bool>,
|
pub show_std_err: Option<bool>,
|
||||||
pub show_timestamp: Option<bool>,
|
pub show_timestamp: Option<bool>,
|
||||||
pub timestamp_format: Option<String>,
|
pub timestamp_format: Option<String>,
|
||||||
pub timezone: Option<String>,
|
pub timezone: Option<String>,
|
||||||
pub use_cli: Option<bool>,
|
pub use_cli: Option<bool>,
|
||||||
pub show_logs: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigFile {
|
impl ConfigFile {
|
||||||
|
|||||||
+98
-46
@@ -19,7 +19,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
mod message;
|
mod message;
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{AppData, DockerCommand, Header},
|
app_data::{AppData, DockerCommand, Header, ScrollDirection},
|
||||||
app_error::AppError,
|
app_error::AppError,
|
||||||
config,
|
config,
|
||||||
docker_data::DockerMessage,
|
docker_data::DockerMessage,
|
||||||
@@ -74,9 +74,11 @@ impl InputHandler {
|
|||||||
if contains(Status::DeleteConfirm) {
|
if contains(Status::DeleteConfirm) {
|
||||||
self.button_intersect(mouse_event).await;
|
self.button_intersect(mouse_event).await;
|
||||||
} else if !contains(Status::Error)
|
} else if !contains(Status::Error)
|
||||||
| !contains(Status::Help)
|
&& !contains(Status::Help)
|
||||||
| !contains(Status::DeleteConfirm)
|
&& !contains(Status::DeleteConfirm)
|
||||||
| !contains(Status::Filter)
|
&& !contains(Status::Filter)
|
||||||
|
// TODO handle state where you want to scroll log search results with the mouse wheel
|
||||||
|
&& !contains(Status::SearchLogs)
|
||||||
{
|
{
|
||||||
self.mouse_press(mouse_event, modifider);
|
self.mouse_press(mouse_event, modifider);
|
||||||
}
|
}
|
||||||
@@ -294,23 +296,12 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advance the "cursor" along the logs
|
fn logs_horizontal_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) {
|
||||||
fn logs_forward(&self, modifier: KeyModifiers) {
|
|
||||||
let panel = self.gui_state.lock().get_selected_panel();
|
let panel = self.gui_state.lock().get_selected_panel();
|
||||||
if panel == SelectablePanel::Logs {
|
if panel == SelectablePanel::Logs {
|
||||||
for _ in 0..self.get_modifier_total(modifier) {
|
for _ in 0..self.get_modifier_total(modifier) {
|
||||||
let width = self.gui_state.lock().get_screen_width();
|
let width = self.gui_state.lock().get_screen_width();
|
||||||
self.app_data.lock().log_forward(width);
|
self.app_data.lock().logs_horizontal_scroll(sd, width);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retreat the "cursor" along the logs
|
|
||||||
fn logs_back(&self, modifier: KeyModifiers) {
|
|
||||||
let panel = self.gui_state.lock().get_selected_panel();
|
|
||||||
if panel == SelectablePanel::Logs {
|
|
||||||
for _ in 0..self.get_modifier_total(modifier) {
|
|
||||||
self.app_data.lock().log_back();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,6 +379,66 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Actions to take when Filter status active
|
||||||
|
fn handle_search_logs(&self, key_code: KeyCode, modifier: KeyModifiers) {
|
||||||
|
match key_code {
|
||||||
|
KeyCode::Esc => {
|
||||||
|
self.app_data.lock().logs_search_clear();
|
||||||
|
self.gui_state.lock().status_del(Status::SearchLogs);
|
||||||
|
}
|
||||||
|
_ if KeyCode::Enter == key_code
|
||||||
|
|| self.keymap.log_search_mode.0 == key_code
|
||||||
|
|| self.keymap.log_search_mode.1 == Some(key_code) =>
|
||||||
|
{
|
||||||
|
self.gui_state.lock().status_del(Status::SearchLogs);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ if self.keymap.log_scroll_back.0 == key_code
|
||||||
|
|| self.keymap.log_scroll_back.1 == Some(key_code) =>
|
||||||
|
{
|
||||||
|
self.logs_horizontal_scroll(modifier, &ScrollDirection::Previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ if self.keymap.log_scroll_forward.0 == key_code
|
||||||
|
|| self.keymap.log_scroll_forward.1 == Some(key_code) =>
|
||||||
|
{
|
||||||
|
self.logs_horizontal_scroll(modifier, &ScrollDirection::Next);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ if self.keymap.scroll_down.0 == key_code => {
|
||||||
|
self.app_data
|
||||||
|
.lock()
|
||||||
|
.log_search_scroll(&ScrollDirection::Next);
|
||||||
|
// TODO should only do this is log_search_scroll returns some
|
||||||
|
// Need to wait til app_data and gui_data is combined
|
||||||
|
self.gui_state
|
||||||
|
.lock()
|
||||||
|
.set_logs_panel_selected(&self.app_data);
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
_ if self.keymap.scroll_up.0 == key_code => {
|
||||||
|
self.app_data
|
||||||
|
.lock()
|
||||||
|
.log_search_scroll(&ScrollDirection::Previous);
|
||||||
|
// TODO should only do this is log_search_scroll returns some
|
||||||
|
// Need to wait til app_data and gui_data is combined
|
||||||
|
self.gui_state
|
||||||
|
.lock()
|
||||||
|
.set_logs_panel_selected(&self.app_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle up and down keys
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
self.app_data.lock().log_search_pop();
|
||||||
|
}
|
||||||
|
KeyCode::Char(x) => {
|
||||||
|
self.app_data.lock().log_search_push(x);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Actions to take when Filter status active
|
/// Actions to take when Filter status active
|
||||||
fn handle_filter(&self, key_code: KeyCode) {
|
fn handle_filter(&self, key_code: KeyCode) {
|
||||||
match key_code {
|
match key_code {
|
||||||
@@ -572,13 +623,13 @@ impl InputHandler {
|
|||||||
_ if self.keymap.scroll_up.0 == key_code
|
_ if self.keymap.scroll_up.0 == key_code
|
||||||
|| self.keymap.scroll_up.1 == Some(key_code) =>
|
|| self.keymap.scroll_up.1 == Some(key_code) =>
|
||||||
{
|
{
|
||||||
self.scroll_up(modifier);
|
self.scroll(modifier, &ScrollDirection::Previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ if self.keymap.scroll_down.0 == key_code
|
_ if self.keymap.scroll_down.0 == key_code
|
||||||
|| self.keymap.scroll_down.1 == Some(key_code) =>
|
|| self.keymap.scroll_down.1 == Some(key_code) =>
|
||||||
{
|
{
|
||||||
self.scroll_down(modifier);
|
self.scroll(modifier, &ScrollDirection::Next);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ if self.keymap.filter_mode.0 == key_code
|
_ if self.keymap.filter_mode.0 == key_code
|
||||||
@@ -588,16 +639,27 @@ impl InputHandler {
|
|||||||
self.docker_tx.send(DockerMessage::Update).await.ok();
|
self.docker_tx.send(DockerMessage::Update).await.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ if self.keymap.log_search_mode.0 == key_code
|
||||||
|
|| self.keymap.log_search_mode.1 == Some(key_code) =>
|
||||||
|
{
|
||||||
|
if !self.gui_state.lock().get_show_logs() {
|
||||||
|
self.gui_state.lock().toggle_show_logs();
|
||||||
|
}
|
||||||
|
self.gui_state.lock().status_push(Status::SearchLogs);
|
||||||
|
}
|
||||||
|
|
||||||
_ if self.keymap.log_scroll_back.0 == key_code
|
_ if self.keymap.log_scroll_back.0 == key_code
|
||||||
|| self.keymap.log_scroll_back.1 == Some(key_code) =>
|
|| self.keymap.log_scroll_back.1 == Some(key_code) =>
|
||||||
{
|
{
|
||||||
self.logs_back(modifier);
|
self.logs_horizontal_scroll(modifier, &ScrollDirection::Previous);
|
||||||
|
// self.logs_back(modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ if self.keymap.log_scroll_forward.0 == key_code
|
_ if self.keymap.log_scroll_forward.0 == key_code
|
||||||
|| self.keymap.log_scroll_forward.1 == Some(key_code) =>
|
|| self.keymap.log_scroll_forward.1 == Some(key_code) =>
|
||||||
{
|
{
|
||||||
self.logs_forward(modifier);
|
self.logs_horizontal_scroll(modifier, &ScrollDirection::Next);
|
||||||
|
// self.logs_forward(modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyCode::Enter => self.enter_key().await,
|
KeyCode::Enter => self.enter_key().await,
|
||||||
@@ -615,13 +677,14 @@ impl InputHandler {
|
|||||||
let contains_exec = contains(Status::Exec);
|
let contains_exec = contains(Status::Exec);
|
||||||
let contains_filter = contains(Status::Filter);
|
let contains_filter = contains(Status::Filter);
|
||||||
let contains_delete = contains(Status::DeleteConfirm);
|
let contains_delete = contains(Status::DeleteConfirm);
|
||||||
|
let containes_search_logs = contains(Status::SearchLogs);
|
||||||
|
|
||||||
if !contains_exec {
|
if !contains_exec {
|
||||||
let is_q = || key_code == self.keymap.quit.0 || Some(key_code) == self.keymap.quit.1;
|
let is_q = || key_code == self.keymap.quit.0 || Some(key_code) == self.keymap.quit.1;
|
||||||
if key_modifier == KeyModifiers::CONTROL && key_code == KeyCode::Char('c')
|
if key_modifier == KeyModifiers::CONTROL && key_code == KeyCode::Char('c')
|
||||||
|| is_q() && !contains_filter
|
|| is_q() && !contains_filter && !containes_search_logs
|
||||||
{
|
{
|
||||||
// Always just quit on Ctrl + c/C or q/Q, unless in Filter status active
|
// Always just quit on Ctrl + c/C or q/Q, unless in filter/search_logs mode, i.e. when user inmput can include the q key
|
||||||
self.quit();
|
self.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,6 +694,8 @@ impl InputHandler {
|
|||||||
self.handle_help(key_code);
|
self.handle_help(key_code);
|
||||||
} else if contains_filter {
|
} else if contains_filter {
|
||||||
self.handle_filter(key_code);
|
self.handle_filter(key_code);
|
||||||
|
} else if containes_search_logs {
|
||||||
|
self.handle_search_logs(key_code, key_modifier);
|
||||||
} else if contains_delete {
|
} else if contains_delete {
|
||||||
self.handle_delete(key_code).await;
|
self.handle_delete(key_code).await;
|
||||||
} else {
|
} else {
|
||||||
@@ -669,8 +734,8 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match mouse_event.kind {
|
match mouse_event.kind {
|
||||||
MouseEventKind::ScrollUp => self.scroll_up(modifier),
|
MouseEventKind::ScrollUp => self.scroll(modifier, &ScrollDirection::Previous),
|
||||||
MouseEventKind::ScrollDown => self.scroll_down(modifier),
|
MouseEventKind::ScrollDown => self.scroll(modifier, &ScrollDirection::Next),
|
||||||
MouseEventKind::Down(MouseButton::Left) => {
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
|
let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1);
|
||||||
let header = self.gui_state.lock().get_intersect_header(mouse_point);
|
let header = self.gui_state.lock().get_intersect_header(mouse_point);
|
||||||
@@ -690,38 +755,25 @@ impl InputHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Change state to next, depending which panel is currently in focus
|
/// Change state to next, depending which panel is currently in focus
|
||||||
fn scroll_down(&self, modifier: KeyModifiers) {
|
fn scroll(&self, modifier: KeyModifiers, scroll: &ScrollDirection) {
|
||||||
|
let status = self.gui_state.lock().get_status();
|
||||||
|
if status.contains(&Status::SearchLogs) {
|
||||||
|
self.app_data.lock().log_search_scroll(scroll);
|
||||||
|
} else {
|
||||||
let selected_panel = self.gui_state.lock().get_selected_panel();
|
let selected_panel = self.gui_state.lock().get_selected_panel();
|
||||||
match selected_panel {
|
match selected_panel {
|
||||||
SelectablePanel::Containers => {
|
SelectablePanel::Containers => {
|
||||||
for _ in 0..self.get_modifier_total(modifier) {
|
for _ in 0..self.get_modifier_total(modifier) {
|
||||||
self.app_data.lock().containers_next();
|
self.app_data.lock().containers_scroll(scroll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SelectablePanel::Logs => {
|
SelectablePanel::Logs => {
|
||||||
for _ in 0..self.get_modifier_total(modifier) {
|
for _ in 0..self.get_modifier_total(modifier) {
|
||||||
self.app_data.lock().log_next();
|
self.app_data.lock().log_scroll(scroll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SelectablePanel::Commands => self.app_data.lock().docker_controls_next(),
|
SelectablePanel::Commands => self.app_data.lock().docker_controls_scroll(scroll),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change state to previous, depending which panel is currently in focus
|
|
||||||
fn scroll_up(&self, modifier: KeyModifiers) {
|
|
||||||
let selected_panel = self.gui_state.lock().get_selected_panel();
|
|
||||||
match selected_panel {
|
|
||||||
SelectablePanel::Containers => {
|
|
||||||
for _ in 0..self.get_modifier_total(modifier) {
|
|
||||||
self.app_data.lock().containers_previous();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectablePanel::Logs => {
|
|
||||||
for _ in 0..self.get_modifier_total(modifier) {
|
|
||||||
self.app_data.lock().log_previous();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectablePanel::Commands => self.app_data.lock().docker_controls_previous(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#![allow(clippy::collapsible_if)]
|
#![allow(clippy::collapsible_if)]
|
||||||
|
// #![allow(unused)]
|
||||||
// Zigbuild is stuck on 1.87.0, which means Mac builds won't work when using collapsible ifs
|
// Zigbuild is stuck on 1.87.0, which means Mac builds won't work when using collapsible ifs
|
||||||
|
|
||||||
use app_data::AppData;
|
use app_data::AppData;
|
||||||
@@ -175,6 +176,7 @@ mod tests {
|
|||||||
show_std_err: false,
|
show_std_err: false,
|
||||||
in_container: false,
|
in_container: false,
|
||||||
save_dir: None,
|
save_dir: None,
|
||||||
|
log_search_case_sensitive: true,
|
||||||
raw_logs: false,
|
raw_logs: false,
|
||||||
show_self: false,
|
show_self: false,
|
||||||
app_colors: AppColors::new(),
|
app_colors: AppColors::new(),
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ mod tests {
|
|||||||
use ratatui::style::{Color, Modifier};
|
use ratatui::style::{Color, Modifier};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
app_data::ScrollDirection,
|
||||||
config::AppColors,
|
config::AppColors,
|
||||||
tests::gen_container_summary,
|
tests::gen_container_summary,
|
||||||
ui::{
|
ui::{
|
||||||
@@ -169,7 +170,10 @@ mod tests {
|
|||||||
.app_data
|
.app_data
|
||||||
.lock()
|
.lock()
|
||||||
.update_containers(vec![gen_container_summary(1, "paused")]);
|
.update_containers(vec![gen_container_summary(1, "paused")]);
|
||||||
setup.app_data.lock().docker_controls_next();
|
setup
|
||||||
|
.app_data
|
||||||
|
.lock()
|
||||||
|
.docker_controls_scroll(&ScrollDirection::Next);
|
||||||
|
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
@@ -363,7 +367,10 @@ mod tests {
|
|||||||
.app_data
|
.app_data
|
||||||
.lock()
|
.lock()
|
||||||
.update_containers(vec![gen_container_summary(1, "paused")]);
|
.update_containers(vec![gen_container_summary(1, "paused")]);
|
||||||
setup.app_data.lock().docker_controls_next();
|
setup
|
||||||
|
.app_data
|
||||||
|
.lock()
|
||||||
|
.docker_controls_scroll(&ScrollDirection::Next);
|
||||||
|
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
|
|||||||
+30
-30
@@ -160,6 +160,11 @@ impl HelpInfo {
|
|||||||
button_item("/"),
|
button_item("/"),
|
||||||
button_desc("enter filter mode"),
|
button_desc("enter filter mode"),
|
||||||
]),
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
space(),
|
||||||
|
button_item("#"),
|
||||||
|
button_desc("enter log search mode"),
|
||||||
|
]),
|
||||||
Line::from(vec![space(), button_item("0"), button_desc("stop sort")]),
|
Line::from(vec![space(), button_item("0"), button_desc("stop sort")]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
space(),
|
space(),
|
||||||
@@ -306,6 +311,7 @@ impl HelpInfo {
|
|||||||
"toggle mouse capture - if disabled, text on screen can be selected & copied",
|
"toggle mouse capture - if disabled, text on screen can be selected & copied",
|
||||||
),
|
),
|
||||||
or_secondary(km.filter_mode, "enter filter mode"),
|
or_secondary(km.filter_mode, "enter filter mode"),
|
||||||
|
or_secondary(km.log_search_mode, "enter log search mode"),
|
||||||
or_secondary(km.sort_reset, "reset container sorting"),
|
or_secondary(km.sort_reset, "reset container sorting"),
|
||||||
or_secondary(km.sort_by_name, "sort containers by name"),
|
or_secondary(km.sort_by_name, "sort containers by name"),
|
||||||
or_secondary(km.sort_by_state, "sort containers by state"),
|
or_secondary(km.sort_by_state, "sort containers by state"),
|
||||||
@@ -458,7 +464,7 @@ mod tests {
|
|||||||
/// This test is incredibly annoying
|
/// This test is incredibly annoying
|
||||||
/// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg);
|
/// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg);
|
||||||
fn test_draw_blocks_help() {
|
fn test_draw_blocks_help() {
|
||||||
let mut setup = test_setup(87, 37, true, true);
|
let mut setup = test_setup(87, 39, true, true);
|
||||||
let tz = setup.app_data.lock().config.timezone.clone();
|
let tz = setup.app_data.lock().config.timezone.clone();
|
||||||
|
|
||||||
setup
|
setup
|
||||||
@@ -480,35 +486,30 @@ mod tests {
|
|||||||
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
match (row_index, result_cell_index) {
|
match (row_index, result_cell_index) {
|
||||||
// first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
|
// first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
|
||||||
(0 | 36, _) | (0..=35, 0 | 86) => {
|
(0 | 38, _) | (0..=37, 0 | 86) => {
|
||||||
assert_eq!(result_cell.bg, Color::Reset);
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
assert_eq!(result_cell.fg, Color::Reset);
|
assert_eq!(result_cell.fg, Color::Reset);
|
||||||
}
|
}
|
||||||
// border is red on black
|
|
||||||
(1 | 34, _) | (1..=31, 1 | 85) => {
|
|
||||||
assert_eq!(result_cell.bg, Color::Magenta);
|
|
||||||
assert_eq!(result_cell.fg, Color::Black);
|
|
||||||
}
|
|
||||||
// Buttons
|
// Buttons
|
||||||
(2..=10, 2..=85)
|
(2..=10, 2..=84)
|
||||||
| (12, 19..=66)
|
| (12, 19..=66)
|
||||||
| (14, 2..=10 | 13..=27)
|
| (14, 2..=10 | 13..=27)
|
||||||
| (15, 2..=10 | 13..=21 | 24..=37)
|
| (15, 2..=10 | 13..=21 | 24..=37)
|
||||||
| (16 | 27 | 29, 2..=10)
|
| (16 | 28 | 30, 2..=10)
|
||||||
|
| (19..=26 | 29 | 31, 2..=8)
|
||||||
| (17, 2..=11)
|
| (17, 2..=11)
|
||||||
| (18 | 26, 2..=12)
|
| (18 | 27, 2..=12)
|
||||||
| (19 | 20 | 21 | 22 | 24 | 25 | 28 | 23 | 30, 2..=8)
|
|
||||||
| (24, 2..=9 | 12..=18) => {
|
| (24, 2..=9 | 12..=18) => {
|
||||||
assert_eq!(result_cell.bg, Color::Magenta);
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
assert_eq!(result_cell.fg, Color::White);
|
assert_eq!(result_cell.fg, Color::White);
|
||||||
}
|
}
|
||||||
// The URL is yellow and underlined
|
// The URL is white on yellow and underlined
|
||||||
(33, 25..=60) => {
|
(34, 25..=60) => {
|
||||||
assert_eq!(result_cell.bg, Color::Magenta);
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
assert_eq!(result_cell.fg, Color::White);
|
assert_eq!(result_cell.fg, Color::White);
|
||||||
assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
|
assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
|
||||||
}
|
}
|
||||||
// The rest is red on black
|
// The rest is black on magenta
|
||||||
_ => {
|
_ => {
|
||||||
assert_eq!(result_cell.bg, Color::Magenta);
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
assert_eq!(result_cell.fg, Color::Black);
|
assert_eq!(result_cell.fg, Color::Black);
|
||||||
@@ -523,7 +524,7 @@ mod tests {
|
|||||||
/// This test is incredibly annoying
|
/// This test is incredibly annoying
|
||||||
/// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg);
|
/// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg);
|
||||||
fn test_draw_blocks_help_custom_colors() {
|
fn test_draw_blocks_help_custom_colors() {
|
||||||
let mut setup = test_setup(87, 37, true, true);
|
let mut setup = test_setup(87, 39, true, true);
|
||||||
let mut colors = AppColors::new();
|
let mut colors = AppColors::new();
|
||||||
let tz = setup.app_data.lock().config.timezone.clone();
|
let tz = setup.app_data.lock().config.timezone.clone();
|
||||||
|
|
||||||
@@ -549,35 +550,30 @@ mod tests {
|
|||||||
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
match (row_index, result_cell_index) {
|
match (row_index, result_cell_index) {
|
||||||
// first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
|
// first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area
|
||||||
(0 | 36, _) | (0..=35, 0 | 86) => {
|
(0 | 38, _) | (0..=37, 0 | 86) => {
|
||||||
assert_eq!(result_cell.bg, Color::Reset);
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
assert_eq!(result_cell.fg, Color::Reset);
|
assert_eq!(result_cell.fg, Color::Reset);
|
||||||
}
|
}
|
||||||
// border is red on black
|
|
||||||
(1 | 34, _) | (1..=31, 1 | 85) => {
|
|
||||||
assert_eq!(result_cell.bg, Color::Black);
|
|
||||||
assert_eq!(result_cell.fg, Color::Red);
|
|
||||||
}
|
|
||||||
// Buttons
|
// Buttons
|
||||||
(2..=10, 2..=85)
|
(2..=10, 2..=84)
|
||||||
| (12, 19..=66)
|
| (12, 19..=66)
|
||||||
| (14, 2..=10 | 13..=27)
|
| (14, 2..=10 | 13..=27)
|
||||||
| (15, 2..=10 | 13..=21 | 24..=37)
|
| (15, 2..=10 | 13..=21 | 24..=37)
|
||||||
| (16 | 27 | 29, 2..=10)
|
| (16 | 28 | 30, 2..=10)
|
||||||
|
| (19..=26 | 29 | 31, 2..=8)
|
||||||
| (17, 2..=11)
|
| (17, 2..=11)
|
||||||
| (18 | 26, 2..=12)
|
| (18 | 27, 2..=12)
|
||||||
| (19 | 20 | 21 | 22 | 24 | 25 | 28 | 23 | 30, 2..=8)
|
|
||||||
| (24, 2..=9 | 12..=18) => {
|
| (24, 2..=9 | 12..=18) => {
|
||||||
assert_eq!(result_cell.bg, Color::Black);
|
assert_eq!(result_cell.bg, Color::Black);
|
||||||
assert_eq!(result_cell.fg, Color::Yellow);
|
assert_eq!(result_cell.fg, Color::Yellow);
|
||||||
}
|
}
|
||||||
// The URL is yellow and underlined
|
// The URL is white on yellow and underlined
|
||||||
(33, 25..=60) => {
|
(34, 25..=60) => {
|
||||||
assert_eq!(result_cell.bg, Color::Black);
|
assert_eq!(result_cell.bg, Color::Black);
|
||||||
assert_eq!(result_cell.fg, Color::Yellow);
|
assert_eq!(result_cell.fg, Color::Yellow);
|
||||||
assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
|
assert_eq!(result_cell.modifier, Modifier::UNDERLINED);
|
||||||
}
|
}
|
||||||
// The rest is red on black
|
// The rest is black on magenta
|
||||||
_ => {
|
_ => {
|
||||||
assert_eq!(result_cell.bg, Color::Black);
|
assert_eq!(result_cell.bg, Color::Black);
|
||||||
assert_eq!(result_cell.fg, Color::Red);
|
assert_eq!(result_cell.fg, Color::Red);
|
||||||
@@ -598,6 +594,7 @@ mod tests {
|
|||||||
delete_deny: (KeyCode::Char('c'), None),
|
delete_deny: (KeyCode::Char('c'), None),
|
||||||
exec: (KeyCode::Char('d'), None),
|
exec: (KeyCode::Char('d'), None),
|
||||||
filter_mode: (KeyCode::Char('e'), None),
|
filter_mode: (KeyCode::Char('e'), None),
|
||||||
|
log_search_mode: (KeyCode::Char('7'), None),
|
||||||
force_redraw: (KeyCode::Char('f'), None),
|
force_redraw: (KeyCode::Char('f'), None),
|
||||||
log_scroll_back: (KeyCode::Char('g'), None),
|
log_scroll_back: (KeyCode::Char('g'), None),
|
||||||
log_scroll_forward: (KeyCode::Char('h'), None),
|
log_scroll_forward: (KeyCode::Char('h'), None),
|
||||||
@@ -648,6 +645,7 @@ mod tests {
|
|||||||
delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))),
|
delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))),
|
||||||
exec: (KeyCode::Char('d'), Some(KeyCode::Char('D'))),
|
exec: (KeyCode::Char('d'), Some(KeyCode::Char('D'))),
|
||||||
filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))),
|
filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))),
|
||||||
|
log_search_mode: (KeyCode::Char('m'), Some(KeyCode::Char('M'))),
|
||||||
force_redraw: (KeyCode::Char('f'), Some(KeyCode::Char('F'))),
|
force_redraw: (KeyCode::Char('f'), Some(KeyCode::Char('F'))),
|
||||||
log_scroll_back: (KeyCode::Char('f'), Some(KeyCode::Char('F'))),
|
log_scroll_back: (KeyCode::Char('f'), Some(KeyCode::Char('F'))),
|
||||||
log_scroll_forward: (KeyCode::Char('g'), Some(KeyCode::Char('G'))),
|
log_scroll_forward: (KeyCode::Char('g'), Some(KeyCode::Char('G'))),
|
||||||
@@ -689,6 +687,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// Help panel will show custom keymap if in use, with either one or two definition for each entry
|
/// Help panel will show custom keymap if in use, with either one or two definition for each entry
|
||||||
|
#[allow(clippy::todo)]
|
||||||
fn test_draw_blocks_help_one_and_two_definitions() {
|
fn test_draw_blocks_help_one_and_two_definitions() {
|
||||||
let mut setup = test_setup(110, 50, true, true);
|
let mut setup = test_setup(110, 50, true, true);
|
||||||
|
|
||||||
@@ -698,6 +697,7 @@ mod tests {
|
|||||||
delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))),
|
delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))),
|
||||||
exec: (KeyCode::Char('d'), None),
|
exec: (KeyCode::Char('d'), None),
|
||||||
filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))),
|
filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))),
|
||||||
|
log_search_mode: (KeyCode::Char('8'), None),
|
||||||
force_redraw: (KeyCode::Char('f'), None),
|
force_redraw: (KeyCode::Char('f'), None),
|
||||||
log_scroll_back: (KeyCode::Char('g'), Some(KeyCode::Char('G'))),
|
log_scroll_back: (KeyCode::Char('g'), Some(KeyCode::Char('G'))),
|
||||||
log_scroll_forward: (KeyCode::Char('h'), None),
|
log_scroll_forward: (KeyCode::Char('h'), None),
|
||||||
@@ -724,7 +724,7 @@ mod tests {
|
|||||||
sort_by_tx: (KeyCode::Char('3'), None),
|
sort_by_tx: (KeyCode::Char('3'), None),
|
||||||
sort_reset: (KeyCode::Char('4'), Some(KeyCode::Char('5'))),
|
sort_reset: (KeyCode::Char('4'), Some(KeyCode::Char('5'))),
|
||||||
toggle_help: (KeyCode::Char('5'), None),
|
toggle_help: (KeyCode::Char('5'), None),
|
||||||
toggle_mouse_capture: (KeyCode::Char('6'), Some(KeyCode::Char('7'))),
|
toggle_mouse_capture: (KeyCode::Char('6'), Some(KeyCode::Char('#'))),
|
||||||
};
|
};
|
||||||
|
|
||||||
let tz = setup.app_data.lock().config.timezone.clone();
|
let tz = setup.app_data.lock().config.timezone.clone();
|
||||||
@@ -741,7 +741,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_draw_blocks_help_show_timezone() {
|
fn test_draw_blocks_help_show_timezone() {
|
||||||
let mut setup = test_setup(87, 37, true, true);
|
let mut setup = test_setup(87, 39, true, true);
|
||||||
|
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ mod tests {
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{ContainerImage, ContainerName},
|
app_data::{ContainerImage, ContainerName, ScrollDirection},
|
||||||
config::AppColors,
|
config::AppColors,
|
||||||
ui::{
|
ui::{
|
||||||
FrameData, Status,
|
FrameData, Status,
|
||||||
@@ -309,7 +309,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
setup.app_data.lock().log_previous();
|
setup.app_data.lock().log_scroll(&ScrollDirection::Previous);
|
||||||
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
setup
|
setup
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ pub mod info;
|
|||||||
pub mod logs;
|
pub mod logs;
|
||||||
pub mod popup;
|
pub mod popup;
|
||||||
pub mod ports;
|
pub mod ports;
|
||||||
|
pub mod search_logs;
|
||||||
|
|
||||||
pub const NAME_TEXT: &str = r#"
|
pub const NAME_TEXT: &str = r#"
|
||||||
88
|
88
|
||||||
@@ -160,19 +161,11 @@ pub mod tests {
|
|||||||
fn from(data: (&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)) -> Self {
|
fn from(data: (&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)) -> Self {
|
||||||
let (mut app_data, gui_data) = (data.0.lock(), data.1.lock());
|
let (mut app_data, gui_data) = (data.0.lock(), data.1.lock());
|
||||||
|
|
||||||
// let container_section_height = app_data.get_container_len();
|
|
||||||
// let container_section_height = if container_section_height < 12 {
|
|
||||||
// u16::try_from(container_section_height + 5).unwrap_or_default()
|
|
||||||
// } else {
|
|
||||||
// 12
|
|
||||||
// };
|
|
||||||
|
|
||||||
let (filter_by, filter_term) = app_data.get_filter();
|
let (filter_by, filter_term) = app_data.get_filter();
|
||||||
Self {
|
Self {
|
||||||
chart_data: app_data.get_chart_data(),
|
chart_data: app_data.get_chart_data(),
|
||||||
color_logs: app_data.config.color_logs,
|
color_logs: app_data.config.color_logs,
|
||||||
columns: app_data.get_width(),
|
columns: app_data.get_width(),
|
||||||
// container_section_height,
|
|
||||||
container_title: app_data.get_container_title(),
|
container_title: app_data.get_container_title(),
|
||||||
delete_confirm: gui_data.get_delete_container(),
|
delete_confirm: gui_data.get_delete_container(),
|
||||||
filter_by,
|
filter_by,
|
||||||
@@ -182,6 +175,7 @@ pub mod tests {
|
|||||||
show_logs: gui_data.get_show_logs(),
|
show_logs: gui_data.get_show_logs(),
|
||||||
info_text: gui_data.info_box_text.clone(),
|
info_text: gui_data.info_box_text.clone(),
|
||||||
is_loading: gui_data.is_loading(),
|
is_loading: gui_data.is_loading(),
|
||||||
|
log_search: app_data.gen_log_search(),
|
||||||
loading_icon: gui_data.get_loading().to_string(),
|
loading_icon: gui_data.get_loading().to_string(),
|
||||||
log_height: gui_data.get_log_height(),
|
log_height: gui_data.get_log_height(),
|
||||||
log_title: app_data.get_log_title(),
|
log_title: app_data.get_log_title(),
|
||||||
@@ -230,7 +224,6 @@ pub mod tests {
|
|||||||
/// Just a shorthand for when enumerating over result cells
|
/// Just a shorthand for when enumerating over result cells
|
||||||
pub fn get_result(
|
pub fn get_result(
|
||||||
setup: &'_ TuiTestSetup,
|
setup: &'_ TuiTestSetup,
|
||||||
// w: u16,
|
|
||||||
) -> std::iter::Enumerate<std::slice::Chunks<'_, ratatui::buffer::Cell>> {
|
) -> std::iter::Enumerate<std::slice::Chunks<'_, ratatui::buffer::Cell>> {
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
|
|||||||
@@ -0,0 +1,479 @@
|
|||||||
|
use crossterm::event::KeyCode;
|
||||||
|
use ratatui::{
|
||||||
|
Frame,
|
||||||
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
style::{Modifier, Style},
|
||||||
|
text::{Line, Span},
|
||||||
|
widgets::Paragraph,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app_data::LogsButton,
|
||||||
|
config::{AppColors, Keymap},
|
||||||
|
ui::FrameData,
|
||||||
|
};
|
||||||
|
|
||||||
|
// background, text, selected_text, highlight;
|
||||||
|
/// Draw the filter bar
|
||||||
|
pub fn draw(area: Rect, colors: AppColors, frame: &mut Frame, fd: &FrameData, keymap: &Keymap) {
|
||||||
|
let style_but = Style::default()
|
||||||
|
.fg(colors.log_search.button_text)
|
||||||
|
.bg(colors.log_search.highlight);
|
||||||
|
let style_desc = Style::default()
|
||||||
|
.fg(colors.log_search.text)
|
||||||
|
.bg(colors.log_search.background);
|
||||||
|
let space = || Span::from(" ");
|
||||||
|
|
||||||
|
let mut line = vec![
|
||||||
|
Span::styled(" Esc ", style_but),
|
||||||
|
Span::styled(" clear ", style_desc),
|
||||||
|
space(),
|
||||||
|
];
|
||||||
|
line.extend([Span::styled(
|
||||||
|
" search term: ",
|
||||||
|
Style::default()
|
||||||
|
.fg(colors.log_search.highlight)
|
||||||
|
.bg(colors.log_search.background)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)]);
|
||||||
|
|
||||||
|
if let Some(log_search) = fd.log_search.as_ref() {
|
||||||
|
line.extend([
|
||||||
|
Span::styled(
|
||||||
|
log_search
|
||||||
|
.term
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), std::clone::Clone::clone),
|
||||||
|
Style::default()
|
||||||
|
.fg(colors.log_search.text)
|
||||||
|
.bg(colors.log_search.background),
|
||||||
|
),
|
||||||
|
space(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let left_text = Paragraph::new(Line::from(line))
|
||||||
|
.alignment(ratatui::layout::Alignment::Left)
|
||||||
|
.style(Style::default().bg(colors.log_search.background));
|
||||||
|
let mut line = vec![];
|
||||||
|
if let Some(log_search) = fd.log_search.as_ref() {
|
||||||
|
if let Some(buttons) = log_search.buttons.as_ref() {
|
||||||
|
let down = if keymap.scroll_down.0 == KeyCode::Down {
|
||||||
|
"↑".to_owned()
|
||||||
|
} else {
|
||||||
|
keymap.scroll_down.0.to_string()
|
||||||
|
};
|
||||||
|
let up = if keymap.scroll_up.0 == KeyCode::Up {
|
||||||
|
"↓".to_owned()
|
||||||
|
} else {
|
||||||
|
keymap.scroll_up.0.to_string()
|
||||||
|
};
|
||||||
|
let next = [
|
||||||
|
space(),
|
||||||
|
Span::styled(format!(" {up} "), style_but),
|
||||||
|
Span::styled(" next ", style_desc),
|
||||||
|
];
|
||||||
|
let previous = [
|
||||||
|
space(),
|
||||||
|
Span::styled(format!(" {down} "), style_but),
|
||||||
|
Span::styled(" previous ", style_desc),
|
||||||
|
];
|
||||||
|
|
||||||
|
match buttons {
|
||||||
|
LogsButton::Both => line.extend(previous.into_iter().chain(next)),
|
||||||
|
LogsButton::Next => line.extend(next),
|
||||||
|
LogsButton::Previous => line.extend(previous),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(results) = log_search.result.as_ref() {
|
||||||
|
line.extend([
|
||||||
|
Span::styled(
|
||||||
|
" matches: ",
|
||||||
|
Style::default()
|
||||||
|
.fg(colors.log_search.highlight)
|
||||||
|
.bg(colors.log_search.background)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
|
Span::styled(
|
||||||
|
results,
|
||||||
|
Style::default()
|
||||||
|
.fg(colors.log_search.text)
|
||||||
|
.bg(colors.log_search.background),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let right_text = Paragraph::new(Line::from(line))
|
||||||
|
.alignment(ratatui::layout::Alignment::Right)
|
||||||
|
.style(Style::default().bg(colors.log_search.background));
|
||||||
|
|
||||||
|
let line_split = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
|
.split(area);
|
||||||
|
frame.render_widget(left_text, line_split[0]);
|
||||||
|
frame.render_widget(right_text, line_split[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use crossterm::event::KeyCode;
|
||||||
|
use insta::assert_snapshot;
|
||||||
|
use ratatui::style::{Color, Modifier};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{AppColors, Keymap},
|
||||||
|
ui::{
|
||||||
|
FrameData,
|
||||||
|
draw_blocks::tests::{get_result, insert_logs, test_setup},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Filter row is drawn correctly & colors are correct
|
||||||
|
/// Colours change when filter_by option is changed
|
||||||
|
fn test_draw_blocks_log_search_row() {
|
||||||
|
let mut setup = test_setup(140, 1, true, true);
|
||||||
|
|
||||||
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.status_push(crate::ui::Status::SearchLogs);
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
super::draw(setup.area, AppColors::new(), f, &setup.fd, &Keymap::new());
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
|
||||||
|
for (_, result_row) in get_result(&setup) {
|
||||||
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
|
match result_cell_index {
|
||||||
|
0..=4 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.fg, Color::Black);
|
||||||
|
}
|
||||||
|
5..=11 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Gray);
|
||||||
|
}
|
||||||
|
13..=26 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.modifier, Modifier::BOLD);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Log item found, previous button visible
|
||||||
|
fn test_draw_blocks_log_search_match_previous() {
|
||||||
|
let mut setup = test_setup(140, 1, true, true);
|
||||||
|
|
||||||
|
insert_logs(&setup);
|
||||||
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.status_push(crate::ui::Status::SearchLogs);
|
||||||
|
|
||||||
|
setup.app_data.lock().log_search_push('e');
|
||||||
|
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
super::draw(setup.area, AppColors::new(), f, &fd, &Keymap::new());
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
|
||||||
|
for (_, result_row) in get_result(&setup) {
|
||||||
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
|
match result_cell_index {
|
||||||
|
0..=4 | 114..=116 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.fg, Color::Black);
|
||||||
|
}
|
||||||
|
5..=11 | 27 | 117..=126 | 137..=139 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Gray);
|
||||||
|
}
|
||||||
|
13..=26 | 127..=136 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.modifier, Modifier::BOLD);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Log item found, next button visible
|
||||||
|
fn test_draw_blocks_log_search_match_next() {
|
||||||
|
let mut setup = test_setup(140, 1, true, true);
|
||||||
|
|
||||||
|
insert_logs(&setup);
|
||||||
|
|
||||||
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.status_push(crate::ui::Status::SearchLogs);
|
||||||
|
|
||||||
|
setup.app_data.lock().log_search_push('e');
|
||||||
|
setup
|
||||||
|
.app_data
|
||||||
|
.lock()
|
||||||
|
.log_scroll(&crate::app_data::ScrollDirection::Previous);
|
||||||
|
setup
|
||||||
|
.app_data
|
||||||
|
.lock()
|
||||||
|
.log_scroll(&crate::app_data::ScrollDirection::Previous);
|
||||||
|
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
super::draw(setup.area, AppColors::new(), f, &fd, &Keymap::new());
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
|
||||||
|
for (_, result_row) in get_result(&setup) {
|
||||||
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
|
match result_cell_index {
|
||||||
|
0..=4 | 118..=120 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.fg, Color::Black);
|
||||||
|
}
|
||||||
|
5..=11 | 27 | 121..=126 | 137..=139 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Gray);
|
||||||
|
}
|
||||||
|
13..=26 | 127..=136 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.modifier, Modifier::BOLD);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Log item found, next & previous button visible
|
||||||
|
fn test_draw_blocks_log_search_match_both_next_previous() {
|
||||||
|
let mut setup = test_setup(140, 1, true, true);
|
||||||
|
|
||||||
|
insert_logs(&setup);
|
||||||
|
|
||||||
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.status_push(crate::ui::Status::SearchLogs);
|
||||||
|
|
||||||
|
setup.app_data.lock().log_search_push('e');
|
||||||
|
setup
|
||||||
|
.app_data
|
||||||
|
.lock()
|
||||||
|
.log_scroll(&crate::app_data::ScrollDirection::Previous);
|
||||||
|
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
super::draw(setup.area, AppColors::new(), f, &fd, &Keymap::new());
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
|
||||||
|
for (_, result_row) in get_result(&setup) {
|
||||||
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
|
match result_cell_index {
|
||||||
|
0..=4 | 104..=106 | 118..=120 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.fg, Color::Black);
|
||||||
|
}
|
||||||
|
5..=11 | 27 | 107..=116 | 121..=126 | 137..=139 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Gray);
|
||||||
|
}
|
||||||
|
13..=26 | 127..=136 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.modifier, Modifier::BOLD);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// No log item found
|
||||||
|
fn test_draw_blocks_log_search_match_none() {
|
||||||
|
let mut setup = test_setup(140, 1, true, true);
|
||||||
|
|
||||||
|
insert_logs(&setup);
|
||||||
|
|
||||||
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.status_push(crate::ui::Status::SearchLogs);
|
||||||
|
|
||||||
|
setup.app_data.lock().log_search_push('z');
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
super::draw(setup.area, AppColors::new(), f, &fd, &Keymap::new());
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
|
||||||
|
for (_, result_row) in get_result(&setup) {
|
||||||
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
|
match result_cell_index {
|
||||||
|
0..=4 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.fg, Color::Black);
|
||||||
|
}
|
||||||
|
5..=11 | 27 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Gray);
|
||||||
|
}
|
||||||
|
13..=26 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Magenta);
|
||||||
|
assert_eq!(result_cell.modifier, Modifier::BOLD);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Reset);
|
||||||
|
assert_eq!(result_cell.fg, Color::Reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Custom keymap for scroll buttons
|
||||||
|
fn test_draw_blocks_log_search_keymap() {
|
||||||
|
let mut setup = test_setup(140, 1, true, true);
|
||||||
|
|
||||||
|
insert_logs(&setup);
|
||||||
|
|
||||||
|
let mut keymap = setup.app_data.lock().config.keymap.clone();
|
||||||
|
keymap.scroll_up = (KeyCode::Char('a'), None);
|
||||||
|
keymap.scroll_down = (KeyCode::Char('b'), None);
|
||||||
|
|
||||||
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.status_push(crate::ui::Status::SearchLogs);
|
||||||
|
|
||||||
|
setup.app_data.lock().log_search_push('e');
|
||||||
|
setup
|
||||||
|
.app_data
|
||||||
|
.lock()
|
||||||
|
.log_scroll(&crate::app_data::ScrollDirection::Previous);
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
super::draw(setup.area, AppColors::new(), f, &fd, &keymap);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Custom colours applied
|
||||||
|
fn test_draw_blocks_log_search_colors() {
|
||||||
|
let mut setup = test_setup(140, 1, true, true);
|
||||||
|
|
||||||
|
insert_logs(&setup);
|
||||||
|
|
||||||
|
setup
|
||||||
|
.gui_state
|
||||||
|
.lock()
|
||||||
|
.status_push(crate::ui::Status::SearchLogs);
|
||||||
|
|
||||||
|
setup.app_data.lock().log_search_push('e');
|
||||||
|
setup
|
||||||
|
.app_data
|
||||||
|
.lock()
|
||||||
|
.log_scroll(&crate::app_data::ScrollDirection::Previous);
|
||||||
|
|
||||||
|
let mut colors = AppColors::new();
|
||||||
|
|
||||||
|
colors.log_search.background = Color::White;
|
||||||
|
colors.log_search.highlight = Color::Blue;
|
||||||
|
colors.log_search.button_text = Color::Yellow;
|
||||||
|
colors.log_search.text = Color::Magenta;
|
||||||
|
|
||||||
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
setup
|
||||||
|
.terminal
|
||||||
|
.draw(|f| {
|
||||||
|
super::draw(setup.area, colors, f, &fd, &Keymap::new());
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_snapshot!(setup.terminal.backend());
|
||||||
|
|
||||||
|
for (_, result_row) in get_result(&setup) {
|
||||||
|
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
|
||||||
|
match result_cell_index {
|
||||||
|
0..=4 | 104..=106 | 118..=120 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::Blue);
|
||||||
|
assert_eq!(result_cell.fg, Color::Yellow);
|
||||||
|
}
|
||||||
|
5..=11 | 27 | 107..=116 | 121..=126 | 137..=139 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::White);
|
||||||
|
assert_eq!(result_cell.fg, Color::Magenta);
|
||||||
|
}
|
||||||
|
13..=26 | 127..=136 => {
|
||||||
|
assert_eq!(result_cell.bg, Color::White);
|
||||||
|
assert_eq!(result_cell.fg, Color::Blue);
|
||||||
|
assert_eq!(result_cell.modifier, Modifier::BOLD);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
assert_eq!(result_cell.bg, Color::White);
|
||||||
|
assert_eq!(result_cell.fg, Color::Reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -27,6 +27,7 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( s ) save logs to file │ "
|
" │ ( s ) save logs to file │ "
|
||||||
" │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
" │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
||||||
" │ ( F1 ) or ( / ) enter filter mode │ "
|
" │ ( F1 ) or ( / ) enter filter mode │ "
|
||||||
|
" │ ( # ) enter log search mode │ "
|
||||||
" │ ( 0 ) stop sort │ "
|
" │ ( 0 ) stop sort │ "
|
||||||
" │ ( 1 - 9 ) sort by header - or click header │ "
|
" │ ( 1 - 9 ) sort by header - or click header │ "
|
||||||
" │ ( - = ) change log section height │ "
|
" │ ( - = ) change log section height │ "
|
||||||
@@ -37,5 +38,6 @@ expression: setup.terminal.backend()
|
|||||||
" │ currently an early work in progress, all and any input appreciated │ "
|
" │ currently an early work in progress, all and any input appreciated │ "
|
||||||
" │ https://github.com/mrjackwills/oxker │ "
|
" │ https://github.com/mrjackwills/oxker │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
|
" │ │ "
|
||||||
" ╰───────────────────────────────────────────────────────────────────────────────────╯ "
|
" ╰───────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
" "
|
" "
|
||||||
|
|||||||
+2
@@ -27,6 +27,7 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( s ) save logs to file │ "
|
" │ ( s ) save logs to file │ "
|
||||||
" │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
" │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
||||||
" │ ( F1 ) or ( / ) enter filter mode │ "
|
" │ ( F1 ) or ( / ) enter filter mode │ "
|
||||||
|
" │ ( # ) enter log search mode │ "
|
||||||
" │ ( 0 ) stop sort │ "
|
" │ ( 0 ) stop sort │ "
|
||||||
" │ ( 1 - 9 ) sort by header - or click header │ "
|
" │ ( 1 - 9 ) sort by header - or click header │ "
|
||||||
" │ ( - = ) change log section height │ "
|
" │ ( - = ) change log section height │ "
|
||||||
@@ -37,5 +38,6 @@ expression: setup.terminal.backend()
|
|||||||
" │ currently an early work in progress, all and any input appreciated │ "
|
" │ currently an early work in progress, all and any input appreciated │ "
|
||||||
" │ https://github.com/mrjackwills/oxker │ "
|
" │ https://github.com/mrjackwills/oxker │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
|
" │ │ "
|
||||||
" ╰───────────────────────────────────────────────────────────────────────────────────╯ "
|
" ╰───────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
" "
|
" "
|
||||||
|
|||||||
+1
-1
@@ -31,6 +31,7 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( m ) save logs to file │ "
|
" │ ( m ) save logs to file │ "
|
||||||
" │ ( 6 ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
" │ ( 6 ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
||||||
" │ ( e ) enter filter mode │ "
|
" │ ( e ) enter filter mode │ "
|
||||||
|
" │ ( 7 ) enter log search mode │ "
|
||||||
" │ ( 4 ) reset container sorting │ "
|
" │ ( 4 ) reset container sorting │ "
|
||||||
" │ ( z ) sort containers by name │ "
|
" │ ( z ) sort containers by name │ "
|
||||||
" │ ( 1 ) sort containers by state │ "
|
" │ ( 1 ) sort containers by state │ "
|
||||||
@@ -50,5 +51,4 @@ expression: setup.terminal.backend()
|
|||||||
" │ currently an early work in progress, all and any input appreciated │ "
|
" │ currently an early work in progress, all and any input appreciated │ "
|
||||||
" │ https://github.com/mrjackwills/oxker │ "
|
" │ https://github.com/mrjackwills/oxker │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ │ "
|
|
||||||
" ╰────────────────────────────────────────────────────────────────────────────────────╯ "
|
" ╰────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
|||||||
+1
-1
@@ -31,6 +31,7 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( l ) or ( L ) save logs to file │ "
|
" │ ( l ) or ( L ) save logs to file │ "
|
||||||
" │ ( 5 ) or ( Page Down ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
" │ ( 5 ) or ( Page Down ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
||||||
" │ ( e ) or ( E ) enter filter mode │ "
|
" │ ( e ) or ( E ) enter filter mode │ "
|
||||||
|
" │ ( m ) or ( M ) enter log search mode │ "
|
||||||
" │ ( 3 ) or ( 6 ) reset container sorting │ "
|
" │ ( 3 ) or ( 6 ) reset container sorting │ "
|
||||||
" │ ( y ) or ( Y ) sort containers by name │ "
|
" │ ( y ) or ( Y ) sort containers by name │ "
|
||||||
" │ ( 0 ) or ( 9 ) sort containers by state │ "
|
" │ ( 0 ) or ( 9 ) sort containers by state │ "
|
||||||
@@ -50,5 +51,4 @@ expression: setup.terminal.backend()
|
|||||||
" │ currently an early work in progress, all and any input appreciated │ "
|
" │ currently an early work in progress, all and any input appreciated │ "
|
||||||
" │ https://github.com/mrjackwills/oxker │ "
|
" │ https://github.com/mrjackwills/oxker │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ │ "
|
|
||||||
" ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
" ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
|||||||
+2
-2
@@ -29,8 +29,9 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( f ) force clear the screen & redraw the gui │ "
|
" │ ( f ) force clear the screen & redraw the gui │ "
|
||||||
" │ ( 5 ) toggle this help information - or click heading │ "
|
" │ ( 5 ) toggle this help information - or click heading │ "
|
||||||
" │ ( m ) or ( M ) save logs to file │ "
|
" │ ( m ) or ( M ) save logs to file │ "
|
||||||
" │ ( 6 ) or ( 7 ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
" │ ( 6 ) or ( # ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
||||||
" │ ( e ) or ( E ) enter filter mode │ "
|
" │ ( e ) or ( E ) enter filter mode │ "
|
||||||
|
" │ ( 8 ) enter log search mode │ "
|
||||||
" │ ( 4 ) or ( 5 ) reset container sorting │ "
|
" │ ( 4 ) or ( 5 ) reset container sorting │ "
|
||||||
" │ ( z ) sort containers by name │ "
|
" │ ( z ) sort containers by name │ "
|
||||||
" │ ( 1 ) sort containers by state │ "
|
" │ ( 1 ) sort containers by state │ "
|
||||||
@@ -50,5 +51,4 @@ expression: setup.terminal.backend()
|
|||||||
" │ currently an early work in progress, all and any input appreciated │ "
|
" │ currently an early work in progress, all and any input appreciated │ "
|
||||||
" │ https://github.com/mrjackwills/oxker │ "
|
" │ https://github.com/mrjackwills/oxker │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
" │ │ "
|
|
||||||
" ╰────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
" ╰────────────────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
|||||||
+2
@@ -28,6 +28,7 @@ expression: setup.terminal.backend()
|
|||||||
" │ ( s ) save logs to file │ "
|
" │ ( s ) save logs to file │ "
|
||||||
" │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
" │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ "
|
||||||
" │ ( F1 ) or ( / ) enter filter mode │ "
|
" │ ( F1 ) or ( / ) enter filter mode │ "
|
||||||
|
" │ ( # ) enter log search mode │ "
|
||||||
" │ ( 0 ) stop sort │ "
|
" │ ( 0 ) stop sort │ "
|
||||||
" │ ( 1 - 9 ) sort by header - or click header │ "
|
" │ ( 1 - 9 ) sort by header - or click header │ "
|
||||||
" │ ( - = ) change log section height │ "
|
" │ ( - = ) change log section height │ "
|
||||||
@@ -38,4 +39,5 @@ expression: setup.terminal.backend()
|
|||||||
" │ currently an early work in progress, all and any input appreciated │ "
|
" │ currently an early work in progress, all and any input appreciated │ "
|
||||||
" │ https://github.com/mrjackwills/oxker │ "
|
" │ https://github.com/mrjackwills/oxker │ "
|
||||||
" │ │ "
|
" │ │ "
|
||||||
|
" │ │ "
|
||||||
" ╰───────────────────────────────────────────────────────────────────────────────────╯ "
|
" ╰───────────────────────────────────────────────────────────────────────────────────╯ "
|
||||||
|
|||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/search_logs.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" Esc clear search term: e ↑ previous ↓ next matches: 2/3"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/search_logs.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" Esc clear search term: e b previous a next matches: 2/3"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/search_logs.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" Esc clear search term: e ↑ previous ↓ next matches: 2/3"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/search_logs.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" Esc clear search term: e ↓ next matches: 1/3"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/search_logs.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" Esc clear search term: z "
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/search_logs.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" Esc clear search term: e ↑ previous matches: 3/3"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/draw_blocks/search_logs.rs
|
||||||
|
expression: setup.terminal.backend()
|
||||||
|
---
|
||||||
|
" Esc clear search term: "
|
||||||
+8
-8
@@ -28,16 +28,16 @@ expression: setup.terminal.backend()
|
|||||||
"│ │ ( s ) save logs to file │ │"
|
"│ │ ( s ) save logs to file │ │"
|
||||||
"│ │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ │"
|
"│ │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ │"
|
||||||
"│ │ ( F1 ) or ( / ) enter filter mode │ │"
|
"│ │ ( F1 ) or ( / ) enter filter mode │ │"
|
||||||
|
"│ │ ( # ) enter log search mode │ │"
|
||||||
"│ │ ( 0 ) stop sort │ │"
|
"│ │ ( 0 ) stop sort │ │"
|
||||||
"│ │ ( 1 - 9 ) sort by header - or click header │ │"
|
"│ │ ( 1 - 9 ) sort by header - or click header │ │"
|
||||||
"│ │ ( - = ) change log section height │ │"
|
"╰────────────────────────────────────│ ( - = ) change log section height │────────────────────────────────────╯"
|
||||||
"╰────────────────────────────────────│ ( \ ) toggle log section visibility │────────────────────────────────────╯"
|
"╭───────────────────────── cpu 03.00%│ ( \ ) toggle log section visibility │──────╮╭────────── ports ───────────╮"
|
||||||
"╭───────────────────────── cpu 03.00%│ ( esc ) close dialog │──────╮╭────────── ports ───────────╮"
|
"│10.00%│ •• │ ( esc ) close dialog │ ││ ip private public│"
|
||||||
"│10.00%│ •• │ ( q ) quit at any time │ ││ ip private public│"
|
"│ │ • • │ ( q ) quit at any time │ ││ 8001 │"
|
||||||
"│ │ • • │ │ ││ 8001 │"
|
"│ │ •• • │ │ ││127.0.0.1 8003 8003│"
|
||||||
"│ │ •• • │ currently an early work in progress, all and any input appreciated │ ││127.0.0.1 8003 8003│"
|
"│ │ • • │ currently an early work in progress, all and any input appreciated │ ││ │"
|
||||||
"│ │ • • │ https://github.com/mrjackwills/oxker │ ││ │"
|
"│ │ •• • • │ https://github.com/mrjackwills/oxker │ ││ │"
|
||||||
"│ │ •• • • │ │ ││ │"
|
|
||||||
"│ │• •• │ │ ││ │"
|
"│ │• •• │ │ ││ │"
|
||||||
"│ │• • ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │"
|
"│ │• • ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │"
|
||||||
"│ │ ││ │ ││ │"
|
"│ │ ││ │ ││ │"
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ pub enum Status {
|
|||||||
Help,
|
Help,
|
||||||
Init,
|
Init,
|
||||||
Logs,
|
Logs,
|
||||||
|
SearchLogs,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Global gui_state, stored in an Arc<Mutex>
|
/// Global gui_state, stored in an Arc<Mutex>
|
||||||
@@ -411,6 +412,16 @@ impl GuiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_logs_panel_selected(&mut self, app_data: &Arc<Mutex<AppData>>) {
|
||||||
|
self.selected_panel = SelectablePanel::Logs;
|
||||||
|
if (app_data.lock().get_container_len() == 0
|
||||||
|
&& self.get_selected_panel() == SelectablePanel::Commands)
|
||||||
|
|| (self.log_height == 0 && self.get_selected_panel() == SelectablePanel::Logs)
|
||||||
|
{
|
||||||
|
self.selected_panel = self.selected_panel.next();
|
||||||
|
}
|
||||||
|
self.rerender.update_draw();
|
||||||
|
}
|
||||||
/// Change to next selectable panel
|
/// Change to next selectable panel
|
||||||
pub fn selectable_panel_next(&mut self, app_data: &Arc<Mutex<AppData>>) {
|
pub fn selectable_panel_next(&mut self, app_data: &Arc<Mutex<AppData>>) {
|
||||||
self.selected_panel = self.selected_panel.next();
|
self.selected_panel = self.selected_panel.next();
|
||||||
|
|||||||
+13
-8
@@ -30,8 +30,8 @@ pub use self::color_match::*;
|
|||||||
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
|
pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status};
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{
|
app_data::{
|
||||||
AppData, Columns, ContainerId, ContainerPorts, CpuTuple, FilterBy, Header, MemTuple,
|
AppData, Columns, ContainerId, ContainerPorts, CpuTuple, FilterBy, Header, LogSearch,
|
||||||
SortedOrder, State,
|
MemTuple, SortedOrder, State,
|
||||||
},
|
},
|
||||||
app_error::AppError,
|
app_error::AppError,
|
||||||
config::{AppColors, Keymap},
|
config::{AppColors, Keymap},
|
||||||
@@ -218,10 +218,6 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while self.is_running.load(Ordering::SeqCst) {
|
while self.is_running.load(Ordering::SeqCst) {
|
||||||
// if self.redraw.get_clear() {
|
|
||||||
// self.terminal.clear().ok();
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
if self.should_redraw(&mut drawn_at, docker_interval_ms) {
|
if self.should_redraw(&mut drawn_at, docker_interval_ms) {
|
||||||
let fd = FrameData::from(&*self);
|
let fd = FrameData::from(&*self);
|
||||||
|
|
||||||
@@ -287,6 +283,7 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Frequent data required by multiple frame drawing functions, can reduce mutex reads by placing it all in here
|
/// Frequent data required by multiple frame drawing functions, can reduce mutex reads by placing it all in here
|
||||||
|
/// TODO refactor this
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct FrameData {
|
pub struct FrameData {
|
||||||
@@ -294,6 +291,7 @@ pub struct FrameData {
|
|||||||
color_logs: bool,
|
color_logs: bool,
|
||||||
columns: Columns,
|
columns: Columns,
|
||||||
container_title: String,
|
container_title: String,
|
||||||
|
log_search: Option<LogSearch>,
|
||||||
delete_confirm: Option<ContainerId>,
|
delete_confirm: Option<ContainerId>,
|
||||||
filter_by: FilterBy,
|
filter_by: FilterBy,
|
||||||
filter_term: Option<String>,
|
filter_term: Option<String>,
|
||||||
@@ -327,6 +325,7 @@ impl From<&Ui> for FrameData {
|
|||||||
filter_by,
|
filter_by,
|
||||||
filter_term: filter_term.cloned(),
|
filter_term: filter_term.cloned(),
|
||||||
has_containers: app_data.get_container_len() > 0,
|
has_containers: app_data.get_container_len() > 0,
|
||||||
|
log_search: app_data.gen_log_search(),
|
||||||
has_error: app_data.get_error(),
|
has_error: app_data.get_error(),
|
||||||
info_text: gui_data.info_box_text.clone(),
|
info_text: gui_data.info_box_text.clone(),
|
||||||
is_loading: gui_data.is_loading(),
|
is_loading: gui_data.is_loading(),
|
||||||
@@ -353,9 +352,12 @@ fn draw_frame(
|
|||||||
fd: &FrameData,
|
fd: &FrameData,
|
||||||
gui_state: &Arc<Mutex<GuiState>>,
|
gui_state: &Arc<Mutex<GuiState>>,
|
||||||
) {
|
) {
|
||||||
|
let contains_filter = fd.status.contains(&Status::Filter);
|
||||||
|
let contains_search_logs = fd.status.contains(&Status::SearchLogs);
|
||||||
|
|
||||||
let whole_layout = Layout::default()
|
let whole_layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(if fd.status.contains(&Status::Filter) {
|
.constraints(if contains_filter || contains_search_logs {
|
||||||
vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)]
|
vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)]
|
||||||
} else {
|
} else {
|
||||||
vec![Constraint::Max(1), Constraint::Min(1)]
|
vec![Constraint::Max(1), Constraint::Min(1)]
|
||||||
@@ -364,9 +366,12 @@ fn draw_frame(
|
|||||||
|
|
||||||
draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap);
|
draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap);
|
||||||
|
|
||||||
// If required, draw filter bar
|
|
||||||
if let Some(rect) = whole_layout.get(2) {
|
if let Some(rect) = whole_layout.get(2) {
|
||||||
|
if contains_filter {
|
||||||
draw_blocks::filter::draw(*rect, colors, f, fd);
|
draw_blocks::filter::draw(*rect, colors, f, fd);
|
||||||
|
} else {
|
||||||
|
draw_blocks::search_logs::draw(*rect, colors, f, fd, keymap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let upper_main = Layout::default()
|
let upper_main = Layout::default()
|
||||||
|
|||||||
Reference in New Issue
Block a user