mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 18:14:11 +02:00
group by app and url
This commit is contained in:
parent
9e66967141
commit
a36df49377
9 changed files with 398 additions and 23 deletions
|
|
@ -49,6 +49,12 @@ pub struct LineSet<'logs> {
|
|||
pub sparkline: OnceCell<SparkLine>,
|
||||
}
|
||||
|
||||
impl<'logs> From<Vec<&'logs LogLine<'logs>>> for LineSet<'logs> {
|
||||
fn from(value: Vec<&'logs LogLine<'logs>>) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'logs> LineSet<'logs> {
|
||||
pub fn new(lines: Vec<&'logs LogLine<'logs>>) -> Self {
|
||||
LineSet {
|
||||
|
|
|
|||
29
src/grouping/app.rs
Normal file
29
src/grouping/app.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::app::Filter;
|
||||
use crate::grouping::{GroupingResult, GroupingUi};
|
||||
use ratatui::layout::{Alignment, Constraint};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub struct AppGrouping<'a> {
|
||||
pub app: &'a str,
|
||||
}
|
||||
|
||||
pub const APP_GROUPING_UI: GroupingUi = GroupingUi {
|
||||
header: &[("App", Alignment::Left)],
|
||||
widths: &[Constraint::Percentage(100)],
|
||||
};
|
||||
|
||||
impl<'a> GroupingResult<'a> for AppGrouping<'a> {
|
||||
fn matches(&self, filter: &Filter) -> bool {
|
||||
if filter.is_empty() {
|
||||
return true;
|
||||
}
|
||||
filter
|
||||
.parts()
|
||||
.all(|filter_part| filter_part.is_match(self.app))
|
||||
}
|
||||
|
||||
fn render(&self) -> impl Iterator<Item = Cow<'a, str>> {
|
||||
[self.app.into()].into_iter()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
mod app;
|
||||
mod unique;
|
||||
mod url;
|
||||
|
||||
use crate::app::{Filter, LineSet};
|
||||
use crate::grouping::app::{AppGrouping, APP_GROUPING_UI};
|
||||
use crate::grouping::url::{UrlGrouping, URL_GROUPING_UI};
|
||||
use crate::logfile::LogLine;
|
||||
use crate::matcher::MatchResult;
|
||||
use crate::timegraph::{SparkLine, TimeGraph};
|
||||
use ahash::AHasher;
|
||||
use ratatui::layout::{Alignment, Constraint};
|
||||
use std::borrow::Cow;
|
||||
use std::cell::OnceCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::RangeInclusive;
|
||||
use time::OffsetDateTime;
|
||||
pub use unique::*;
|
||||
|
|
@ -31,16 +37,16 @@ pub trait GroupingResult<'a> {
|
|||
#[derive(Clone)]
|
||||
pub struct LogGrouping<'logs> {
|
||||
pub name: Option<&'static str>,
|
||||
pub result: Option<Groupings>,
|
||||
pub result: Option<Groupings<'logs>>,
|
||||
pub count: usize,
|
||||
pub lines: LineSet<'logs>,
|
||||
pub by_identifier: OnceCell<Vec<LineSet<'logs>>>,
|
||||
}
|
||||
|
||||
impl<'logs> LogGrouping<'logs> {
|
||||
pub fn new(result: impl Into<Groupings>, lines: Vec<&'logs LogLine<'logs>>) -> Self {
|
||||
pub fn new(result: impl Into<Groupings<'logs>>, lines: impl Into<LineSet<'logs>>) -> Self {
|
||||
let lines = lines.into();
|
||||
let count = lines.len();
|
||||
let lines = LineSet::new(lines);
|
||||
|
||||
LogGrouping {
|
||||
name: None,
|
||||
|
|
@ -115,26 +121,104 @@ where
|
|||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum Groupings {
|
||||
pub enum Groupings<'logs> {
|
||||
Match(MatchResult),
|
||||
Url(UrlGrouping<'logs>),
|
||||
App(AppGrouping<'logs>),
|
||||
}
|
||||
|
||||
impl From<MatchResult> for Groupings {
|
||||
impl<'logs> From<MatchResult> for Groupings<'logs> {
|
||||
fn from(value: MatchResult) -> Self {
|
||||
Groupings::Match(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GroupingResult<'a> for Groupings {
|
||||
impl<'logs> From<UrlGrouping<'logs>> for Groupings<'logs> {
|
||||
fn from(value: UrlGrouping<'logs>) -> Self {
|
||||
Groupings::Url(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'logs> From<AppGrouping<'logs>> for Groupings<'logs> {
|
||||
fn from(value: AppGrouping<'logs>) -> Self {
|
||||
Groupings::App(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'logs> GroupingResult<'logs> for Groupings<'logs> {
|
||||
fn matches(&self, filter: &Filter) -> bool {
|
||||
match self {
|
||||
Groupings::Match(r) => r.matches(filter),
|
||||
Groupings::Url(r) => r.matches(filter),
|
||||
Groupings::App(r) => r.matches(filter),
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self) -> impl Iterator<Item = Cow<'a, str>> {
|
||||
fn render(&self) -> impl Iterator<Item = Cow<'logs, str>> {
|
||||
match self {
|
||||
Groupings::Match(r) => r.render(),
|
||||
Groupings::Match(r) => {
|
||||
Box::new(r.render()) as Box<dyn Iterator<Item = Cow<'logs, str>>>
|
||||
}
|
||||
Groupings::Url(r) => Box::new(r.render()),
|
||||
Groupings::App(r) => Box::new(r.render()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum GroupingOptions {
|
||||
Url,
|
||||
App,
|
||||
}
|
||||
|
||||
impl GroupingOptions {
|
||||
pub fn all() -> impl Iterator<Item = Self> {
|
||||
[GroupingOptions::Url, GroupingOptions::App].into_iter()
|
||||
}
|
||||
|
||||
pub fn group_key(&self, line: &LogLine) -> u64 {
|
||||
let mut hasher = AHasher::default();
|
||||
match self {
|
||||
GroupingOptions::Url => line.url.hash(&mut hasher),
|
||||
GroupingOptions::App => line.app.hash(&mut hasher),
|
||||
}
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn group<'logs>(&self, line: &'logs LogLine<'logs>) -> Groupings<'logs> {
|
||||
match self {
|
||||
GroupingOptions::Url => Groupings::Url(UrlGrouping {
|
||||
url: line.url.as_ref(),
|
||||
}),
|
||||
GroupingOptions::App => Groupings::App(AppGrouping {
|
||||
app: line.app.as_ref(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
GroupingOptions::Url => "Url",
|
||||
GroupingOptions::App => "App",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ui(&self) -> GroupingUi {
|
||||
match self {
|
||||
GroupingOptions::Url => URL_GROUPING_UI,
|
||||
GroupingOptions::App => APP_GROUPING_UI,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group_by<'logs>(&self, lines: Vec<&'logs LogLine<'logs>>) -> Vec<LogGrouping<'logs>> {
|
||||
let mut results = vec![LogGrouping::named("All", lines.clone())];
|
||||
results.extend(
|
||||
group_lines_by(lines.into_iter(), |line| self.group_key(line))
|
||||
.into_iter()
|
||||
.map(|lines| {
|
||||
let group = self.group(lines.lines[0]);
|
||||
LogGrouping::new(group, lines)
|
||||
}),
|
||||
);
|
||||
results
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
src/grouping/url.rs
Normal file
29
src/grouping/url.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::app::Filter;
|
||||
use crate::grouping::{GroupingResult, GroupingUi};
|
||||
use ratatui::layout::{Alignment, Constraint};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub struct UrlGrouping<'a> {
|
||||
pub url: &'a str,
|
||||
}
|
||||
|
||||
pub const URL_GROUPING_UI: GroupingUi = GroupingUi {
|
||||
header: &[("Url", Alignment::Left)],
|
||||
widths: &[Constraint::Percentage(100)],
|
||||
};
|
||||
|
||||
impl<'a> GroupingResult<'a> for UrlGrouping<'a> {
|
||||
fn matches(&self, filter: &Filter) -> bool {
|
||||
if filter.is_empty() {
|
||||
return true;
|
||||
}
|
||||
filter
|
||||
.parts()
|
||||
.all(|filter_part| filter_part.is_match(self.url))
|
||||
}
|
||||
|
||||
fn render(&self) -> impl Iterator<Item = Cow<'a, str>> {
|
||||
[self.url.into()].into_iter()
|
||||
}
|
||||
}
|
||||
|
|
@ -55,8 +55,8 @@ pub fn footer<'a>(app: &App, params: FooterParams<'a>) -> Table<'a> {
|
|||
|
||||
fn help(page: UiPage) -> &'static str {
|
||||
match page {
|
||||
UiPage::MatchList => "«Q» Exit - «Enter» Select - «F» Filter - «E» Show parse errors",
|
||||
UiPage::Match => "«Q» Exit - «Enter» Select - «F» Filter - «Esc» Back",
|
||||
UiPage::GroupList => "«Q» Exit - «Enter» Select - «F» Filter - «E» Show parse errors",
|
||||
UiPage::Group => "«Q» Exit - «Enter» Select - «F» Filter - «G» Group By - «Esc» Back",
|
||||
UiPage::Logs => {
|
||||
"«Q» Exit - «F» Filter - «Esc» Back - «C» Copy log line - «R» Show logs for request"
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ fn help(page: UiPage) -> &'static str {
|
|||
|
||||
fn filter_help(page: UiPage) -> &'static str {
|
||||
match page {
|
||||
UiPage::MatchList => "«Esc» Clear",
|
||||
UiPage::GroupList => "«Esc» Clear",
|
||||
_ => "«Esc» Clear - «Left» Back",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ pub enum UiEvent {
|
|||
Text(char),
|
||||
PopText(PopMode),
|
||||
ByRequest,
|
||||
GroupBy,
|
||||
}
|
||||
|
||||
pub enum PopMode {
|
||||
|
|
@ -40,10 +41,10 @@ pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result<Option<UiEv
|
|||
(Mode::Normal, KeyCode::Esc) => Some(UiEvent::Back),
|
||||
|
||||
(Mode::Normal, KeyCode::Char('q')) => Some(UiEvent::Quit),
|
||||
(Mode::Normal, KeyCode::Char('e')) if page == UiPage::MatchList => {
|
||||
(Mode::Normal, KeyCode::Char('e')) if page == UiPage::GroupList => {
|
||||
Some(UiEvent::Errors)
|
||||
}
|
||||
(_, KeyCode::Left) if page != UiPage::MatchList => Some(UiEvent::Back),
|
||||
(_, KeyCode::Left) if ui_state.has_previous() => Some(UiEvent::Back),
|
||||
(_, KeyCode::Down) => Some(UiEvent::Down(1, true)),
|
||||
(_, KeyCode::Up) => Some(UiEvent::Up(1, true)),
|
||||
(_, KeyCode::PageDown) => Some(UiEvent::Down(10, false)),
|
||||
|
|
@ -53,6 +54,7 @@ pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result<Option<UiEv
|
|||
(_, KeyCode::Enter | KeyCode::Right) => Some(UiEvent::Select),
|
||||
(Mode::Normal, KeyCode::Char('c')) => Some(UiEvent::Copy),
|
||||
(Mode::Normal, KeyCode::Char('r')) => Some(UiEvent::ByRequest),
|
||||
(Mode::Normal, KeyCode::Char('g')) => Some(UiEvent::GroupBy),
|
||||
(Mode::Normal, KeyCode::F(4) | KeyCode::Char('f')) => {
|
||||
Some(UiEvent::EnterFilterMode)
|
||||
}
|
||||
|
|
|
|||
101
src/ui/list.rs
Normal file
101
src/ui/list.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
use crate::ui::style::TABLE_SELECTED_STYLE;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::StatefulWidget;
|
||||
use ratatui::widgets::{Block, HighlightSpacing, List, ListItem, ListState};
|
||||
|
||||
pub struct SelectList<'a> {
|
||||
list: List<'a>,
|
||||
}
|
||||
|
||||
impl<'a> SelectList<'a> {
|
||||
pub fn new<T>(items: T) -> Self
|
||||
where
|
||||
T: IntoIterator,
|
||||
T::Item: Into<ListItem<'a>>,
|
||||
{
|
||||
SelectList {
|
||||
list: List::new(items)
|
||||
.highlight_style(TABLE_SELECTED_STYLE)
|
||||
.highlight_spacing(HighlightSpacing::Always),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub fn block(self, block: Block<'a>) -> Self {
|
||||
SelectList {
|
||||
list: self.list.block(block),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulWidget for SelectList<'_> {
|
||||
type State = SizedListState;
|
||||
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
StatefulWidget::render(self.list, area, buf, &mut state.list_state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct SizedListState {
|
||||
list_state: ListState,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl SizedListState {
|
||||
pub fn new(count: usize) -> Self {
|
||||
SizedListState {
|
||||
list_state: ListState::default().with_selected(Some(0)),
|
||||
count,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> usize {
|
||||
self.list_state.selected().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn up(&mut self, step: usize, rollover: bool) -> usize {
|
||||
let current = self.list_state.selected().unwrap_or(0);
|
||||
let after = if step > current {
|
||||
if rollover {
|
||||
self.count - 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
current - step
|
||||
};
|
||||
self.list_state.select(Some(after));
|
||||
after
|
||||
}
|
||||
|
||||
pub fn down(&mut self, step: usize, rollover: bool) -> usize {
|
||||
let current = self.list_state.selected().unwrap_or(0);
|
||||
let after = if step >= self.count - current {
|
||||
if rollover {
|
||||
0
|
||||
} else {
|
||||
self.count - 1
|
||||
}
|
||||
} else {
|
||||
current + step
|
||||
};
|
||||
self.list_state.select(Some(after));
|
||||
after
|
||||
}
|
||||
|
||||
pub fn scroll(&mut self, step: isize) {
|
||||
let selected = self
|
||||
.list_state
|
||||
.selected()
|
||||
.unwrap_or_default()
|
||||
.saturating_add_signed(step)
|
||||
.min(self.count - 1);
|
||||
self.list_state.select(Some(selected));
|
||||
}
|
||||
|
||||
pub fn select(&mut self, selected: usize) {
|
||||
self.list_state.select(Some(selected));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,27 @@
|
|||
use crate::app::App;
|
||||
use crate::error::UiError;
|
||||
use crate::grouping::LogGrouping;
|
||||
use crate::grouping::{GroupingOptions, LogGrouping};
|
||||
use crate::ui::by_identifier::logs_by_identifier;
|
||||
use crate::ui::error_list::error_list;
|
||||
use crate::ui::footer::footer;
|
||||
use crate::ui::grouping_list::grouping_list;
|
||||
use crate::ui::histogram::UiHistogram;
|
||||
use crate::ui::input::handle_events;
|
||||
use crate::ui::list::SelectList;
|
||||
use crate::ui::single_group::single_group;
|
||||
use crate::ui::single_log::single_log;
|
||||
use crate::ui::state::{
|
||||
ErrorLinesState, ErrorState, GroupListState, GroupState, LogState, LogsByIdentifierState,
|
||||
UiState,
|
||||
ErrorLinesState, ErrorState, GroupByMenuState, GroupListState, GroupState, LogState,
|
||||
LogsByIdentifierState, UiState,
|
||||
};
|
||||
use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
||||
use ratatui::crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
};
|
||||
use ratatui::crossterm::ExecutableCommand;
|
||||
use ratatui::layout::Flex;
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::widgets::{Block, Clear, Paragraph};
|
||||
use ratatui::Terminal;
|
||||
use serde_json::Value;
|
||||
use std::io;
|
||||
|
|
@ -32,6 +34,7 @@ mod footer;
|
|||
mod grouping_list;
|
||||
mod histogram;
|
||||
mod input;
|
||||
mod list;
|
||||
mod single_group;
|
||||
mod single_log;
|
||||
mod state;
|
||||
|
|
@ -158,6 +161,21 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
|||
);
|
||||
frame.render_widget(footer(app, state.footer_params()), layout[2]);
|
||||
}
|
||||
UiState::GroupByMenu(GroupByMenuState {
|
||||
previous,
|
||||
list_state,
|
||||
}) => {
|
||||
ui(frame, app, previous);
|
||||
let area = center(
|
||||
frame.area(),
|
||||
Constraint::Percentage(20),
|
||||
Constraint::Length(GroupingOptions::all().count() as u16 + 2), // top and bottom border + content
|
||||
);
|
||||
let popup = SelectList::new(GroupingOptions::all().map(|option| option.as_str()))
|
||||
.block(Block::bordered().title("Group By"));
|
||||
frame.render_widget(Clear, area);
|
||||
frame.render_stateful_widget(popup, area, list_state);
|
||||
}
|
||||
UiState::ByIdentifier(LogsByIdentifierState {
|
||||
lines,
|
||||
table_state,
|
||||
|
|
@ -198,3 +216,11 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect {
|
||||
let [area] = Layout::horizontal([horizontal])
|
||||
.flex(Flex::Center)
|
||||
.areas(area);
|
||||
let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area);
|
||||
area
|
||||
}
|
||||
|
|
|
|||
110
src/ui/state.rs
110
src/ui/state.rs
|
|
@ -1,10 +1,11 @@
|
|||
use crate::app::{App, Filter, EMPTY_FILTER};
|
||||
use crate::error::ParseError;
|
||||
use crate::grouping::{GroupingUi, LogGrouping};
|
||||
use crate::grouping::{GroupingOptions, GroupingUi, LogGrouping};
|
||||
use crate::logfile::logline::{FullLogLine, LogLine};
|
||||
use crate::matcher::MATCH_GROUPING_UI;
|
||||
use crate::ui::footer::FooterParams;
|
||||
use crate::ui::input::{PopMode, UiEvent};
|
||||
use crate::ui::list::SizedListState;
|
||||
use crate::ui::table::ScrollbarTableState;
|
||||
use crate::ui::UI_HEADER_SIZE;
|
||||
use crate::{copy_osc, parse_line_full};
|
||||
|
|
@ -17,6 +18,7 @@ use std::sync::Arc;
|
|||
pub enum UiState<'a> {
|
||||
GroupList(GroupListState<'a>),
|
||||
Group(GroupState<'a>),
|
||||
GroupByMenu(GroupByMenuState<'a>),
|
||||
ByIdentifier(LogsByIdentifierState<'a>),
|
||||
Log(LogState<'a>),
|
||||
Errors(ErrorLinesState<'a>),
|
||||
|
|
@ -24,6 +26,22 @@ pub enum UiState<'a> {
|
|||
Quit,
|
||||
}
|
||||
|
||||
impl<'a> UiState<'a> {
|
||||
pub fn has_previous(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
UiState::Group(GroupState { .. })
|
||||
| UiState::GroupList(GroupListState {
|
||||
previous: Some(_),
|
||||
..
|
||||
})
|
||||
| UiState::ByIdentifier(LogsByIdentifierState { .. })
|
||||
| UiState::Log(LogState { .. })
|
||||
| UiState::Errors(ErrorLinesState { .. })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Mode {
|
||||
Normal,
|
||||
|
|
@ -35,6 +53,7 @@ pub struct GroupListState<'a> {
|
|||
pub ui: GroupingUi,
|
||||
pub table_state: ScrollbarTableState,
|
||||
pub filter: Filter,
|
||||
pub previous: Option<Box<UiState<'a>>>,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +92,42 @@ impl PartialEq for GroupListState<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct GroupByMenuState<'a> {
|
||||
pub previous: Box<UiState<'a>>,
|
||||
pub list_state: SizedListState,
|
||||
}
|
||||
|
||||
impl PartialEq for GroupByMenuState<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.list_state == other.list_state
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GroupByMenuState<'a> {
|
||||
fn selected(&self) -> usize {
|
||||
self.list_state.selected()
|
||||
}
|
||||
|
||||
pub fn enter(self, selected: usize) -> UiState<'a> {
|
||||
let group_option = GroupingOptions::all().nth(selected).unwrap();
|
||||
let lines = match self.previous.as_ref() {
|
||||
UiState::Group(group) => &group.result.lines,
|
||||
_ => panic!("Group by called from non-group"),
|
||||
};
|
||||
let items = group_option.group_by(lines.lines.clone());
|
||||
let count = items.len();
|
||||
|
||||
UiState::GroupList(GroupListState {
|
||||
items,
|
||||
filter: Filter::default(),
|
||||
ui: group_option.ui(),
|
||||
mode: Mode::Normal,
|
||||
table_state: ScrollbarTableState::new(count),
|
||||
previous: Some(self.previous),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GroupState<'a> {
|
||||
pub result: LogGrouping<'a>,
|
||||
pub table_state: ScrollbarTableState,
|
||||
|
|
@ -86,6 +141,13 @@ impl<'a> GroupState<'a> {
|
|||
self.table_state.selected()
|
||||
}
|
||||
|
||||
fn group_by_menu(self) -> UiState<'a> {
|
||||
UiState::GroupByMenu(GroupByMenuState {
|
||||
previous: Box::new(self.into()),
|
||||
list_state: SizedListState::new(GroupingOptions::all().count()),
|
||||
})
|
||||
}
|
||||
|
||||
fn enter(self, selected: usize) -> UiState<'a> {
|
||||
let mut table_state = TableState::default();
|
||||
table_state.select(Some(0));
|
||||
|
|
@ -260,13 +322,15 @@ impl<'a> UiState<'a> {
|
|||
table_state: ScrollbarTableState::new(lines),
|
||||
filter: Filter::default(),
|
||||
mode: Mode::Normal,
|
||||
previous: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn page(&self) -> UiPage {
|
||||
match self {
|
||||
UiState::Quit | UiState::GroupList(_) => UiPage::MatchList,
|
||||
UiState::Group(_) => UiPage::Match,
|
||||
UiState::Quit | UiState::GroupList(_) => UiPage::GroupList,
|
||||
UiState::Group(_) => UiPage::Group,
|
||||
UiState::GroupByMenu(_) => UiPage::Group, // todo
|
||||
UiState::ByIdentifier(_) => UiPage::Logs,
|
||||
UiState::Log(_) => UiPage::Log,
|
||||
UiState::Errors(_) => UiPage::Errors,
|
||||
|
|
@ -332,6 +396,13 @@ impl<'a> UiState<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn list_state_mut(&mut self) -> Option<&mut SizedListState> {
|
||||
match self {
|
||||
UiState::GroupByMenu(state) => Some(&mut state.list_state),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_offset(&self) -> usize {
|
||||
if let Some(table_state) = self.table_state() {
|
||||
table_state.offset()
|
||||
|
|
@ -383,6 +454,7 @@ impl<'a> UiState<'a> {
|
|||
UiState::GroupList(_) => UI_HEADER_SIZE + 1,
|
||||
UiState::Group(_) => UI_HEADER_SIZE + 1,
|
||||
UiState::ByIdentifier(_) => UI_HEADER_SIZE + 1,
|
||||
UiState::GroupByMenu(_) => 0,
|
||||
UiState::Log(_) => 0,
|
||||
UiState::Errors(_) => 0,
|
||||
UiState::Error(_) => 0,
|
||||
|
|
@ -396,7 +468,9 @@ impl<'a> UiState<'a> {
|
|||
(_, UiEvent::Quit) => (true, UiState::Quit),
|
||||
(
|
||||
UiState::GroupList(GroupListState {
|
||||
mode: Mode::Normal, ..
|
||||
previous: None,
|
||||
mode: Mode::Normal,
|
||||
..
|
||||
}),
|
||||
UiEvent::Back,
|
||||
) => (true, UiState::Quit),
|
||||
|
|
@ -404,24 +478,36 @@ impl<'a> UiState<'a> {
|
|||
if let Some(table_state) = state.table_state_mut() {
|
||||
table_state.down(step, rollover);
|
||||
}
|
||||
if let Some(list_state) = state.list_state_mut() {
|
||||
list_state.down(step, rollover);
|
||||
}
|
||||
(true, state)
|
||||
}
|
||||
(mut state, UiEvent::Up(step, rollover)) => {
|
||||
if let Some(table_state) = state.table_state_mut() {
|
||||
table_state.up(step, rollover);
|
||||
}
|
||||
if let Some(list_state) = state.list_state_mut() {
|
||||
list_state.up(step, rollover);
|
||||
}
|
||||
(true, state)
|
||||
}
|
||||
(mut state, UiEvent::Scroll(step)) => {
|
||||
if let Some(table_state) = state.table_state_mut() {
|
||||
table_state.scroll(step);
|
||||
}
|
||||
if let Some(list_state) = state.list_state_mut() {
|
||||
list_state.scroll(step);
|
||||
}
|
||||
(true, state)
|
||||
}
|
||||
(mut state, UiEvent::SelectAt(selected)) => {
|
||||
if let Some(table_state) = state.table_state_mut() {
|
||||
table_state.select(selected);
|
||||
}
|
||||
if let Some(list_state) = state.list_state_mut() {
|
||||
list_state.select(selected);
|
||||
}
|
||||
(true, state)
|
||||
}
|
||||
(UiState::GroupList(state), UiEvent::Select) => {
|
||||
|
|
@ -444,6 +530,14 @@ impl<'a> UiState<'a> {
|
|||
(true, state.enter(selected))
|
||||
}
|
||||
(UiState::Group(state), UiEvent::Enter(selected)) => (true, state.enter(selected)),
|
||||
(UiState::Group(state), UiEvent::GroupBy) => (true, state.group_by_menu()),
|
||||
(UiState::GroupByMenu(state), UiEvent::Enter(selected)) => {
|
||||
(true, state.enter(selected))
|
||||
}
|
||||
(UiState::GroupByMenu(state), UiEvent::Select) => {
|
||||
let selected = state.selected();
|
||||
(true, state.enter(selected))
|
||||
}
|
||||
(UiState::ByIdentifier(state), UiEvent::Select) => {
|
||||
let selected = state.selected();
|
||||
(true, state.enter(selected, app))
|
||||
|
|
@ -524,6 +618,10 @@ impl<'a> UiState<'a> {
|
|||
|
||||
(
|
||||
UiState::Group(GroupState { previous, .. })
|
||||
| UiState::GroupList(GroupListState {
|
||||
previous: Some(previous),
|
||||
..
|
||||
})
|
||||
| UiState::ByIdentifier(LogsByIdentifierState { previous, .. })
|
||||
| UiState::Log(LogState { previous, .. })
|
||||
| UiState::Errors(ErrorLinesState { previous, .. }),
|
||||
|
|
@ -546,8 +644,8 @@ impl<'a> UiState<'a> {
|
|||
|
||||
#[derive(PartialEq)]
|
||||
pub enum UiPage {
|
||||
MatchList,
|
||||
Match,
|
||||
GroupList,
|
||||
Group,
|
||||
Logs,
|
||||
Log,
|
||||
Errors,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue