feat: log search functionality, closes #72
This commit is contained in:
+330
-15
@@ -22,6 +22,12 @@ const ONE_KB: f64 = 1000.0;
|
||||
const ONE_MB: f64 = ONE_KB * 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)]
|
||||
pub struct ContainerId(String);
|
||||
|
||||
@@ -177,7 +183,14 @@ impl<T> StatefulList<T> {
|
||||
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() {
|
||||
self.state.select(Some(
|
||||
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() {
|
||||
self.state.select(Some(
|
||||
self.state
|
||||
@@ -600,6 +613,8 @@ impl LogsTz {
|
||||
pub struct Logs {
|
||||
lines: StatefulList<Text<'static>>,
|
||||
tz: HashSet<LogsTz>,
|
||||
search_results: Vec<usize>,
|
||||
search_term: Option<String>,
|
||||
offset: u16,
|
||||
max_log_len: usize,
|
||||
adjusted_max_width: usize,
|
||||
@@ -614,6 +629,8 @@ impl Default for Logs {
|
||||
lines,
|
||||
tz: HashSet::new(),
|
||||
offset: 0,
|
||||
search_term: None,
|
||||
search_results: vec![],
|
||||
adjusted_max_width: 0,
|
||||
adjust_max_width_text_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 {
|
||||
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
|
||||
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) {
|
||||
self.max_log_len = self.max_log_len.max(line.width());
|
||||
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);
|
||||
}
|
||||
|
||||
/// Scroll lines down by one
|
||||
pub fn next(&mut self) {
|
||||
self.lines.next();
|
||||
}
|
||||
|
||||
/// Scroll lines up by one
|
||||
pub fn previous(&mut self) {
|
||||
self.lines.previous();
|
||||
}
|
||||
|
||||
/// Go to the end of the lines
|
||||
pub fn end(&mut self) {
|
||||
self.lines.end();
|
||||
}
|
||||
|
||||
/// Go to the start of the lines
|
||||
pub fn start(&mut self) {
|
||||
self.lines.start();
|
||||
}
|
||||
|
||||
/// Get total number of log lines
|
||||
pub const fn len(&self) -> usize {
|
||||
self.lines.items.len()
|
||||
}
|
||||
@@ -938,7 +1138,7 @@ mod tests {
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app_data::{ContainerImage, Logs, LogsTz, RunningState},
|
||||
app_data::{ContainerImage, LogSearch, Logs, LogsTz, RunningState},
|
||||
ui::log_sanitizer,
|
||||
};
|
||||
|
||||
@@ -1075,9 +1275,9 @@ mod tests {
|
||||
let mut logs = Logs::default();
|
||||
let line = log_sanitizer::remove_ansi(input);
|
||||
|
||||
logs.insert(Text::from(line.clone()), tz.clone());
|
||||
logs.insert(Text::from(line.clone()), tz.clone());
|
||||
logs.insert(Text::from(line), tz);
|
||||
logs.insert(Text::from(line.clone()), tz.clone(), true);
|
||||
logs.insert(Text::from(line.clone()), tz.clone(), true);
|
||||
logs.insert(Text::from(line), tz, true);
|
||||
|
||||
assert_eq!(logs.lines.items.len(), 1);
|
||||
|
||||
@@ -1085,9 +1285,9 @@ mod tests {
|
||||
let (tz, _) = LogsTz::splitter(input);
|
||||
let line = log_sanitizer::remove_ansi(input);
|
||||
|
||||
logs.insert(Text::from(line.clone()), tz.clone());
|
||||
logs.insert(Text::from(line.clone()), tz.clone());
|
||||
logs.insert(Text::from(line), tz);
|
||||
logs.insert(Text::from(line.clone()), tz.clone(), true);
|
||||
logs.insert(Text::from(line.clone()), tz.clone(), true);
|
||||
logs.insert(Text::from(line), tz, true);
|
||||
|
||||
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 (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 (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 (tz, _) = LogsTz::splitter(&input);
|
||||
logs.insert(Text::from(input), tz);
|
||||
logs.insert(Text::from(input), tz, true);
|
||||
|
||||
logs.offset = 43;
|
||||
let result = logs.get_visible_logs(
|
||||
@@ -1188,14 +1388,14 @@ mod tests {
|
||||
|
||||
let input = "short".to_owned();
|
||||
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);
|
||||
assert!(result.is_none());
|
||||
|
||||
let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned();
|
||||
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);
|
||||
assert_eq!(result, Some(" 0/51 → ".to_owned()));
|
||||
@@ -1211,4 +1411,119 @@ mod tests {
|
||||
let result = logs.get_scroll_title(10);
|
||||
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)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+85
-64
@@ -171,6 +171,19 @@ impl AppData {
|
||||
(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
|
||||
fn can_insert(&self, container: &ContainerItem) -> bool {
|
||||
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
|
||||
fn re_filter(&mut self) {
|
||||
self.containers.items.append(&mut self.hidden_containers);
|
||||
@@ -448,15 +486,8 @@ impl AppData {
|
||||
self.rerender.update_draw();
|
||||
}
|
||||
|
||||
/// Select the next container
|
||||
pub fn containers_next(&mut self) {
|
||||
self.containers.next();
|
||||
self.rerender.update_draw();
|
||||
}
|
||||
|
||||
/// select the previous container
|
||||
pub fn containers_previous(&mut self) {
|
||||
self.containers.previous();
|
||||
pub fn containers_scroll(&mut self, scroll: &ScrollDirection) {
|
||||
self.containers.scroll(scroll);
|
||||
self.rerender.update_draw();
|
||||
}
|
||||
|
||||
@@ -576,17 +607,10 @@ impl AppData {
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
i.docker_controls.next();
|
||||
self.rerender.update_draw();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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();
|
||||
i.docker_controls.scroll(scroll);
|
||||
// i.docker_controls.next();
|
||||
self.rerender.update_draw();
|
||||
}
|
||||
}
|
||||
@@ -643,34 +667,30 @@ impl AppData {
|
||||
.and_then(|i| i.logs.get_scroll_title(width))
|
||||
}
|
||||
|
||||
/// Increase the logs offset, basically moving an invisible cursor back
|
||||
pub fn log_back(&mut self) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
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() {
|
||||
i.logs.forward(width);
|
||||
self.rerender.update_draw();
|
||||
pub fn logs_horizontal_scroll(&mut self, sd: &ScrollDirection, width: u16) {
|
||||
match sd {
|
||||
ScrollDirection::Next => {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.logs.forward(width);
|
||||
self.rerender.update_draw();
|
||||
}
|
||||
}
|
||||
ScrollDirection::Previous => {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.logs.back();
|
||||
self.rerender.update_draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// select next selected log line
|
||||
pub fn log_next(&mut self) {
|
||||
pub fn log_scroll(&mut self, scroll: &ScrollDirection) {
|
||||
if let Some(i) = self.get_mut_selected_container() {
|
||||
i.logs.next();
|
||||
self.rerender.update_draw();
|
||||
}
|
||||
}
|
||||
|
||||
/// select previous selected log line
|
||||
pub fn log_previous(&mut self) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -857,7 +877,7 @@ impl AppData {
|
||||
// If removed container is currently selected, then change selected to previous
|
||||
// This will default to 0 in any edge cases
|
||||
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
|
||||
if self.containers.items.get(index).is_some() {
|
||||
@@ -959,6 +979,8 @@ impl AppData {
|
||||
let format = self.config.timestamp_format.clone();
|
||||
let config_tz = self.config.timezone.clone();
|
||||
|
||||
let cs = self.config.log_search_case_sensitive;
|
||||
|
||||
let show_timestamp = self.config.show_timestamp;
|
||||
|
||||
if let Some(container) = self.get_any_container_by_id(id) {
|
||||
@@ -985,7 +1007,7 @@ impl AppData {
|
||||
} else {
|
||||
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
|
||||
@@ -1422,7 +1444,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// 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();
|
||||
assert_eq!(result, Some(ContainerId::from("1")));
|
||||
let result = app_data.get_selected_container_id_state_name();
|
||||
@@ -1445,7 +1467,7 @@ mod tests {
|
||||
|
||||
// Advance list state by 1
|
||||
app_data.containers_start();
|
||||
app_data.containers_next();
|
||||
app_data.containers.scroll(&ScrollDirection::Next);
|
||||
|
||||
let result = app_data.get_container_state();
|
||||
assert_eq!(result.selected(), Some(1));
|
||||
@@ -1489,7 +1511,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// 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();
|
||||
assert_eq!(result, Some(ContainerId::from("3")));
|
||||
let result = app_data.get_selected_container_id_state_name();
|
||||
@@ -1510,7 +1532,7 @@ mod tests {
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
|
||||
app_data.containers_end();
|
||||
app_data.containers_previous();
|
||||
app_data.containers.scroll(&ScrollDirection::Previous);
|
||||
let result = app_data.get_container_state();
|
||||
assert_eq!(result.selected(), Some(1));
|
||||
assert_eq!(result.offset(), 0);
|
||||
@@ -1526,7 +1548,7 @@ mod tests {
|
||||
assert_eq!(result, None);
|
||||
|
||||
app_data.containers.start();
|
||||
app_data.containers.next();
|
||||
app_data.containers.scroll(&ScrollDirection::Next);
|
||||
|
||||
let result = app_data.get_selected_container();
|
||||
assert_eq!(result, Some(&containers[1]));
|
||||
@@ -1613,7 +1635,7 @@ mod tests {
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
app_data.containers_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();
|
||||
assert_eq!(result, Some(DockerCommand::Restart));
|
||||
@@ -1631,7 +1653,7 @@ mod tests {
|
||||
assert_eq!(result, Some(DockerCommand::Delete));
|
||||
|
||||
// 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();
|
||||
assert_eq!(result, Some(DockerCommand::Delete));
|
||||
}
|
||||
@@ -1643,14 +1665,14 @@ mod tests {
|
||||
let mut app_data = gen_appdata(&containers);
|
||||
app_data.containers_start();
|
||||
app_data.docker_controls_end();
|
||||
app_data.docker_controls_previous();
|
||||
app_data.docker_controls_scroll(&ScrollDirection::Previous);
|
||||
|
||||
let result = app_data.selected_docker_controls();
|
||||
assert_eq!(result, Some(DockerCommand::Stop));
|
||||
|
||||
// previous has no effect when at 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();
|
||||
assert_eq!(result, Some(DockerCommand::Pause));
|
||||
}
|
||||
@@ -1914,7 +1936,7 @@ mod tests {
|
||||
assert_eq!(result, " 3/3 - container_1 - image_1");
|
||||
|
||||
// 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();
|
||||
assert_eq!(result, " 2/3 - container_1 - image_1");
|
||||
}
|
||||
@@ -1935,7 +1957,7 @@ mod tests {
|
||||
assert_eq!(result, " - container_1 - image_1");
|
||||
|
||||
// change container
|
||||
app_data.containers_next();
|
||||
app_data.containers_scroll(&ScrollDirection::Next);
|
||||
let result = app_data.get_log_title();
|
||||
assert_eq!(result, " - container_2 - image_2");
|
||||
|
||||
@@ -1946,7 +1968,7 @@ mod tests {
|
||||
assert_eq!(result, " 3/3 - container_2 - image_2");
|
||||
|
||||
// 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();
|
||||
assert_eq!(result, " 2/3 - container_2 - image_2");
|
||||
}
|
||||
@@ -2053,8 +2075,7 @@ mod tests {
|
||||
let result = app_data.get_log_title();
|
||||
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();
|
||||
assert!(result.is_some());
|
||||
assert_eq!(result.as_ref().unwrap().selected(), Some(1));
|
||||
@@ -2063,7 +2084,7 @@ mod tests {
|
||||
let result = app_data.get_log_title();
|
||||
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();
|
||||
assert!(result.is_some());
|
||||
assert_eq!(result.as_ref().unwrap().selected(), Some(2));
|
||||
@@ -2071,7 +2092,7 @@ mod tests {
|
||||
|
||||
let result = app_data.get_log_title();
|
||||
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();
|
||||
assert!(result.is_some());
|
||||
@@ -2102,7 +2123,7 @@ mod tests {
|
||||
let result = app_data.get_log_title();
|
||||
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();
|
||||
assert!(result.is_some());
|
||||
@@ -2111,7 +2132,7 @@ mod tests {
|
||||
let result = app_data.get_log_title();
|
||||
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();
|
||||
assert!(result.is_some());
|
||||
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
||||
@@ -2119,7 +2140,7 @@ mod tests {
|
||||
let result = app_data.get_log_title();
|
||||
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();
|
||||
assert!(result.is_some());
|
||||
assert_eq!(result.as_ref().unwrap().selected(), Some(0));
|
||||
@@ -2413,7 +2434,7 @@ mod tests {
|
||||
}
|
||||
|
||||
for _ in 0..=500 {
|
||||
app_data.log_next();
|
||||
app_data.log_scroll(&ScrollDirection::Next);
|
||||
}
|
||||
let result = app_data.get_logs(
|
||||
Size {
|
||||
|
||||
Reference in New Issue
Block a user