mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 10:04:12 +02:00
more grouping prep
This commit is contained in:
parent
3943407b9b
commit
0fa90510cb
14 changed files with 251 additions and 185 deletions
24
src/app.rs
24
src/app.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::grouping::LogGrouping;
|
use crate::grouping::{group_lines_by, FilterGrouping, LogGrouping};
|
||||||
use crate::logfile::{LogFile, LogLine, LogLineNumber};
|
use crate::logfile::{LogFile, LogLine, LogLineNumber};
|
||||||
use crate::logs::ParsedLogs;
|
use crate::logs::ParsedLogs;
|
||||||
use crate::matcher::MatchResult;
|
use crate::matcher::MatchResult;
|
||||||
|
|
@ -12,8 +12,7 @@ use time::OffsetDateTime;
|
||||||
|
|
||||||
pub struct App<'logs> {
|
pub struct App<'logs> {
|
||||||
pub lines: &'logs ParsedLogs<'logs>,
|
pub lines: &'logs ParsedLogs<'logs>,
|
||||||
pub matches: Vec<LogGrouping<'logs, MatchResult>>,
|
pub matches: &'logs [LogGrouping<'logs, MatchResult>],
|
||||||
pub all: LogGrouping<'logs, MatchResult>,
|
|
||||||
pub log_file: &'logs LogFile,
|
pub log_file: &'logs LogFile,
|
||||||
pub unmatched_count: usize,
|
pub unmatched_count: usize,
|
||||||
}
|
}
|
||||||
|
|
@ -27,15 +26,15 @@ impl<'logs> App<'logs> {
|
||||||
self.log_file.nth(index)
|
self.log_file.nth(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lines_by_request<'a: 'logs>(
|
pub fn lines_by_request<'s>(
|
||||||
&'a self,
|
&self,
|
||||||
request_id: &'a str,
|
request_id: &'s str,
|
||||||
) -> impl Iterator<Item = &'logs LogLine<'logs>> + use<'a, 'logs> {
|
) -> impl Iterator<Item = &'logs LogLine<'logs>> + use<'logs, 's> {
|
||||||
self.lines
|
self.lines
|
||||||
.find_lines(move |line| line.request_id == request_id)
|
.find_lines(move |line| line.request_id == request_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn error_lines(&self) -> impl Iterator<Item = (&'logs str, &JsonError)> {
|
pub fn error_lines(&self) -> impl Iterator<Item = (&'logs str, &'logs JsonError)> + use<'logs> {
|
||||||
self.lines.errors().iter().map(|(line_number, error)| {
|
self.lines.errors().iter().map(|(line_number, error)| {
|
||||||
(self.log_file.nth(*line_number).unwrap_or_default(), error)
|
(self.log_file.nth(*line_number).unwrap_or_default(), error)
|
||||||
})
|
})
|
||||||
|
|
@ -96,6 +95,15 @@ impl<'logs> LineSet<'logs> {
|
||||||
.parts()
|
.parts()
|
||||||
.all(|filter_part| filter_part.is_match(&line.message))
|
.all(|filter_part| filter_part.is_match(&line.message))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iter<'a>(&'a self) -> impl Iterator<Item = &'logs LogLine<'logs>> + use<'a, 'logs> {
|
||||||
|
self.lines.iter().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn group_by<G: FilterGrouping>(&self) -> Vec<LogGrouping<G::Result>> {
|
||||||
|
let _grouped = group_lines_by(self.lines.iter().copied(), G::filter);
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,37 @@
|
||||||
|
mod unique;
|
||||||
|
|
||||||
use crate::app::{Filter, LineSet};
|
use crate::app::{Filter, LineSet};
|
||||||
use crate::logfile::LogLine;
|
use crate::logfile::LogLine;
|
||||||
use crate::timegraph::{SparkLine, TimeGraph};
|
use crate::timegraph::{SparkLine, TimeGraph};
|
||||||
use ratatui::layout::{Alignment, Constraint};
|
use ratatui::layout::{Alignment, Constraint};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::cell::OnceCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
pub use unique::*;
|
||||||
|
|
||||||
pub trait GroupedLine {
|
pub trait Grouping {
|
||||||
const HEADER: &'static [(&'static str, Alignment)];
|
const HEADER: &'static [(&'static str, Alignment)];
|
||||||
|
|
||||||
const WIDTHS: &'static [Constraint];
|
const WIDTHS: &'static [Constraint];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait FilterGrouping: Grouping {
|
||||||
|
type Result: Grouping;
|
||||||
|
type Identifier: Ord;
|
||||||
|
|
||||||
|
fn filter(line: &LogLine) -> Self::Identifier;
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::len_without_is_empty)]
|
#[allow(clippy::len_without_is_empty)]
|
||||||
pub trait GroupingResult {
|
pub trait GroupingResult {
|
||||||
type Lines: GroupedLine;
|
type Grouping: Grouping;
|
||||||
|
type Next: FilterGrouping;
|
||||||
|
|
||||||
fn len(&self) -> usize;
|
fn height(&self) -> usize {
|
||||||
fn lines<'a>(&'a self) -> impl Iterator<Item = &'a Self::Lines> + use<'a, Self>
|
1
|
||||||
where
|
}
|
||||||
<Self as GroupingResult>::Lines: 'a;
|
|
||||||
|
|
||||||
fn matches(&self, filter: &Filter) -> bool;
|
fn matches(&self, filter: &Filter) -> bool;
|
||||||
|
|
||||||
|
|
@ -31,53 +42,47 @@ pub struct LogGrouping<'logs, G> {
|
||||||
pub name: Option<&'static str>,
|
pub name: Option<&'static str>,
|
||||||
pub result: Option<G>,
|
pub result: Option<G>,
|
||||||
pub count: usize,
|
pub count: usize,
|
||||||
pub all: LineSet<'logs>,
|
pub lines: LineSet<'logs>,
|
||||||
pub grouped: Vec<LineSet<'logs>>,
|
pub by_identifier: OnceCell<Vec<LineSet<'logs>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'logs, G: GroupingResult> LogGrouping<'logs, G> {
|
impl<'logs, G: GroupingResult> LogGrouping<'logs, G> {
|
||||||
pub fn new(result: G, lines: Vec<&'logs LogLine<'logs>>) -> Self {
|
pub fn new(result: G, lines: Vec<&'logs LogLine<'logs>>) -> Self {
|
||||||
let count = lines.len();
|
let count = lines.len();
|
||||||
let grouped = group_lines_by(lines.iter().copied(), LogLine::identity);
|
let lines = LineSet::new(lines);
|
||||||
let all = LineSet::new(lines);
|
|
||||||
|
|
||||||
LogGrouping {
|
LogGrouping {
|
||||||
name: None,
|
name: None,
|
||||||
result: Some(result),
|
result: Some(result),
|
||||||
count,
|
count,
|
||||||
grouped,
|
lines,
|
||||||
all,
|
by_identifier: OnceCell::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn named(name: &'static str, lines: Vec<&'logs LogLine<'logs>>) -> Self {
|
pub fn named(name: &'static str, lines: Vec<&'logs LogLine<'logs>>) -> Self {
|
||||||
let count = lines.len();
|
let count = lines.len();
|
||||||
let grouped = group_lines_by(lines.iter().copied(), LogLine::identity);
|
let lines = LineSet::new(lines);
|
||||||
let all = LineSet::new(lines);
|
|
||||||
|
|
||||||
LogGrouping {
|
LogGrouping {
|
||||||
name: Some(name),
|
name: Some(name),
|
||||||
result: None,
|
result: None,
|
||||||
count,
|
count,
|
||||||
grouped,
|
lines,
|
||||||
all,
|
by_identifier: OnceCell::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sparkline(&self, time_range: RangeInclusive<OffsetDateTime>) -> &SparkLine {
|
pub fn sparkline(&self, time_range: RangeInclusive<OffsetDateTime>) -> &SparkLine {
|
||||||
self.all.sparkline(time_range)
|
self.lines.sparkline(time_range)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn histogram(&self, time_range: RangeInclusive<OffsetDateTime>) -> &TimeGraph {
|
pub fn histogram(&self, time_range: RangeInclusive<OffsetDateTime>) -> &TimeGraph {
|
||||||
self.all.histogram(time_range)
|
self.lines.histogram(time_range)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn row_count(&self) -> usize {
|
pub fn row_count(&self) -> usize {
|
||||||
self.result.as_ref().map(|res| res.len()).unwrap_or(1)
|
self.result.as_ref().map(|res| res.height()).unwrap_or(1)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &LineSet<'logs>> {
|
|
||||||
self.grouped.iter()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches(&self, filter: &Filter) -> bool {
|
pub fn matches(&self, filter: &Filter) -> bool {
|
||||||
|
|
@ -93,6 +98,11 @@ impl<'logs, G: GroupingResult> LogGrouping<'logs, G> {
|
||||||
pub fn count(&self) -> usize {
|
pub fn count(&self) -> usize {
|
||||||
self.count
|
self.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn by_identifier(&self) -> &[LineSet<'logs>] {
|
||||||
|
self.by_identifier
|
||||||
|
.get_or_init(|| group_lines_by(self.lines.iter(), LogLine::identity))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn group_lines_by<'logs, I, F, K>(indices: I, f: F) -> Vec<LineSet<'logs>>
|
pub fn group_lines_by<'logs, I, F, K>(indices: I, f: F) -> Vec<LineSet<'logs>>
|
||||||
57
src/grouping/unique.rs
Normal file
57
src/grouping/unique.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::app::Filter;
|
||||||
|
use crate::grouping::{FilterGrouping, Grouping, GroupingResult};
|
||||||
|
use crate::logfile::LogLine;
|
||||||
|
use ratatui::layout::{Alignment, Constraint};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
pub struct UniqueLog;
|
||||||
|
|
||||||
|
impl Grouping for UniqueLog {
|
||||||
|
const HEADER: &'static [(&'static str, Alignment)] = &[
|
||||||
|
("Level", Alignment::Left),
|
||||||
|
("App", Alignment::Left),
|
||||||
|
("Message", Alignment::Left),
|
||||||
|
];
|
||||||
|
|
||||||
|
const WIDTHS: &'static [Constraint] = &[
|
||||||
|
Constraint::Min(10),
|
||||||
|
Constraint::Min(20),
|
||||||
|
Constraint::Percentage(100),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilterGrouping for UniqueLog {
|
||||||
|
type Result = UniqueLog;
|
||||||
|
type Identifier = u64;
|
||||||
|
|
||||||
|
fn filter(line: &LogLine) -> Self::Identifier {
|
||||||
|
line.identity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UniqueGrouping<'a> {
|
||||||
|
pub line: &'a LogLine<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GroupingResult for UniqueGrouping<'a> {
|
||||||
|
type Grouping = UniqueLog;
|
||||||
|
type Next = UniqueLog;
|
||||||
|
|
||||||
|
fn matches(&self, filter: &Filter) -> bool {
|
||||||
|
if filter.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
filter
|
||||||
|
.parts()
|
||||||
|
.all(|filter_part| filter_part.is_match(&self.line.message))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self) -> impl Iterator<Item = Cow<str>> {
|
||||||
|
[
|
||||||
|
Cow::from(self.line.level.as_str()),
|
||||||
|
Cow::from(self.line.app.as_ref()),
|
||||||
|
self.line.display(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,10 +32,10 @@ impl<'logfile> ParsedLogs<'logfile> {
|
||||||
&self.error_lines
|
&self.error_lines
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_lines<'a: 'logfile, F: Fn(&'logfile LogLine<'logfile>) -> bool + 'logfile>(
|
pub fn find_lines<'a: 'f, 'f, F: Fn(&'a LogLine<'logfile>) -> bool + 'f>(
|
||||||
&'a self,
|
&'a self,
|
||||||
filter: F,
|
filter: F,
|
||||||
) -> impl Iterator<Item = &'logfile LogLine<'logfile>> + use<'a, 'logfile, F> {
|
) -> impl Iterator<Item = &'a LogLine<'logfile>> + use<'a, 'logfile, F> {
|
||||||
self.parsed.iter().filter(move |line| filter(line))
|
self.parsed.iter().filter(move |line| filter(line))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,8 @@ fn main() -> MainResult {
|
||||||
.map(|(result, lines)| LogGrouping::new(result, lines))
|
.map(|(result, lines)| LogGrouping::new(result, lines))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
matches.insert(0, all); // todo: seems unoptimized
|
||||||
|
|
||||||
let unmatched_count = unmatched.count();
|
let unmatched_count = unmatched.count();
|
||||||
if unmatched_count > 0 {
|
if unmatched_count > 0 {
|
||||||
matches.push(unmatched);
|
matches.push(unmatched);
|
||||||
|
|
@ -167,8 +169,7 @@ fn main() -> MainResult {
|
||||||
|
|
||||||
let app = App {
|
let app = App {
|
||||||
lines: &parsed_log,
|
lines: &parsed_log,
|
||||||
matches,
|
matches: &matches,
|
||||||
all,
|
|
||||||
log_file: &log_file,
|
log_file: &log_file,
|
||||||
unmatched_count,
|
unmatched_count,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::app::Filter;
|
use crate::app::Filter;
|
||||||
use crate::grouping::{GroupedLine, GroupingResult};
|
use crate::grouping::{Grouping, GroupingResult, UniqueLog};
|
||||||
use crate::logfile::logline::{Exception, LogLine};
|
use crate::logfile::logline::{Exception, LogLine};
|
||||||
use crate::logfile::LineNumber;
|
use crate::logfile::LineNumber;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
|
|
@ -211,7 +211,7 @@ impl From<Vec<LoggingStatementWithPathPrefix>> for MatchResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupedLine for LoggingStatementWithPathPrefix {
|
impl Grouping for LoggingStatementWithPathPrefix {
|
||||||
const HEADER: &'static [(&'static str, Alignment)] = &[
|
const HEADER: &'static [(&'static str, Alignment)] = &[
|
||||||
("Statement", Alignment::Left),
|
("Statement", Alignment::Left),
|
||||||
("File", Alignment::Left),
|
("File", Alignment::Left),
|
||||||
|
|
@ -222,25 +222,17 @@ impl GroupedLine for LoggingStatementWithPathPrefix {
|
||||||
Constraint::Percentage(70),
|
Constraint::Percentage(70),
|
||||||
Constraint::Percentage(30),
|
Constraint::Percentage(30),
|
||||||
Constraint::Min(6),
|
Constraint::Min(6),
|
||||||
Constraint::Length(10),
|
|
||||||
Constraint::Min(10),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupingResult for MatchResult {
|
impl GroupingResult for MatchResult {
|
||||||
type Lines = LoggingStatementWithPathPrefix;
|
type Grouping = LoggingStatementWithPathPrefix;
|
||||||
|
type Next = UniqueLog;
|
||||||
|
|
||||||
fn len(&self) -> usize {
|
fn height(&self) -> usize {
|
||||||
self.count()
|
self.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lines<'a>(&'a self) -> impl Iterator<Item = &'a Self::Lines> + use<'a>
|
|
||||||
where
|
|
||||||
<Self as GroupingResult>::Lines: 'a,
|
|
||||||
{
|
|
||||||
self.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, filter: &Filter) -> bool {
|
fn matches(&self, filter: &Filter) -> bool {
|
||||||
if filter.is_empty() {
|
if filter.is_empty() {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -269,7 +261,7 @@ impl GroupingResult for MatchResult {
|
||||||
let mut paths = String::new();
|
let mut paths = String::new();
|
||||||
let mut lines = String::new();
|
let mut lines = String::new();
|
||||||
|
|
||||||
for statement in self.lines() {
|
for statement in self.iter() {
|
||||||
writeln!(&mut message, "{}", statement.message()).ok();
|
writeln!(&mut message, "{}", statement.message()).ok();
|
||||||
writeln!(&mut paths, "{}", statement.path()).ok();
|
writeln!(&mut paths, "{}", statement.path()).ok();
|
||||||
writeln!(&mut lines, "{}", statement.line()).ok();
|
writeln!(&mut lines, "{}", statement.line()).ok();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::app::{App, Filter};
|
use crate::app::Filter;
|
||||||
use crate::logfile::logline::{format_time, LogLine};
|
use crate::logfile::logline::{format_time, LogLine};
|
||||||
use crate::ui::state::GroupedLogGrouping;
|
use crate::ui::state::GroupedLogGrouping;
|
||||||
use crate::ui::style::TABLE_HEADER_STYLE;
|
use crate::ui::style::TABLE_HEADER_STYLE;
|
||||||
|
|
@ -10,28 +10,25 @@ use ratatui::prelude::{StatefulWidget, Widget};
|
||||||
use ratatui::text::Text;
|
use ratatui::text::Text;
|
||||||
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||||
|
|
||||||
pub struct GroupedLogs<'a> {
|
pub struct LogsByIdentifier<'a> {
|
||||||
lines: &'a [&'a LogLine<'a>],
|
lines: &'a [&'a LogLine<'a>],
|
||||||
app: &'a App<'a>,
|
|
||||||
filter: &'a Filter,
|
filter: &'a Filter,
|
||||||
grouping: GroupedLogGrouping,
|
grouping: GroupedLogGrouping,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grouped_logs<'a>(
|
pub fn logs_by_identifier<'a>(
|
||||||
app: &'a App<'a>,
|
|
||||||
lines: &'a [&'a LogLine<'a>],
|
lines: &'a [&'a LogLine<'a>],
|
||||||
filter: &'a Filter,
|
filter: &'a Filter,
|
||||||
grouping: GroupedLogGrouping,
|
grouping: GroupedLogGrouping,
|
||||||
) -> GroupedLogs<'a> {
|
) -> LogsByIdentifier<'a> {
|
||||||
GroupedLogs {
|
LogsByIdentifier {
|
||||||
lines,
|
lines,
|
||||||
app,
|
|
||||||
filter,
|
filter,
|
||||||
grouping,
|
grouping,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatefulWidget for GroupedLogs<'_> {
|
impl StatefulWidget for LogsByIdentifier<'_> {
|
||||||
type State = ScrollbarTableState;
|
type State = ScrollbarTableState;
|
||||||
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
|
||||||
|
|
@ -45,7 +42,7 @@ impl StatefulWidget for GroupedLogs<'_> {
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|line| line.matches(self.filter))
|
.filter(|line| line.matches(self.filter))
|
||||||
.nth(state.selected())
|
.nth(state.selected())
|
||||||
.unwrap_or(self.app.lines.first());
|
.unwrap_or(self.lines.first().unwrap());
|
||||||
|
|
||||||
let par = match self.grouping {
|
let par = match self.grouping {
|
||||||
GroupedLogGrouping::Message => Paragraph::new(format!(
|
GroupedLogGrouping::Message => Paragraph::new(format!(
|
||||||
|
|
@ -5,7 +5,7 @@ use ratatui::layout::Constraint;
|
||||||
use ratatui::text::Text;
|
use ratatui::text::Text;
|
||||||
use ratatui::widgets::{Cell, Row};
|
use ratatui::widgets::{Cell, Row};
|
||||||
|
|
||||||
pub fn error_list<'a>(app: &'a App<'a>) -> ScrollbarTable<'a> {
|
pub fn error_list<'a>(app: &App<'a>) -> ScrollbarTable<'a> {
|
||||||
let header = [Text::from("Error"), Text::from("Line")]
|
let header = [Text::from("Error"), Text::from("Line")]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Cell::from)
|
.map(Cell::from)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub enum FooterParams<'a> {
|
||||||
FilterInput { page: UiPage, filter: &'a Filter },
|
FilterInput { page: UiPage, filter: &'a Filter },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn footer<'a>(app: &App<'a>, params: FooterParams<'a>) -> Table<'a> {
|
pub fn footer<'a>(app: &App, params: FooterParams<'a>) -> Table<'a> {
|
||||||
let footer_style = Style::default()
|
let footer_style = Style::default()
|
||||||
.bg(tailwind::BLACK)
|
.bg(tailwind::BLACK)
|
||||||
.fg(tailwind::GREEN.c600);
|
.fg(tailwind::GREEN.c600);
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,19 @@
|
||||||
use crate::app::Filter;
|
use crate::app::Filter;
|
||||||
use crate::grouping::{GroupedLine, GroupingResult, LogGrouping};
|
use crate::grouping::{Grouping, GroupingResult, LogGrouping};
|
||||||
use crate::ui::style::TABLE_HEADER_STYLE;
|
use crate::ui::style::TABLE_HEADER_STYLE;
|
||||||
use crate::ui::table::ScrollbarTable;
|
use crate::ui::table::ScrollbarTable;
|
||||||
use ratatui::prelude::*;
|
use ratatui::prelude::*;
|
||||||
use ratatui::widgets::{Cell, Row};
|
use ratatui::widgets::{Cell, Row};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::iter::once;
|
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
pub fn grouping_list<'a, G: GroupingResult>(
|
pub fn grouping_list<'a, G: GroupingResult>(
|
||||||
all: &'a LogGrouping<'a, G>,
|
|
||||||
items: &'a [LogGrouping<'a, G>],
|
items: &'a [LogGrouping<'a, G>],
|
||||||
time_range: RangeInclusive<OffsetDateTime>,
|
time_range: RangeInclusive<OffsetDateTime>,
|
||||||
filter: &Filter,
|
filter: &Filter,
|
||||||
) -> ScrollbarTable<'a> {
|
) -> ScrollbarTable<'a> {
|
||||||
let header = G::Lines::HEADER
|
let header = G::Grouping::HEADER
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.chain([("Time", Alignment::Left), ("Count", Alignment::Left)]);
|
.chain([("Time", Alignment::Left), ("Count", Alignment::Left)]);
|
||||||
|
|
@ -28,10 +26,14 @@ pub fn grouping_list<'a, G: GroupingResult>(
|
||||||
.height(1);
|
.height(1);
|
||||||
|
|
||||||
ScrollbarTable::new(
|
ScrollbarTable::new(
|
||||||
once(all)
|
items
|
||||||
.chain(items.iter().filter(|result| result.matches(filter)))
|
.iter()
|
||||||
|
.filter(|result| result.matches(filter))
|
||||||
.map(|result| grouped_row(result, time_range.clone())),
|
.map(|result| grouped_row(result, time_range.clone())),
|
||||||
G::Lines::WIDTHS,
|
G::Grouping::WIDTHS
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.chain([Constraint::Length(10), Constraint::Min(10)]),
|
||||||
)
|
)
|
||||||
.header(header)
|
.header(header)
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +48,7 @@ fn grouped_row<'a, G: GroupingResult>(
|
||||||
Cow::from(grouping.sparkline(time_range)),
|
Cow::from(grouping.sparkline(time_range)),
|
||||||
Cow::from(grouping.count().to_string()),
|
Cow::from(grouping.count().to_string()),
|
||||||
]);
|
]);
|
||||||
Row::new(columns).height(match_result.len() as u16)
|
Row::new(columns).height(match_result.height() as u16)
|
||||||
} else {
|
} else {
|
||||||
Row::new([
|
Row::new([
|
||||||
Text::from(grouping.name.unwrap_or_default()),
|
Text::from(grouping.name.unwrap_or_default()),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::app::App;
|
||||||
use crate::ui::find_hit_row;
|
use crate::ui::find_hit_row;
|
||||||
use crate::ui::state::{Mode, UiPage, UiState};
|
use crate::ui::state::{Mode, UiPage, UiState};
|
||||||
use ratatui::crossterm::event;
|
use ratatui::crossterm::event;
|
||||||
|
|
@ -29,7 +30,7 @@ pub enum PopMode {
|
||||||
Word,
|
Word,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result<Option<UiEvent>> {
|
pub fn handle_events(page: UiPage, ui_state: &UiState, app: &App) -> io::Result<Option<UiEvent>> {
|
||||||
if event::poll(Duration::from_millis(50))? {
|
if event::poll(Duration::from_millis(50))? {
|
||||||
match event::read()? {
|
match event::read()? {
|
||||||
Event::Key(key) if key.kind == event::KeyEventKind::Press => {
|
Event::Key(key) if key.kind == event::KeyEventKind::Press => {
|
||||||
|
|
@ -76,7 +77,7 @@ pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result<Option<UiEv
|
||||||
MouseEventKind::ScrollUp => Some(UiEvent::Scroll(-1)),
|
MouseEventKind::ScrollUp => Some(UiEvent::Scroll(-1)),
|
||||||
MouseEventKind::ScrollDown => Some(UiEvent::Scroll(1)),
|
MouseEventKind::ScrollDown => Some(UiEvent::Scroll(1)),
|
||||||
MouseEventKind::Down(MouseButton::Left) => {
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
find_hit_row(mouse.row, ui_state).map(UiEvent::Enter)
|
find_hit_row(mouse.row, ui_state, app).map(UiEvent::Enter)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::error::UiError;
|
use crate::error::UiError;
|
||||||
use crate::matcher::MatchResult;
|
use crate::matcher::MatchResult;
|
||||||
|
use crate::ui::by_identifier::logs_by_identifier;
|
||||||
use crate::ui::error_list::error_list;
|
use crate::ui::error_list::error_list;
|
||||||
use crate::ui::footer::footer;
|
use crate::ui::footer::footer;
|
||||||
use crate::ui::grouped_logs::grouped_logs;
|
|
||||||
use crate::ui::grouping_list::grouping_list;
|
use crate::ui::grouping_list::grouping_list;
|
||||||
use crate::ui::histogram::UiHistogram;
|
use crate::ui::histogram::UiHistogram;
|
||||||
use crate::ui::input::handle_events;
|
use crate::ui::input::handle_events;
|
||||||
|
use crate::ui::single_group::single_group;
|
||||||
use crate::ui::single_log::single_log;
|
use crate::ui::single_log::single_log;
|
||||||
use crate::ui::single_match::grouped_lines;
|
|
||||||
use crate::ui::state::{
|
use crate::ui::state::{
|
||||||
ErrorLinesState, ErrorState, GroupedLogsState, LogState, MatchListState, MatchState, UiState,
|
ErrorLinesState, ErrorState, GroupListState, GroupState, LogState, LogsByIdentifierState,
|
||||||
|
UiState,
|
||||||
};
|
};
|
||||||
use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
||||||
use ratatui::crossterm::terminal::{
|
use ratatui::crossterm::terminal::{
|
||||||
|
|
@ -25,24 +26,24 @@ use std::io;
|
||||||
use std::io::stdout;
|
use std::io::stdout;
|
||||||
use std::panic::{set_hook, take_hook};
|
use std::panic::{set_hook, take_hook};
|
||||||
|
|
||||||
|
mod by_identifier;
|
||||||
mod error_list;
|
mod error_list;
|
||||||
mod footer;
|
mod footer;
|
||||||
mod grouped_logs;
|
|
||||||
mod grouping_list;
|
mod grouping_list;
|
||||||
mod histogram;
|
mod histogram;
|
||||||
mod input;
|
mod input;
|
||||||
|
mod single_group;
|
||||||
mod single_log;
|
mod single_log;
|
||||||
mod single_match;
|
|
||||||
mod state;
|
mod state;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
mod table;
|
mod table;
|
||||||
|
|
||||||
pub fn run_ui(app: App) -> Result<(), UiError> {
|
pub fn run_ui<'a>(app: App<'a>) -> Result<(), UiError> {
|
||||||
init_panic_hook();
|
init_panic_hook();
|
||||||
let mut terminal = init_tui()?;
|
let mut terminal = init_tui()?;
|
||||||
terminal.clear().ok();
|
terminal.clear().ok();
|
||||||
|
|
||||||
let mut ui_state = UiState::new(&app);
|
let mut ui_state: UiState = UiState::new(&app);
|
||||||
let mut update = true;
|
let mut update = true;
|
||||||
|
|
||||||
while !matches!(ui_state, UiState::Quit) {
|
while !matches!(ui_state, UiState::Quit) {
|
||||||
|
|
@ -50,7 +51,7 @@ pub fn run_ui(app: App) -> Result<(), UiError> {
|
||||||
terminal.draw(|frame| ui(frame, &app, &mut ui_state))?;
|
terminal.draw(|frame| ui(frame, &app, &mut ui_state))?;
|
||||||
}
|
}
|
||||||
update = false;
|
update = false;
|
||||||
if let Some(event) = handle_events(ui_state.page(), &ui_state)? {
|
if let Some(event) = handle_events(ui_state.page(), &ui_state, &app)? {
|
||||||
(update, ui_state) = ui_state.process(event, &app);
|
(update, ui_state) = ui_state.process(event, &app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -83,9 +84,9 @@ pub fn restore_tui() -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_hit_row(row: u16, ui_state: &UiState) -> Option<usize> {
|
fn find_hit_row(row: u16, ui_state: &UiState, app: &App) -> Option<usize> {
|
||||||
if let Some(table_row) = row.checked_sub(ui_state.content_offset()) {
|
if let Some(table_row) = row.checked_sub(ui_state.content_offset()) {
|
||||||
let selected = ui_state.index_for_row(table_row as usize);
|
let selected = ui_state.index_for_row(table_row as usize, app);
|
||||||
if selected < ui_state.row_count() {
|
if selected < ui_state.row_count() {
|
||||||
Some(selected)
|
Some(selected)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -110,28 +111,23 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
||||||
|
|
||||||
match state {
|
match state {
|
||||||
UiState::Quit => {}
|
UiState::Quit => {}
|
||||||
UiState::MatchList(MatchListState {
|
UiState::GroupList(GroupListState {
|
||||||
table_state,
|
table_state,
|
||||||
filter,
|
filter,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
let selected = table_state.selected();
|
let selected = table_state.selected();
|
||||||
let histogram = if selected == 0 {
|
let histogram = app.matches[selected].histogram(app.time_range());
|
||||||
app.all.histogram(app.time_range())
|
|
||||||
} else {
|
|
||||||
let log_match = &app.matches[selected - 1];
|
|
||||||
log_match.histogram(app.time_range())
|
|
||||||
};
|
|
||||||
|
|
||||||
frame.render_widget(UiHistogram::new(histogram), layout[0]);
|
frame.render_widget(UiHistogram::new(histogram), layout[0]);
|
||||||
frame.render_stateful_widget(
|
frame.render_stateful_widget(
|
||||||
grouping_list::<MatchResult>(&app.all, &app.matches, app.time_range(), filter),
|
grouping_list::<MatchResult>(app.matches, app.time_range(), filter),
|
||||||
layout[1],
|
layout[1],
|
||||||
table_state,
|
table_state,
|
||||||
);
|
);
|
||||||
frame.render_widget(footer(app, state.footer_params()), layout[2]);
|
frame.render_widget(footer(app, state.footer_params()), layout[2]);
|
||||||
}
|
}
|
||||||
UiState::Match(MatchState {
|
UiState::Group(GroupState {
|
||||||
result,
|
result,
|
||||||
table_state,
|
table_state,
|
||||||
filter,
|
filter,
|
||||||
|
|
@ -139,9 +135,9 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
||||||
}) => {
|
}) => {
|
||||||
let selected = table_state.selected();
|
let selected = table_state.selected();
|
||||||
let selected_group = if selected == 0 {
|
let selected_group = if selected == 0 {
|
||||||
&result.all
|
&result.lines
|
||||||
} else {
|
} else {
|
||||||
&result.grouped[selected - 1]
|
&result.by_identifier()[selected - 1]
|
||||||
};
|
};
|
||||||
|
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
|
|
@ -149,13 +145,18 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
||||||
layout[0],
|
layout[0],
|
||||||
);
|
);
|
||||||
frame.render_stateful_widget(
|
frame.render_stateful_widget(
|
||||||
grouped_lines(app, result, filter),
|
single_group(
|
||||||
|
app.time_range(),
|
||||||
|
&result.lines,
|
||||||
|
result.by_identifier(),
|
||||||
|
filter,
|
||||||
|
),
|
||||||
layout[1],
|
layout[1],
|
||||||
table_state,
|
table_state,
|
||||||
);
|
);
|
||||||
frame.render_widget(footer(app, state.footer_params()), layout[2]);
|
frame.render_widget(footer(app, state.footer_params()), layout[2]);
|
||||||
}
|
}
|
||||||
UiState::GroupedLogs(GroupedLogsState {
|
UiState::ByIdentifier(LogsByIdentifierState {
|
||||||
lines,
|
lines,
|
||||||
table_state,
|
table_state,
|
||||||
filter,
|
filter,
|
||||||
|
|
@ -163,7 +164,7 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
frame.render_stateful_widget(
|
frame.render_stateful_widget(
|
||||||
grouped_logs(app, lines, filter, *grouping),
|
logs_by_identifier(lines, filter, *grouping),
|
||||||
layout[0].union(layout[1]),
|
layout[0].union(layout[1]),
|
||||||
table_state,
|
table_state,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
use crate::app::{App, Filter, LineSet};
|
use crate::app::{Filter, LineSet};
|
||||||
use crate::grouping::LogGrouping;
|
|
||||||
use crate::matcher::MatchResult;
|
|
||||||
use crate::ui::style::TABLE_HEADER_STYLE;
|
use crate::ui::style::TABLE_HEADER_STYLE;
|
||||||
use crate::ui::table::{ScrollbarTable, ScrollbarTableState};
|
use crate::ui::table::{ScrollbarTable, ScrollbarTableState};
|
||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
|
|
@ -12,32 +10,35 @@ use std::iter::once;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
pub fn grouped_lines<'a>(
|
pub fn single_group<'a>(
|
||||||
app: &'a App<'a>,
|
time_range: RangeInclusive<OffsetDateTime>,
|
||||||
log_match: &'a LogGrouping<'a, MatchResult>,
|
all: &'a LineSet<'a>,
|
||||||
|
unique: &'a [LineSet<'a>],
|
||||||
filter: &'a Filter,
|
filter: &'a Filter,
|
||||||
) -> SingleMatchTable<'a> {
|
) -> SingleGroup<'a> {
|
||||||
SingleMatchTable {
|
SingleGroup {
|
||||||
app,
|
time_range,
|
||||||
log_match,
|
all,
|
||||||
|
unique,
|
||||||
filter,
|
filter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SingleMatchTable<'a> {
|
pub struct SingleGroup<'a> {
|
||||||
app: &'a App<'a>,
|
time_range: RangeInclusive<OffsetDateTime>,
|
||||||
log_match: &'a LogGrouping<'a, MatchResult>,
|
all: &'a LineSet<'a>,
|
||||||
|
unique: &'a [LineSet<'a>],
|
||||||
filter: &'a Filter,
|
filter: &'a Filter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatefulWidget for SingleMatchTable<'_> {
|
impl StatefulWidget for SingleGroup<'_> {
|
||||||
type State = ScrollbarTableState;
|
type State = ScrollbarTableState;
|
||||||
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let grouped = &self.log_match.grouped;
|
let grouped = self.unique;
|
||||||
let header = [
|
let header = [
|
||||||
Text::from("Level"),
|
Text::from("Level"),
|
||||||
Text::from("App"),
|
Text::from("App"),
|
||||||
|
|
@ -63,8 +64,8 @@ impl StatefulWidget for SingleMatchTable<'_> {
|
||||||
Text::from("All lines"),
|
Text::from("All lines"),
|
||||||
Text::from(""),
|
Text::from(""),
|
||||||
Text::from(""),
|
Text::from(""),
|
||||||
Text::from(self.log_match.sparkline(self.app.time_range())),
|
Text::from(self.all.sparkline(self.time_range.clone())),
|
||||||
Text::from(self.log_match.count().to_string()),
|
Text::from(self.all.len().to_string()),
|
||||||
]))
|
]))
|
||||||
.chain(
|
.chain(
|
||||||
grouped
|
grouped
|
||||||
|
|
@ -73,7 +74,7 @@ impl StatefulWidget for SingleMatchTable<'_> {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, group)| {
|
.map(|(i, group)| {
|
||||||
group_row(
|
group_row(
|
||||||
self.app.time_range(),
|
self.time_range.clone(),
|
||||||
group,
|
group,
|
||||||
i.abs_diff(state.selected()) < 100,
|
i.abs_diff(state.selected()) < 100,
|
||||||
)
|
)
|
||||||
140
src/ui/state.rs
140
src/ui/state.rs
|
|
@ -16,9 +16,9 @@ use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Clone, From, PartialEq)]
|
#[derive(Clone, From, PartialEq)]
|
||||||
pub enum UiState<'a> {
|
pub enum UiState<'a> {
|
||||||
MatchList(MatchListState<'a>),
|
GroupList(GroupListState),
|
||||||
Match(MatchState<'a>),
|
Group(GroupState<'a>),
|
||||||
GroupedLogs(GroupedLogsState<'a>),
|
ByIdentifier(LogsByIdentifierState<'a>),
|
||||||
Log(LogState<'a>),
|
Log(LogState<'a>),
|
||||||
Errors(ErrorLinesState<'a>),
|
Errors(ErrorLinesState<'a>),
|
||||||
Error(ErrorState<'a>),
|
Error(ErrorState<'a>),
|
||||||
|
|
@ -32,22 +32,19 @@ pub enum Mode {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MatchListState<'a> {
|
pub struct GroupListState {
|
||||||
app: &'a App<'a>,
|
|
||||||
pub table_state: ScrollbarTableState,
|
pub table_state: ScrollbarTableState,
|
||||||
pub filter: Filter,
|
pub filter: Filter,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MatchListState<'a> {
|
impl GroupListState {
|
||||||
fn selected(&self) -> usize {
|
fn selected(&self) -> usize {
|
||||||
self.table_state.selected()
|
self.table_state.selected()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter(self, selected: usize, app: &'a App) -> UiState<'a> {
|
fn enter<'a>(self, selected: usize, app: &App<'a>) -> UiState<'a> {
|
||||||
let result = if selected == 0 {
|
let result = if self.filter.is_empty() {
|
||||||
&app.all
|
|
||||||
} else if self.filter.is_empty() {
|
|
||||||
&app.matches[selected - 1]
|
&app.matches[selected - 1]
|
||||||
} else {
|
} else {
|
||||||
app.matches
|
app.matches
|
||||||
|
|
@ -57,8 +54,8 @@ impl<'a> MatchListState<'a> {
|
||||||
.unwrap_or(app.matches.last().unwrap())
|
.unwrap_or(app.matches.last().unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
let table_state = ScrollbarTableState::new(result.grouped.len() + 1);
|
let table_state = ScrollbarTableState::new(result.by_identifier().len() + 1);
|
||||||
UiState::Match(MatchState {
|
UiState::Group(GroupState {
|
||||||
result,
|
result,
|
||||||
table_state,
|
table_state,
|
||||||
previous: Box::new(self.into()),
|
previous: Box::new(self.into()),
|
||||||
|
|
@ -68,14 +65,14 @@ impl<'a> MatchListState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for MatchListState<'_> {
|
impl PartialEq for GroupListState {
|
||||||
fn eq(&self, _other: &Self) -> bool {
|
fn eq(&self, _other: &Self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MatchState<'a> {
|
pub struct GroupState<'a> {
|
||||||
pub result: &'a LogGrouping<'a, MatchResult>,
|
pub result: &'a LogGrouping<'a, MatchResult>,
|
||||||
pub table_state: ScrollbarTableState,
|
pub table_state: ScrollbarTableState,
|
||||||
pub previous: Box<UiState<'a>>,
|
pub previous: Box<UiState<'a>>,
|
||||||
|
|
@ -83,7 +80,7 @@ pub struct MatchState<'a> {
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MatchState<'a> {
|
impl<'a> GroupState<'a> {
|
||||||
fn selected(&self) -> usize {
|
fn selected(&self) -> usize {
|
||||||
self.table_state.selected()
|
self.table_state.selected()
|
||||||
}
|
}
|
||||||
|
|
@ -93,12 +90,12 @@ impl<'a> MatchState<'a> {
|
||||||
table_state.select(Some(0));
|
table_state.select(Some(0));
|
||||||
|
|
||||||
let selected_line = if selected == 0 {
|
let selected_line = if selected == 0 {
|
||||||
&self.result.all
|
&self.result.lines
|
||||||
} else if self.filter.is_empty() {
|
} else if self.filter.is_empty() {
|
||||||
&self.result.grouped[selected - 1]
|
&self.result.by_identifier()[selected - 1]
|
||||||
} else {
|
} else {
|
||||||
self.result
|
self.result
|
||||||
.grouped
|
.by_identifier()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|grouped| grouped.matches(&self.filter))
|
.filter(|grouped| grouped.matches(&self.filter))
|
||||||
.nth(selected - 1)
|
.nth(selected - 1)
|
||||||
|
|
@ -106,7 +103,7 @@ impl<'a> MatchState<'a> {
|
||||||
};
|
};
|
||||||
let lines = selected_line.lines.as_slice();
|
let lines = selected_line.lines.as_slice();
|
||||||
let table_state = ScrollbarTableState::new(lines.len());
|
let table_state = ScrollbarTableState::new(lines.len());
|
||||||
UiState::GroupedLogs(GroupedLogsState {
|
UiState::ByIdentifier(LogsByIdentifierState {
|
||||||
lines: lines.into(),
|
lines: lines.into(),
|
||||||
table_state,
|
table_state,
|
||||||
previous: Box::new(self.into()),
|
previous: Box::new(self.into()),
|
||||||
|
|
@ -117,7 +114,7 @@ impl<'a> MatchState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for MatchState<'_> {
|
impl PartialEq for GroupState<'_> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.result.result == other.result.result
|
self.result.result == other.result.result
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +127,7 @@ pub enum GroupedLogGrouping {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GroupedLogsState<'a> {
|
pub struct LogsByIdentifierState<'a> {
|
||||||
pub lines: Cow<'a, [&'a LogLine<'a>]>,
|
pub lines: Cow<'a, [&'a LogLine<'a>]>,
|
||||||
pub table_state: ScrollbarTableState,
|
pub table_state: ScrollbarTableState,
|
||||||
pub previous: Box<UiState<'a>>,
|
pub previous: Box<UiState<'a>>,
|
||||||
|
|
@ -139,7 +136,7 @@ pub struct GroupedLogsState<'a> {
|
||||||
pub grouping: GroupedLogGrouping,
|
pub grouping: GroupedLogGrouping,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GroupedLogsState<'a> {
|
impl<'a> LogsByIdentifierState<'a> {
|
||||||
fn selected(&self) -> usize {
|
fn selected(&self) -> usize {
|
||||||
self.table_state.selected()
|
self.table_state.selected()
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +154,7 @@ impl<'a> GroupedLogsState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter(self, selected: usize, app: &'a App<'a>) -> UiState<'a> {
|
fn enter(self, selected: usize, app: &App<'a>) -> UiState<'a> {
|
||||||
let log = self.get_selected(selected);
|
let log = self.get_selected(selected);
|
||||||
let raw_line = app.get_source_line(log.line_number).unwrap();
|
let raw_line = app.get_source_line(log.line_number).unwrap();
|
||||||
let full_line = match parse_line_full(raw_line) {
|
let full_line = match parse_line_full(raw_line) {
|
||||||
|
|
@ -187,12 +184,12 @@ impl<'a> GroupedLogsState<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn by_request(self, selected: usize, app: &'a App<'a>) -> UiState<'a> {
|
fn by_request(self, selected: usize, app: &App<'a>) -> UiState<'a> {
|
||||||
let log = self.get_selected(selected);
|
let log = self.get_selected(selected);
|
||||||
let lines: Vec<_> = app.lines_by_request(&log.request_id).collect();
|
let lines: Vec<_> = app.lines_by_request(&log.request_id).collect();
|
||||||
|
|
||||||
let table_state = ScrollbarTableState::new(lines.len());
|
let table_state = ScrollbarTableState::new(lines.len());
|
||||||
UiState::GroupedLogs(GroupedLogsState {
|
UiState::ByIdentifier(LogsByIdentifierState {
|
||||||
lines: lines.into(),
|
lines: lines.into(),
|
||||||
mode: Mode::Normal,
|
mode: Mode::Normal,
|
||||||
filter: Filter::default(),
|
filter: Filter::default(),
|
||||||
|
|
@ -203,7 +200,7 @@ impl<'a> GroupedLogsState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for GroupedLogsState<'_> {
|
impl PartialEq for LogsByIdentifierState<'_> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.lines == other.lines
|
self.lines == other.lines
|
||||||
}
|
}
|
||||||
|
|
@ -230,11 +227,11 @@ pub struct LogState<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LogState<'a> {
|
impl<'a> LogState<'a> {
|
||||||
fn by_request(self, app: &'a App<'a>) -> UiState<'a> {
|
fn by_request(self, app: &App<'a>) -> UiState<'a> {
|
||||||
let lines: Vec<_> = app.lines_by_request(&self.log.request_id).collect();
|
let lines: Vec<_> = app.lines_by_request(&self.log.request_id).collect();
|
||||||
|
|
||||||
let table_state = ScrollbarTableState::new(lines.len());
|
let table_state = ScrollbarTableState::new(lines.len());
|
||||||
UiState::GroupedLogs(GroupedLogsState {
|
UiState::ByIdentifier(LogsByIdentifierState {
|
||||||
lines: lines.into(),
|
lines: lines.into(),
|
||||||
mode: Mode::Normal,
|
mode: Mode::Normal,
|
||||||
filter: Filter::default(),
|
filter: Filter::default(),
|
||||||
|
|
@ -252,11 +249,10 @@ impl PartialEq for LogState<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> UiState<'a> {
|
impl<'a> UiState<'a> {
|
||||||
pub fn new(app: &'a App<'a>) -> Self {
|
pub fn new(app: &App) -> Self {
|
||||||
let mut table_state = TableState::default();
|
let mut table_state = TableState::default();
|
||||||
table_state.select(Some(0));
|
table_state.select(Some(0));
|
||||||
UiState::MatchList(MatchListState {
|
UiState::GroupList(GroupListState {
|
||||||
app,
|
|
||||||
table_state: ScrollbarTableState::new(app.match_lines()),
|
table_state: ScrollbarTableState::new(app.match_lines()),
|
||||||
filter: Filter::default(),
|
filter: Filter::default(),
|
||||||
mode: Mode::Normal,
|
mode: Mode::Normal,
|
||||||
|
|
@ -265,9 +261,9 @@ impl<'a> UiState<'a> {
|
||||||
|
|
||||||
pub fn page(&self) -> UiPage {
|
pub fn page(&self) -> UiPage {
|
||||||
match self {
|
match self {
|
||||||
UiState::Quit | UiState::MatchList(_) => UiPage::MatchList,
|
UiState::Quit | UiState::GroupList(_) => UiPage::MatchList,
|
||||||
UiState::Match(_) => UiPage::Match,
|
UiState::Group(_) => UiPage::Match,
|
||||||
UiState::GroupedLogs(_) => UiPage::Logs,
|
UiState::ByIdentifier(_) => UiPage::Logs,
|
||||||
UiState::Log(_) => UiPage::Log,
|
UiState::Log(_) => UiPage::Log,
|
||||||
UiState::Errors(_) => UiPage::Errors,
|
UiState::Errors(_) => UiPage::Errors,
|
||||||
UiState::Error(_) => UiPage::Error,
|
UiState::Error(_) => UiPage::Error,
|
||||||
|
|
@ -276,45 +272,45 @@ impl<'a> UiState<'a> {
|
||||||
|
|
||||||
pub fn mode(&self) -> Mode {
|
pub fn mode(&self) -> Mode {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList(state) => state.mode,
|
UiState::GroupList(state) => state.mode,
|
||||||
UiState::Match(state) => state.mode,
|
UiState::Group(state) => state.mode,
|
||||||
UiState::GroupedLogs(state) => state.mode,
|
UiState::ByIdentifier(state) => state.mode,
|
||||||
_ => Mode::Normal,
|
_ => Mode::Normal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mode(&mut self, mode: Mode) {
|
pub fn set_mode(&mut self, mode: Mode) {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList(state) => state.mode = mode,
|
UiState::GroupList(state) => state.mode = mode,
|
||||||
UiState::Match(state) => state.mode = mode,
|
UiState::Group(state) => state.mode = mode,
|
||||||
UiState::GroupedLogs(state) => state.mode = mode,
|
UiState::ByIdentifier(state) => state.mode = mode,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filter(&self) -> Option<&Filter> {
|
pub fn filter(&self) -> Option<&Filter> {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList(state) => Some(&state.filter),
|
UiState::GroupList(state) => Some(&state.filter),
|
||||||
UiState::Match(state) => Some(&state.filter),
|
UiState::Group(state) => Some(&state.filter),
|
||||||
UiState::GroupedLogs(state) => Some(&state.filter),
|
UiState::ByIdentifier(state) => Some(&state.filter),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filter_mut(&mut self) -> Option<&mut Filter> {
|
pub fn filter_mut(&mut self) -> Option<&mut Filter> {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList(state) => Some(&mut state.filter),
|
UiState::GroupList(state) => Some(&mut state.filter),
|
||||||
UiState::Match(state) => Some(&mut state.filter),
|
UiState::Group(state) => Some(&mut state.filter),
|
||||||
UiState::GroupedLogs(state) => Some(&mut state.filter),
|
UiState::ByIdentifier(state) => Some(&mut state.filter),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_state(&self) -> Option<&ScrollbarTableState> {
|
fn table_state(&self) -> Option<&ScrollbarTableState> {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList(state) => Some(&state.table_state),
|
UiState::GroupList(state) => Some(&state.table_state),
|
||||||
UiState::Match(state) => Some(&state.table_state),
|
UiState::Group(state) => Some(&state.table_state),
|
||||||
UiState::GroupedLogs(state) => Some(&state.table_state),
|
UiState::ByIdentifier(state) => Some(&state.table_state),
|
||||||
UiState::Log(state) => Some(&state.table_state),
|
UiState::Log(state) => Some(&state.table_state),
|
||||||
UiState::Errors(state) => Some(&state.table_state),
|
UiState::Errors(state) => Some(&state.table_state),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -323,9 +319,9 @@ impl<'a> UiState<'a> {
|
||||||
|
|
||||||
fn table_state_mut(&mut self) -> Option<&mut ScrollbarTableState> {
|
fn table_state_mut(&mut self) -> Option<&mut ScrollbarTableState> {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList(state) => Some(&mut state.table_state),
|
UiState::GroupList(state) => Some(&mut state.table_state),
|
||||||
UiState::Match(state) => Some(&mut state.table_state),
|
UiState::Group(state) => Some(&mut state.table_state),
|
||||||
UiState::GroupedLogs(state) => Some(&mut state.table_state),
|
UiState::ByIdentifier(state) => Some(&mut state.table_state),
|
||||||
UiState::Log(state) => Some(&mut state.table_state),
|
UiState::Log(state) => Some(&mut state.table_state),
|
||||||
UiState::Errors(state) => Some(&mut state.table_state),
|
UiState::Errors(state) => Some(&mut state.table_state),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -348,9 +344,9 @@ impl<'a> UiState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn index_for_row(&self, row: usize) -> usize {
|
pub fn index_for_row(&self, row: usize, app: &App) -> usize {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList(MatchListState { app, filter, .. }) => {
|
UiState::GroupList(GroupListState { filter, .. }) => {
|
||||||
let mut total_height = 0;
|
let mut total_height = 0;
|
||||||
let match_row_counts = app
|
let match_row_counts = app
|
||||||
.matches
|
.matches
|
||||||
|
|
@ -381,9 +377,9 @@ impl<'a> UiState<'a> {
|
||||||
/// get the offset of the "main content" from the top of the screen
|
/// get the offset of the "main content" from the top of the screen
|
||||||
pub fn content_offset(&self) -> u16 {
|
pub fn content_offset(&self) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList(_) => UI_HEADER_SIZE + 1,
|
UiState::GroupList(_) => UI_HEADER_SIZE + 1,
|
||||||
UiState::Match(_) => UI_HEADER_SIZE + 1,
|
UiState::Group(_) => UI_HEADER_SIZE + 1,
|
||||||
UiState::GroupedLogs(_) => UI_HEADER_SIZE + 1,
|
UiState::ByIdentifier(_) => UI_HEADER_SIZE + 1,
|
||||||
UiState::Log(_) => 0,
|
UiState::Log(_) => 0,
|
||||||
UiState::Errors(_) => 0,
|
UiState::Errors(_) => 0,
|
||||||
UiState::Error(_) => 0,
|
UiState::Error(_) => 0,
|
||||||
|
|
@ -391,12 +387,12 @@ impl<'a> UiState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process(self, event: UiEvent, app: &'a App<'a>) -> (bool, UiState<'a>) {
|
pub fn process(self, event: UiEvent, app: &App<'a>) -> (bool, UiState<'a>) {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(UiState::Quit, _) => (true, UiState::Quit),
|
(UiState::Quit, _) => (true, UiState::Quit),
|
||||||
(_, UiEvent::Quit) => (true, UiState::Quit),
|
(_, UiEvent::Quit) => (true, UiState::Quit),
|
||||||
(
|
(
|
||||||
UiState::MatchList(MatchListState {
|
UiState::GroupList(GroupListState {
|
||||||
mode: Mode::Normal, ..
|
mode: Mode::Normal, ..
|
||||||
}),
|
}),
|
||||||
UiEvent::Back,
|
UiEvent::Back,
|
||||||
|
|
@ -425,14 +421,14 @@ impl<'a> UiState<'a> {
|
||||||
}
|
}
|
||||||
(true, state)
|
(true, state)
|
||||||
}
|
}
|
||||||
(UiState::MatchList(state), UiEvent::Select) => {
|
(UiState::GroupList(state), UiEvent::Select) => {
|
||||||
let selected = state.selected();
|
let selected = state.selected();
|
||||||
(true, state.enter(selected, app))
|
(true, state.enter(selected, app))
|
||||||
}
|
}
|
||||||
(UiState::MatchList(state), UiEvent::Enter(selected)) => {
|
(UiState::GroupList(state), UiEvent::Enter(selected)) => {
|
||||||
(true, state.enter(selected, app))
|
(true, state.enter(selected, app))
|
||||||
}
|
}
|
||||||
(UiState::MatchList(state), UiEvent::Errors) => {
|
(UiState::GroupList(state), UiEvent::Errors) => {
|
||||||
let table_state = ScrollbarTableState::new(app.error_count());
|
let table_state = ScrollbarTableState::new(app.error_count());
|
||||||
(
|
(
|
||||||
true,
|
true,
|
||||||
|
|
@ -442,19 +438,19 @@ impl<'a> UiState<'a> {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(UiState::Match(state), UiEvent::Select) => {
|
(UiState::Group(state), UiEvent::Select) => {
|
||||||
let selected = state.selected();
|
let selected = state.selected();
|
||||||
(true, state.enter(selected))
|
(true, state.enter(selected))
|
||||||
}
|
}
|
||||||
(UiState::Match(state), UiEvent::Enter(selected)) => (true, state.enter(selected)),
|
(UiState::Group(state), UiEvent::Enter(selected)) => (true, state.enter(selected)),
|
||||||
(UiState::GroupedLogs(state), UiEvent::Select) => {
|
(UiState::ByIdentifier(state), UiEvent::Select) => {
|
||||||
let selected = state.selected();
|
let selected = state.selected();
|
||||||
(true, state.enter(selected, app))
|
(true, state.enter(selected, app))
|
||||||
}
|
}
|
||||||
(UiState::GroupedLogs(state), UiEvent::Enter(selected)) => {
|
(UiState::ByIdentifier(state), UiEvent::Enter(selected)) => {
|
||||||
(true, state.enter(selected, app))
|
(true, state.enter(selected, app))
|
||||||
}
|
}
|
||||||
(UiState::GroupedLogs(state), UiEvent::Copy) => {
|
(UiState::ByIdentifier(state), UiEvent::Copy) => {
|
||||||
let selected = state.selected();
|
let selected = state.selected();
|
||||||
let mut table_state = TableState::default();
|
let mut table_state = TableState::default();
|
||||||
table_state.select(Some(0));
|
table_state.select(Some(0));
|
||||||
|
|
@ -462,7 +458,7 @@ impl<'a> UiState<'a> {
|
||||||
let line = state.lines[selected];
|
let line = state.lines[selected];
|
||||||
let raw = app.get_source_line(line.line_number).unwrap_or_default();
|
let raw = app.get_source_line(line.line_number).unwrap_or_default();
|
||||||
copy_osc(raw);
|
copy_osc(raw);
|
||||||
(false, UiState::GroupedLogs(state))
|
(false, UiState::ByIdentifier(state))
|
||||||
}
|
}
|
||||||
(UiState::Log(state), UiEvent::Copy) => {
|
(UiState::Log(state), UiEvent::Copy) => {
|
||||||
let raw = app
|
let raw = app
|
||||||
|
|
@ -471,7 +467,7 @@ impl<'a> UiState<'a> {
|
||||||
copy_osc(raw);
|
copy_osc(raw);
|
||||||
(false, UiState::Log(state))
|
(false, UiState::Log(state))
|
||||||
}
|
}
|
||||||
(UiState::GroupedLogs(state), UiEvent::ByRequest) => {
|
(UiState::ByIdentifier(state), UiEvent::ByRequest) => {
|
||||||
let selected = state.selected();
|
let selected = state.selected();
|
||||||
(true, state.by_request(selected, app))
|
(true, state.by_request(selected, app))
|
||||||
}
|
}
|
||||||
|
|
@ -512,7 +508,7 @@ impl<'a> UiState<'a> {
|
||||||
(true, ui)
|
(true, ui)
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
mut ui @ UiState::MatchList(MatchListState {
|
mut ui @ UiState::GroupList(GroupListState {
|
||||||
mode: Mode::FilterInput,
|
mode: Mode::FilterInput,
|
||||||
..
|
..
|
||||||
}),
|
}),
|
||||||
|
|
@ -526,8 +522,8 @@ impl<'a> UiState<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
UiState::Match(MatchState { previous, .. })
|
UiState::Group(GroupState { previous, .. })
|
||||||
| UiState::GroupedLogs(GroupedLogsState { previous, .. })
|
| UiState::ByIdentifier(LogsByIdentifierState { previous, .. })
|
||||||
| UiState::Log(LogState { previous, .. })
|
| UiState::Log(LogState { previous, .. })
|
||||||
| UiState::Errors(ErrorLinesState { previous, .. }),
|
| UiState::Errors(ErrorLinesState { previous, .. }),
|
||||||
UiEvent::Back,
|
UiEvent::Back,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue