feat: Logs in own struct
Store the logs, and timestamp into a hashset, so that won't push data into the vec if it's already in the hashset, close #11
This commit is contained in:
@@ -1,4 +1,8 @@
|
|||||||
use std::{cmp::Ordering, collections::VecDeque, fmt};
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
collections::{HashSet, VecDeque},
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
use tui::{
|
use tui::{
|
||||||
style::Color,
|
style::Color,
|
||||||
@@ -352,6 +356,87 @@ impl fmt::Display for ByteStats {
|
|||||||
pub type MemTuple = (Vec<(f64, f64)>, ByteStats, State);
|
pub type MemTuple = (Vec<(f64, f64)>, ByteStats, State);
|
||||||
pub type CpuTuple = (Vec<(f64, f64)>, CpuStats, State);
|
pub type CpuTuple = (Vec<(f64, f64)>, CpuStats, State);
|
||||||
|
|
||||||
|
/// Used to make sure that each log entry, for each container, is unique,
|
||||||
|
/// will only push a log entry into the logs vec if timetstamp of said log entry isn't in the hashset
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
|
pub struct LogsTz(String);
|
||||||
|
|
||||||
|
/// The docker log, which should always contain a timestamp, is in the format `2023-01-14T19:13:30.783138328Z Lorem ipsum dolor sit amet`
|
||||||
|
/// So just split at the inclusive index of the first space, needs to be inclusive, hence the use of format to at the space, so that we can remove the whole thing when the `-t` flag is set
|
||||||
|
/// Need to make sure that this isn't an empty string?!
|
||||||
|
impl From<&String> for LogsTz {
|
||||||
|
fn from(value: &String) -> Self {
|
||||||
|
Self(value.split_inclusive(' ').take(1).collect::<String>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LogsTz {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store the logs alongside a HashSet, each log *should* generate a unique timestamp,
|
||||||
|
/// so if we store the timestamp seperately in a HashSet, we can then check if we should insert a log line into the
|
||||||
|
/// stateful list dependant on whethere the timestamp is in the HashSet or not
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Logs {
|
||||||
|
logs: StatefulList<ListItem<'static>>,
|
||||||
|
tz: HashSet<LogsTz>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Logs {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut logs = StatefulList::new(vec![]);
|
||||||
|
logs.end();
|
||||||
|
Self {
|
||||||
|
logs,
|
||||||
|
tz: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Logs {
|
||||||
|
/// 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: ListItem<'static>, tz: LogsTz) {
|
||||||
|
if self.tz.insert(tz) {
|
||||||
|
self.logs.items.push(line);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(&self) -> Vec<ListItem<'static>> {
|
||||||
|
self.logs.items.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The rest of the methods are basically forwarding from the underlying StatefulList
|
||||||
|
pub fn get_state_title(&self) -> String {
|
||||||
|
self.logs.get_state_title()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
self.logs.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
self.logs.previous();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end(&mut self) {
|
||||||
|
self.logs.end();
|
||||||
|
}
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
self.logs.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.logs.items.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&mut self) -> &mut ListState {
|
||||||
|
&mut self.logs.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Info for each container
|
/// Info for each container
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ContainerItem {
|
pub struct ContainerItem {
|
||||||
@@ -361,7 +446,7 @@ pub struct ContainerItem {
|
|||||||
pub id: ContainerId,
|
pub id: ContainerId,
|
||||||
pub image: String,
|
pub image: String,
|
||||||
pub last_updated: u64,
|
pub last_updated: u64,
|
||||||
pub logs: StatefulList<ListItem<'static>>,
|
pub logs: Logs,
|
||||||
pub mem_limit: ByteStats,
|
pub mem_limit: ByteStats,
|
||||||
pub mem_stats: VecDeque<ByteStats>,
|
pub mem_stats: VecDeque<ByteStats>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -385,8 +470,6 @@ impl ContainerItem {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let mut docker_controls = StatefulList::new(DockerControls::gen_vec(state));
|
let mut docker_controls = StatefulList::new(DockerControls::gen_vec(state));
|
||||||
docker_controls.start();
|
docker_controls.start();
|
||||||
let mut logs = StatefulList::new(vec![]);
|
|
||||||
logs.end();
|
|
||||||
Self {
|
Self {
|
||||||
created,
|
created,
|
||||||
cpu_stats: VecDeque::with_capacity(60),
|
cpu_stats: VecDeque::with_capacity(60),
|
||||||
@@ -395,7 +478,7 @@ impl ContainerItem {
|
|||||||
image,
|
image,
|
||||||
is_oxker,
|
is_oxker,
|
||||||
last_updated: 0,
|
last_updated: 0,
|
||||||
logs,
|
logs: Logs::default(),
|
||||||
mem_limit: ByteStats::default(),
|
mem_limit: ByteStats::default(),
|
||||||
mem_stats: VecDeque::with_capacity(60),
|
mem_stats: VecDeque::with_capacity(60),
|
||||||
name,
|
name,
|
||||||
@@ -479,14 +562,13 @@ impl Columns {
|
|||||||
Self {
|
Self {
|
||||||
state: (Header::State, 11),
|
state: (Header::State, 11),
|
||||||
status: (Header::Status, 16),
|
status: (Header::Status, 16),
|
||||||
// 7 to allow for "100.00%"
|
|
||||||
cpu: (Header::Cpu, 7),
|
cpu: (Header::Cpu, 7),
|
||||||
mem: (Header::Memory, 6, 6),
|
mem: (Header::Memory, 7, 7),
|
||||||
id: (Header::Id, 8),
|
id: (Header::Id, 8),
|
||||||
name: (Header::Name, 4),
|
name: (Header::Name, 4),
|
||||||
image: (Header::Image, 5),
|
image: (Header::Image, 5),
|
||||||
net_rx: (Header::Rx, 5),
|
net_rx: (Header::Rx, 7),
|
||||||
net_tx: (Header::Tx, 5),
|
net_tx: (Header::Tx, 7),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-19
@@ -12,7 +12,6 @@ pub use container_state::*;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AppData {
|
pub struct AppData {
|
||||||
error: Option<AppError>,
|
error: Option<AppError>,
|
||||||
logs_parsed: bool,
|
|
||||||
sorted_by: Option<(Header, SortedOrder)>,
|
sorted_by: Option<(Header, SortedOrder)>,
|
||||||
pub args: CliArgs,
|
pub args: CliArgs,
|
||||||
pub containers: StatefulList<ContainerItem>,
|
pub containers: StatefulList<ContainerItem>,
|
||||||
@@ -62,7 +61,6 @@ impl AppData {
|
|||||||
args,
|
args,
|
||||||
containers: StatefulList::new(vec![]),
|
containers: StatefulList::new(vec![]),
|
||||||
error: None,
|
error: None,
|
||||||
logs_parsed: false,
|
|
||||||
sorted_by: None,
|
sorted_by: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +191,7 @@ impl AppData {
|
|||||||
|
|
||||||
/// Check if the selected container is a dockerised version of oxker
|
/// Check if the selected container is a dockerised version of oxker
|
||||||
/// So that can disallow commands to be send
|
/// So that can disallow commands to be send
|
||||||
/// Is a poor way of implementing this
|
/// Is a shabby way of implementing this
|
||||||
pub fn selected_container_is_oxker(&self) -> bool {
|
pub fn selected_container_is_oxker(&self) -> bool {
|
||||||
if let Some(index) = self.containers.state.selected() {
|
if let Some(index) = self.containers.state.selected() {
|
||||||
if let Some(x) = self.containers.items.get(index) {
|
if let Some(x) = self.containers.items.get(index) {
|
||||||
@@ -352,7 +350,7 @@ impl AppData {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|i| !i.cpu_stats.is_empty())
|
.filter(|i| !i.cpu_stats.is_empty())
|
||||||
.count();
|
.count();
|
||||||
self.logs_parsed && count_is_running == number_with_cpu_status
|
count_is_running == number_with_cpu_status
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Just get the total number of containers
|
/// Just get the total number of containers
|
||||||
@@ -382,7 +380,13 @@ impl AppData {
|
|||||||
let name_count = count(&container.name);
|
let name_count = count(&container.name);
|
||||||
let state_count = count(&container.state.to_string());
|
let state_count = count(&container.state.to_string());
|
||||||
let status_count = count(&container.status);
|
let status_count = count(&container.status);
|
||||||
let mem_current_count = count(&container.mem_stats.back().unwrap_or(&ByteStats::default()).to_string());
|
let mem_current_count = count(
|
||||||
|
&container
|
||||||
|
.mem_stats
|
||||||
|
.back()
|
||||||
|
.unwrap_or(&ByteStats::default())
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
let mem_limit_count = count(&container.mem_limit.to_string());
|
let mem_limit_count = count(&container.mem_limit.to_string());
|
||||||
|
|
||||||
if cpu_count > output.cpu.1 {
|
if cpu_count > output.cpu.1 {
|
||||||
@@ -548,8 +552,8 @@ impl AppData {
|
|||||||
if item.image != image {
|
if item.image != image {
|
||||||
item.image = image;
|
item.image = image;
|
||||||
};
|
};
|
||||||
// else container not known, so make new ContainerItem and push into containers Vec
|
|
||||||
} else {
|
} else {
|
||||||
|
// container not known, so make new ContainerItem and push into containers Vec
|
||||||
let container =
|
let container =
|
||||||
ContainerItem::new(created, id, image, is_oxker, name, state, status);
|
ContainerItem::new(created, id, image, is_oxker, name, state, status);
|
||||||
self.containers.items.push(container);
|
self.containers.items.push(container);
|
||||||
@@ -559,34 +563,39 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// update logs of a given container, based on id
|
/// update logs of a given container, based on id
|
||||||
pub fn update_log_by_id(&mut self, output: &[String], id: &ContainerId) {
|
pub fn update_log_by_id(&mut self, output: Vec<String>, id: &ContainerId) {
|
||||||
let tz = Self::get_systemtime();
|
|
||||||
let color = self.args.color;
|
let color = self.args.color;
|
||||||
let raw = self.args.raw;
|
let raw = self.args.raw;
|
||||||
|
|
||||||
if let Some(container) = self.get_container_by_id(id) {
|
let timestamp = self.args.timestamp;
|
||||||
container.last_updated = tz;
|
|
||||||
let current_len = container.logs.items.len();
|
|
||||||
|
|
||||||
for i in output {
|
if let Some(container) = self.get_container_by_id(id) {
|
||||||
|
container.last_updated = Self::get_systemtime();
|
||||||
|
let current_len = container.logs.len();
|
||||||
|
|
||||||
|
for mut i in output {
|
||||||
|
let tz = LogsTz::from(&i);
|
||||||
|
// Strip the timestamp if `-t` flag set
|
||||||
|
if !timestamp {
|
||||||
|
i = i.replace(&tz.to_string(), "");
|
||||||
|
}
|
||||||
let lines = if color {
|
let lines = if color {
|
||||||
log_sanitizer::colorize_logs(i)
|
log_sanitizer::colorize_logs(&i)
|
||||||
} else if raw {
|
} else if raw {
|
||||||
log_sanitizer::raw(i)
|
log_sanitizer::raw(&i)
|
||||||
} else {
|
} else {
|
||||||
log_sanitizer::remove_ansi(i)
|
log_sanitizer::remove_ansi(&i)
|
||||||
};
|
};
|
||||||
container.logs.items.push(ListItem::new(lines));
|
container.logs.insert(ListItem::new(lines), tz);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the logs selected row for each container
|
// Set the logs selected row for each container
|
||||||
// Either when no long currently selected, or currently selected (before updated) is already at end
|
// Either when no long currently selected, or currently selected (before updated) is already at end
|
||||||
if container.logs.state.selected().is_none()
|
if container.logs.state().selected().is_none()
|
||||||
|| container.logs.state.selected().map_or(1, |f| f + 1) == current_len
|
|| container.logs.state().selected().map_or(1, |f| f + 1) == current_len
|
||||||
{
|
{
|
||||||
container.logs.end();
|
container.logs.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.logs_parsed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user