mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-23 20:53:07 +00:00
feature: add the ability to configure the disk widget's table columns (#1625)
* a bit of refactoring here... * some refactoring, add columns * cleanup * add disk column feature * update changelog
This commit is contained in:
parent
c8cba49463
commit
196d6d18c6
21 changed files with 413 additions and 154 deletions
|
@ -22,6 +22,10 @@ That said, these are more guidelines rather than hardset rules, though the proje
|
|||
|
||||
## [0.11.0] - Unreleased
|
||||
|
||||
### Features
|
||||
|
||||
- [#1625](https://github.com/ClementTsang/bottom/pull/1625): Add the ability to configure the disk widget's table columns.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [#1551](https://github.com/ClementTsang/bottom/pull/1551): Fix missing parent section names in default config.
|
||||
|
|
|
@ -8,24 +8,34 @@
|
|||
[flags]
|
||||
# Whether to hide the average cpu entry.
|
||||
#hide_avg_cpu = false
|
||||
|
||||
# Whether to use dot markers rather than braille.
|
||||
#dot_marker = false
|
||||
|
||||
# The update rate of the application.
|
||||
#rate = "1s"
|
||||
|
||||
# Whether to put the CPU legend to the left.
|
||||
#cpu_left_legend = false
|
||||
|
||||
# Whether to set CPU% on a process to be based on the total CPU or just current usage.
|
||||
#current_usage = false
|
||||
|
||||
# Whether to set CPU% on a process to be based on the total CPU or per-core CPU% (not divided by the number of cpus).
|
||||
#unnormalized_cpu = false
|
||||
|
||||
# Whether to group processes with the same name together by default.
|
||||
#group_processes = false
|
||||
|
||||
# Whether to make process searching case sensitive by default.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to make process searching look for matching the entire word by default.
|
||||
#whole_word = false
|
||||
|
||||
# Whether to make process searching use regex by default.
|
||||
#regex = false
|
||||
|
||||
# The temperature unit. One of the following, defaults to "c" for Celsius:
|
||||
#temperature_type = "c"
|
||||
##temperature_type = "k"
|
||||
|
@ -33,79 +43,112 @@
|
|||
##temperature_type = "kelvin"
|
||||
##temperature_type = "fahrenheit"
|
||||
##temperature_type = "celsius"
|
||||
|
||||
# The default time interval (in milliseconds).
|
||||
#default_time_value = "60s"
|
||||
|
||||
# The time delta on each zoom in/out action (in milliseconds).
|
||||
#time_delta = 15000
|
||||
|
||||
# Hides the time scale.
|
||||
#hide_time = false
|
||||
|
||||
# Override layout default widget
|
||||
#default_widget_type = "proc"
|
||||
#default_widget_count = 1
|
||||
|
||||
# Expand selected widget upon starting the app
|
||||
#expanded = true
|
||||
|
||||
# Use basic mode
|
||||
#basic = false
|
||||
|
||||
# Use the old network legend style
|
||||
#use_old_network_legend = false
|
||||
|
||||
# Remove space in tables
|
||||
#hide_table_gap = false
|
||||
|
||||
# Show the battery widgets
|
||||
#battery = false
|
||||
|
||||
# Disable mouse clicks
|
||||
#disable_click = false
|
||||
|
||||
# Show memory values in the processes widget as values by default
|
||||
#process_memory_as_value = false
|
||||
|
||||
# Show tree mode by default in the processes widget.
|
||||
#tree = false
|
||||
|
||||
# Shows an indicator in table widgets tracking where in the list you are.
|
||||
#show_table_scroll_position = false
|
||||
|
||||
# Show processes as their commands by default in the process widget.
|
||||
#process_command = false
|
||||
|
||||
# Displays the network widget with binary prefixes.
|
||||
#network_use_binary_prefix = false
|
||||
|
||||
# Displays the network widget using bytes.
|
||||
#network_use_bytes = false
|
||||
|
||||
# Displays the network widget with a log scale.
|
||||
#network_use_log = false
|
||||
|
||||
# Hides advanced options to stop a process on Unix-like systems.
|
||||
#disable_advanced_kill = false
|
||||
|
||||
# Hide GPU(s) information
|
||||
#disable_gpu = false
|
||||
|
||||
# Shows cache and buffer memory
|
||||
#enable_cache_memory = false
|
||||
|
||||
# How much data is stored at once in terms of time.
|
||||
#retention = "10m"
|
||||
|
||||
# Where to place the legend for the memory widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
|
||||
#memory_legend = "top-right"
|
||||
|
||||
# Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
|
||||
#network_legend = "top-right"
|
||||
|
||||
|
||||
# Processes widget configuration
|
||||
#[processes]
|
||||
# The columns shown by the process widget. The following columns are supported (the GPU columns are only available if the GPU feature is enabled when built):
|
||||
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%
|
||||
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"]
|
||||
|
||||
|
||||
# CPU widget configuration
|
||||
#[cpu]
|
||||
# One of "all" (default), "average"/"avg"
|
||||
# default = "average"
|
||||
#default = "average"
|
||||
|
||||
|
||||
# Disk widget configuration
|
||||
#[disk]
|
||||
# The columns shown by the process widget. The following columns are supported:
|
||||
# Disk, Mount, Used, Free, Total, Used%, Free%, R/s, W/s
|
||||
#columns = ["Disk", "Mount", "Used", "Free", "Total", "Used%", "R/s", "W/s"]
|
||||
|
||||
# By default, there are no disk name filters enabled. These can be turned on to filter out specific data entries if you
|
||||
# don't want to see them. An example use case is provided below.
|
||||
#[disk.name_filter]
|
||||
# Whether to ignore any matches. Defaults to true.
|
||||
#is_list_ignored = true
|
||||
|
||||
# A list of filters to try and match.
|
||||
#list = ["/dev/sda\\d+", "/dev/nvme0n1p2"]
|
||||
|
||||
# Whether to use regex. Defaults to false.
|
||||
#regex = true
|
||||
|
||||
# Whether to be case-sensitive. Defaults to false.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to be require matching the whole word. Defaults to false.
|
||||
#whole_word = false
|
||||
|
||||
|
@ -113,51 +156,63 @@
|
|||
#[disk.mount_filter]
|
||||
# Whether to ignore any matches. Defaults to true.
|
||||
#is_list_ignored = true
|
||||
|
||||
# A list of filters to try and match.
|
||||
#list = ["/mnt/.*", "/boot"]
|
||||
|
||||
# Whether to use regex. Defaults to false.
|
||||
#regex = true
|
||||
|
||||
# Whether to be case-sensitive. Defaults to false.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to be require matching the whole word. Defaults to false.
|
||||
#whole_word = false
|
||||
|
||||
|
||||
# Temperature widget configuration
|
||||
#[temperature]
|
||||
|
||||
# By default, there are no temperature sensor filters enabled. An example use case is provided below.
|
||||
#[temperature.sensor_filter]
|
||||
# Whether to ignore any matches. Defaults to true.
|
||||
#is_list_ignored = true
|
||||
|
||||
# A list of filters to try and match.
|
||||
#list = ["cpu", "wifi"]
|
||||
|
||||
# Whether to use regex. Defaults to false.
|
||||
#regex = false
|
||||
|
||||
# Whether to be case-sensitive. Defaults to false.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to be require matching the whole word. Defaults to false.
|
||||
#whole_word = false
|
||||
|
||||
|
||||
# Network widget configuration
|
||||
#[network]
|
||||
|
||||
# By default, there are no network interface filters enabled. An example use case is provided below.
|
||||
#[network.interface_filter]
|
||||
# Whether to ignore any matches. Defaults to true.
|
||||
#is_list_ignored = true
|
||||
|
||||
# A list of filters to try and match.
|
||||
#list = ["virbr0.*"]
|
||||
|
||||
# Whether to use regex. Defaults to false.
|
||||
#regex = true
|
||||
|
||||
# Whether to be case-sensitive. Defaults to false.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to be require matching the whole word. Defaults to false.
|
||||
#whole_word = false
|
||||
|
||||
|
||||
# These are all the components that support custom theming. Note that colour support
|
||||
# will depend on terminal support.
|
||||
#[styles] # Uncomment if you want to use custom styling
|
||||
|
||||
# Built-in themes. Valid values are:
|
||||
# - "default"
|
||||
# - "default-light"
|
||||
|
|
|
@ -183,10 +183,35 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"DiskColumn": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Disk",
|
||||
"Free",
|
||||
"Free%",
|
||||
"Mount",
|
||||
"R/s",
|
||||
"Read",
|
||||
"Rps",
|
||||
"Total",
|
||||
"Used",
|
||||
"Used%",
|
||||
"W/s",
|
||||
"Wps",
|
||||
"Write"
|
||||
]
|
||||
},
|
||||
"DiskConfig": {
|
||||
"description": "Disk configuration.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"columns": {
|
||||
"description": "A list of disk widget columns.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/DiskColumn"
|
||||
}
|
||||
},
|
||||
"mount_filter": {
|
||||
"description": "A filter over the mount names.",
|
||||
"anyOf": [
|
||||
|
|
|
@ -51,7 +51,7 @@ def main():
|
|||
read_file = re.sub(
|
||||
r"^#(\s\s+)([a-zA-Z\[])", r"\2", read_file, flags=re.MULTILINE
|
||||
)
|
||||
print(f"uncommented file: \n{read_file}")
|
||||
print(f"uncommented file: \n{read_file}\n=====\n")
|
||||
|
||||
toml_str = tomllib.loads(read_file)
|
||||
else:
|
||||
|
|
|
@ -25,7 +25,7 @@ use crate::{
|
|||
processes::{Pid, ProcessHarvest},
|
||||
temperature, Data,
|
||||
},
|
||||
utils::data_prefixes::*,
|
||||
dec_bytes_per_second_string,
|
||||
};
|
||||
|
||||
pub type TimeOffset = f64;
|
||||
|
@ -423,20 +423,11 @@ impl DataCollection {
|
|||
*io_curr = (r_rate, w_rate);
|
||||
*io_prev = (io_r_pt, io_w_pt);
|
||||
|
||||
// TODO: idk why I'm generating this here tbh
|
||||
if let Some(io_labels) = self.io_labels.get_mut(itx) {
|
||||
let converted_read = get_decimal_bytes(r_rate);
|
||||
let converted_write = get_decimal_bytes(w_rate);
|
||||
*io_labels = (
|
||||
if r_rate >= GIGA_LIMIT {
|
||||
format!("{:.*}{}/s", 1, converted_read.0, converted_read.1)
|
||||
} else {
|
||||
format!("{:.*}{}/s", 0, converted_read.0, converted_read.1)
|
||||
},
|
||||
if w_rate >= GIGA_LIMIT {
|
||||
format!("{:.*}{}/s", 1, converted_write.0, converted_write.1)
|
||||
} else {
|
||||
format!("{:.*}{}/s", 0, converted_write.0, converted_write.1)
|
||||
},
|
||||
dec_bytes_per_second_string(r_rate),
|
||||
dec_bytes_per_second_string(w_rate),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,16 @@ impl SortOrder {
|
|||
SortOrder::Descending => SortOrder::Ascending,
|
||||
}
|
||||
}
|
||||
|
||||
/// A hack to get a const default.
|
||||
pub const fn const_default() -> Self {
|
||||
Self::Ascending
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortOrder {
|
||||
fn default() -> Self {
|
||||
Self::Ascending
|
||||
Self::const_default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,18 +200,18 @@ where
|
|||
|
||||
/// Creates a new [`SortColumn`] with a hard width, which has no shortcut
|
||||
/// and sorts by default in ascending order ([`SortOrder::Ascending`]).
|
||||
pub fn hard(inner: T, width: u16) -> Self {
|
||||
pub const fn hard(inner: T, width: u16) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
bounds: ColumnWidthBounds::Hard(width),
|
||||
is_hidden: false,
|
||||
default_order: SortOrder::default(),
|
||||
default_order: SortOrder::const_default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`SortColumn`] with a soft width, which has no shortcut
|
||||
/// and sorts by default in ascending order ([`SortOrder::Ascending`]).
|
||||
pub fn soft(inner: T, max_percentage: Option<f32>) -> Self {
|
||||
pub const fn soft(inner: T, max_percentage: Option<f32>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
bounds: ColumnWidthBounds::Soft {
|
||||
|
@ -214,7 +219,7 @@ where
|
|||
max_percentage,
|
||||
},
|
||||
is_hidden: false,
|
||||
default_order: SortOrder::default(),
|
||||
default_order: SortOrder::const_default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,7 +230,7 @@ where
|
|||
}
|
||||
|
||||
/// Sets the default sort order to [`SortOrder::Descending`].
|
||||
pub fn default_descending(mut self) -> Self {
|
||||
pub const fn default_descending(mut self) -> Self {
|
||||
self.default_order = SortOrder::Descending;
|
||||
self
|
||||
}
|
||||
|
|
|
@ -268,24 +268,34 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
|
|||
[flags]
|
||||
# Whether to hide the average cpu entry.
|
||||
#hide_avg_cpu = false
|
||||
|
||||
# Whether to use dot markers rather than braille.
|
||||
#dot_marker = false
|
||||
|
||||
# The update rate of the application.
|
||||
#rate = "1s"
|
||||
|
||||
# Whether to put the CPU legend to the left.
|
||||
#cpu_left_legend = false
|
||||
|
||||
# Whether to set CPU% on a process to be based on the total CPU or just current usage.
|
||||
#current_usage = false
|
||||
|
||||
# Whether to set CPU% on a process to be based on the total CPU or per-core CPU% (not divided by the number of cpus).
|
||||
#unnormalized_cpu = false
|
||||
|
||||
# Whether to group processes with the same name together by default.
|
||||
#group_processes = false
|
||||
|
||||
# Whether to make process searching case sensitive by default.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to make process searching look for matching the entire word by default.
|
||||
#whole_word = false
|
||||
|
||||
# Whether to make process searching use regex by default.
|
||||
#regex = false
|
||||
|
||||
# The temperature unit. One of the following, defaults to "c" for Celsius:
|
||||
#temperature_type = "c"
|
||||
##temperature_type = "k"
|
||||
|
@ -293,79 +303,112 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
|
|||
##temperature_type = "kelvin"
|
||||
##temperature_type = "fahrenheit"
|
||||
##temperature_type = "celsius"
|
||||
|
||||
# The default time interval (in milliseconds).
|
||||
#default_time_value = "60s"
|
||||
|
||||
# The time delta on each zoom in/out action (in milliseconds).
|
||||
#time_delta = 15000
|
||||
|
||||
# Hides the time scale.
|
||||
#hide_time = false
|
||||
|
||||
# Override layout default widget
|
||||
#default_widget_type = "proc"
|
||||
#default_widget_count = 1
|
||||
|
||||
# Expand selected widget upon starting the app
|
||||
#expanded = true
|
||||
|
||||
# Use basic mode
|
||||
#basic = false
|
||||
|
||||
# Use the old network legend style
|
||||
#use_old_network_legend = false
|
||||
|
||||
# Remove space in tables
|
||||
#hide_table_gap = false
|
||||
|
||||
# Show the battery widgets
|
||||
#battery = false
|
||||
|
||||
# Disable mouse clicks
|
||||
#disable_click = false
|
||||
|
||||
# Show memory values in the processes widget as values by default
|
||||
#process_memory_as_value = false
|
||||
|
||||
# Show tree mode by default in the processes widget.
|
||||
#tree = false
|
||||
|
||||
# Shows an indicator in table widgets tracking where in the list you are.
|
||||
#show_table_scroll_position = false
|
||||
|
||||
# Show processes as their commands by default in the process widget.
|
||||
#process_command = false
|
||||
|
||||
# Displays the network widget with binary prefixes.
|
||||
#network_use_binary_prefix = false
|
||||
|
||||
# Displays the network widget using bytes.
|
||||
#network_use_bytes = false
|
||||
|
||||
# Displays the network widget with a log scale.
|
||||
#network_use_log = false
|
||||
|
||||
# Hides advanced options to stop a process on Unix-like systems.
|
||||
#disable_advanced_kill = false
|
||||
|
||||
# Hide GPU(s) information
|
||||
#disable_gpu = false
|
||||
|
||||
# Shows cache and buffer memory
|
||||
#enable_cache_memory = false
|
||||
|
||||
# How much data is stored at once in terms of time.
|
||||
#retention = "10m"
|
||||
|
||||
# Where to place the legend for the memory widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
|
||||
#memory_legend = "top-right"
|
||||
|
||||
# Where to place the legend for the network widget. One of "none", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom", "bottom-right".
|
||||
#network_legend = "top-right"
|
||||
|
||||
|
||||
# Processes widget configuration
|
||||
#[processes]
|
||||
# The columns shown by the process widget. The following columns are supported (the GPU columns are only available if the GPU feature is enabled when built):
|
||||
# PID, Name, CPU%, Mem%, R/s, W/s, T.Read, T.Write, User, State, Time, GMem%, GPU%
|
||||
#columns = ["PID", "Name", "CPU%", "Mem%", "R/s", "W/s", "T.Read", "T.Write", "User", "State", "GMem%", "GPU%"]
|
||||
|
||||
|
||||
# CPU widget configuration
|
||||
#[cpu]
|
||||
# One of "all" (default), "average"/"avg"
|
||||
# default = "average"
|
||||
#default = "average"
|
||||
|
||||
|
||||
# Disk widget configuration
|
||||
#[disk]
|
||||
# The columns shown by the process widget. The following columns are supported:
|
||||
# Disk, Mount, Used, Free, Total, Used%, Free%, R/s, W/s
|
||||
#columns = ["Disk", "Mount", "Used", "Free", "Total", "Used%", "R/s", "W/s"]
|
||||
|
||||
# By default, there are no disk name filters enabled. These can be turned on to filter out specific data entries if you
|
||||
# don't want to see them. An example use case is provided below.
|
||||
#[disk.name_filter]
|
||||
# Whether to ignore any matches. Defaults to true.
|
||||
#is_list_ignored = true
|
||||
|
||||
# A list of filters to try and match.
|
||||
#list = ["/dev/sda\\d+", "/dev/nvme0n1p2"]
|
||||
|
||||
# Whether to use regex. Defaults to false.
|
||||
#regex = true
|
||||
|
||||
# Whether to be case-sensitive. Defaults to false.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to be require matching the whole word. Defaults to false.
|
||||
#whole_word = false
|
||||
|
||||
|
@ -373,51 +416,63 @@ pub(crate) const CONFIG_TEXT: &str = r#"# This is a default config file for bott
|
|||
#[disk.mount_filter]
|
||||
# Whether to ignore any matches. Defaults to true.
|
||||
#is_list_ignored = true
|
||||
|
||||
# A list of filters to try and match.
|
||||
#list = ["/mnt/.*", "/boot"]
|
||||
|
||||
# Whether to use regex. Defaults to false.
|
||||
#regex = true
|
||||
|
||||
# Whether to be case-sensitive. Defaults to false.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to be require matching the whole word. Defaults to false.
|
||||
#whole_word = false
|
||||
|
||||
|
||||
# Temperature widget configuration
|
||||
#[temperature]
|
||||
|
||||
# By default, there are no temperature sensor filters enabled. An example use case is provided below.
|
||||
#[temperature.sensor_filter]
|
||||
# Whether to ignore any matches. Defaults to true.
|
||||
#is_list_ignored = true
|
||||
|
||||
# A list of filters to try and match.
|
||||
#list = ["cpu", "wifi"]
|
||||
|
||||
# Whether to use regex. Defaults to false.
|
||||
#regex = false
|
||||
|
||||
# Whether to be case-sensitive. Defaults to false.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to be require matching the whole word. Defaults to false.
|
||||
#whole_word = false
|
||||
|
||||
|
||||
# Network widget configuration
|
||||
#[network]
|
||||
|
||||
# By default, there are no network interface filters enabled. An example use case is provided below.
|
||||
#[network.interface_filter]
|
||||
# Whether to ignore any matches. Defaults to true.
|
||||
#is_list_ignored = true
|
||||
|
||||
# A list of filters to try and match.
|
||||
#list = ["virbr0.*"]
|
||||
|
||||
# Whether to use regex. Defaults to false.
|
||||
#regex = true
|
||||
|
||||
# Whether to be case-sensitive. Defaults to false.
|
||||
#case_sensitive = false
|
||||
|
||||
# Whether to be require matching the whole word. Defaults to false.
|
||||
#whole_word = false
|
||||
|
||||
|
||||
# These are all the components that support custom theming. Note that colour support
|
||||
# will depend on terminal support.
|
||||
#[styles] # Uncomment if you want to use custom styling
|
||||
|
||||
# Built-in themes. Valid values are:
|
||||
# - "default"
|
||||
# - "default-light"
|
||||
|
|
|
@ -402,15 +402,15 @@ pub fn convert_network_points(
|
|||
};
|
||||
|
||||
if need_four_points {
|
||||
let rx_display = format!("{:.*}{}", 1, rx_converted_result.0, rx_converted_result.1);
|
||||
let rx_display = format!("{:.1}{}", rx_converted_result.0, rx_converted_result.1);
|
||||
let total_rx_display = Some(format!(
|
||||
"{:.*}{}",
|
||||
1, total_rx_converted_result.0, total_rx_converted_result.1
|
||||
"{:.1}{}",
|
||||
total_rx_converted_result.0, total_rx_converted_result.1
|
||||
));
|
||||
let tx_display = format!("{:.*}{}", 1, tx_converted_result.0, tx_converted_result.1);
|
||||
let tx_display = format!("{:.1}{}", tx_converted_result.0, tx_converted_result.1);
|
||||
let total_tx_display = Some(format!(
|
||||
"{:.*}{}",
|
||||
1, total_tx_converted_result.0, total_tx_converted_result.1
|
||||
"{:.1}{}",
|
||||
total_tx_converted_result.0, total_tx_converted_result.1
|
||||
));
|
||||
ConvertedNetworkData {
|
||||
rx,
|
||||
|
@ -474,35 +474,38 @@ pub fn convert_network_points(
|
|||
/// Returns a string given a value that is converted to the closest binary
|
||||
/// variant. If the value is greater than a gibibyte, then it will return a
|
||||
/// decimal place.
|
||||
#[inline]
|
||||
pub fn binary_byte_string(value: u64) -> String {
|
||||
let converted_values = get_binary_bytes(value);
|
||||
if value >= GIBI_LIMIT {
|
||||
format!("{:.*}{}", 1, converted_values.0, converted_values.1)
|
||||
format!("{:.1}{}", converted_values.0, converted_values.1)
|
||||
} else {
|
||||
format!("{:.*}{}", 0, converted_values.0, converted_values.1)
|
||||
format!("{:.0}{}", converted_values.0, converted_values.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a string given a value that is converted to the closest SI-variant.
|
||||
/// If the value is greater than a giga-X, then it will return a decimal place.
|
||||
#[inline]
|
||||
pub fn dec_bytes_per_string(value: u64) -> String {
|
||||
let converted_values = get_decimal_bytes(value);
|
||||
if value >= GIGA_LIMIT {
|
||||
format!("{:.*}{}", 1, converted_values.0, converted_values.1)
|
||||
format!("{:.1}{}", converted_values.0, converted_values.1)
|
||||
} else {
|
||||
format!("{:.*}{}", 0, converted_values.0, converted_values.1)
|
||||
format!("{:.0}{}", converted_values.0, converted_values.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a string given a value that is converted to the closest SI-variant,
|
||||
/// per second. If the value is greater than a giga-X, then it will return a
|
||||
/// decimal place.
|
||||
#[inline]
|
||||
pub fn dec_bytes_per_second_string(value: u64) -> String {
|
||||
let converted_values = get_decimal_bytes(value);
|
||||
if value >= GIGA_LIMIT {
|
||||
format!("{:.*}{}/s", 1, converted_values.0, converted_values.1)
|
||||
format!("{:.1}{}/s", converted_values.0, converted_values.1)
|
||||
} else {
|
||||
format!("{:.*}{}/s", 0, converted_values.0, converted_values.1)
|
||||
format!("{:.0}{}/s", converted_values.0, converted_values.1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -511,9 +514,9 @@ pub fn dec_bytes_per_second_string(value: u64) -> String {
|
|||
pub fn dec_bytes_string(value: u64) -> String {
|
||||
let converted_values = get_decimal_bytes(value);
|
||||
if value >= GIGA_LIMIT {
|
||||
format!("{:.*}{}", 1, converted_values.0, converted_values.1)
|
||||
format!("{:.1}{}", converted_values.0, converted_values.1)
|
||||
} else {
|
||||
format!("{:.*}{}", 0, converted_values.0, converted_values.1)
|
||||
format!("{:.0}{}", converted_values.0, converted_values.1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -279,6 +279,8 @@ fn generate_schema() -> anyhow::Result<()> {
|
|||
use itertools::Itertools;
|
||||
use strum::VariantArray;
|
||||
|
||||
// TODO: Maybe make this case insensitive? See https://stackoverflow.com/a/68639341
|
||||
|
||||
let proc_columns = schema.definitions.get_mut("ProcColumn").unwrap();
|
||||
match proc_columns {
|
||||
schemars::schema::Schema::Object(proc_columns) => {
|
||||
|
@ -293,6 +295,21 @@ fn generate_schema() -> anyhow::Result<()> {
|
|||
}
|
||||
_ => anyhow::bail!("missing proc columns definition"),
|
||||
}
|
||||
|
||||
let disk_columns = schema.definitions.get_mut("DiskColumn").unwrap();
|
||||
match disk_columns {
|
||||
schemars::schema::Schema::Object(disk_columns) => {
|
||||
let enums = disk_columns.enum_values.as_mut().unwrap();
|
||||
*enums = widgets::DiskColumn::VARIANTS
|
||||
.iter()
|
||||
.flat_map(|var| var.get_schema_names())
|
||||
.sorted()
|
||||
.map(|v| serde_json::Value::String(v.to_string()))
|
||||
.dedup()
|
||||
.collect();
|
||||
}
|
||||
_ => anyhow::bail!("missing disk columns definition"),
|
||||
}
|
||||
}
|
||||
|
||||
let metadata = schema.schema.metadata.as_mut().unwrap();
|
||||
|
|
|
@ -403,7 +403,11 @@ pub(crate) fn init_app(
|
|||
Disk => {
|
||||
disk_state_map.insert(
|
||||
widget.widget_id,
|
||||
DiskTableWidget::new(&app_config_fields, &styling),
|
||||
DiskTableWidget::new(
|
||||
&app_config_fields,
|
||||
&styling,
|
||||
config.disk.as_ref().map(|cfg| cfg.columns.as_slice()),
|
||||
),
|
||||
);
|
||||
}
|
||||
Temp => {
|
||||
|
|
|
@ -6,7 +6,7 @@ use serde::Deserialize;
|
|||
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub enum CpuDefault {
|
||||
pub(crate) enum CpuDefault {
|
||||
#[default]
|
||||
All,
|
||||
#[serde(alias = "avg")]
|
||||
|
@ -17,9 +17,9 @@ pub enum CpuDefault {
|
|||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
|
||||
#[cfg_attr(test, serde(deny_unknown_fields), derive(PartialEq, Eq))]
|
||||
pub struct CpuConfig {
|
||||
pub(crate) struct CpuConfig {
|
||||
#[serde(default)]
|
||||
pub default: CpuDefault,
|
||||
pub(crate) default: CpuDefault,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,15 +1,45 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::options::DiskColumn;
|
||||
|
||||
use super::IgnoreList;
|
||||
|
||||
/// Disk configuration.
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
|
||||
#[cfg_attr(test, serde(deny_unknown_fields), derive(PartialEq, Eq))]
|
||||
pub struct DiskConfig {
|
||||
pub(crate) struct DiskConfig {
|
||||
/// A filter over the disk names.
|
||||
pub name_filter: Option<IgnoreList>,
|
||||
pub(crate) name_filter: Option<IgnoreList>,
|
||||
|
||||
/// A filter over the mount names.
|
||||
pub mount_filter: Option<IgnoreList>,
|
||||
pub(crate) mount_filter: Option<IgnoreList>,
|
||||
|
||||
/// A list of disk widget columns.
|
||||
#[serde(default)]
|
||||
pub(crate) columns: Vec<DiskColumn>, // TODO: make this more composable(?) in the future, we might need to rethink how it's done for custom widgets
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::DiskConfig;
|
||||
|
||||
#[test]
|
||||
fn empty_column_setting() {
|
||||
let config = "";
|
||||
let generated: DiskConfig = toml_edit::de::from_str(config).unwrap();
|
||||
assert!(generated.columns.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_disk_column_settings() {
|
||||
let config = r#"columns = ["disk", "mount", "used", "free", "total", "used%", "free%", "r/s", "w/s"]"#;
|
||||
toml_edit::de::from_str::<DiskConfig>(config).expect("Should succeed!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_disk_column_settings() {
|
||||
let config = r#"columns = ["diskk"]"#;
|
||||
toml_edit::de::from_str::<DiskConfig>(config).expect_err("Should error out!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use super::IgnoreList;
|
|||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
|
||||
#[cfg_attr(test, serde(deny_unknown_fields), derive(PartialEq, Eq))]
|
||||
pub struct NetworkConfig {
|
||||
pub(crate) struct NetworkConfig {
|
||||
/// A filter over the network interface names.
|
||||
pub interface_filter: Option<IgnoreList>,
|
||||
pub(crate) interface_filter: Option<IgnoreList>,
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@ use crate::widgets::ProcColumn;
|
|||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
|
||||
#[cfg_attr(test, serde(deny_unknown_fields), derive(PartialEq, Eq))]
|
||||
pub struct ProcessesConfig {
|
||||
pub(crate) struct ProcessesConfig {
|
||||
/// A list of process widget columns.
|
||||
#[serde(default)]
|
||||
pub columns: Vec<ProcColumn>,
|
||||
pub(crate) columns: Vec<ProcColumn>, // TODO: make this more composable(?) in the future, we might need to rethink how it's done for custom widgets
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -32,7 +32,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn process_column_settings() {
|
||||
fn valid_process_column_config() {
|
||||
let config = r#"
|
||||
columns = ["CPU%", "PiD", "user", "MEM", "Tread", "T.Write", "Rps", "W/s", "tiMe", "USER", "state"]
|
||||
"#;
|
||||
|
@ -57,13 +57,13 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn process_column_settings_2() {
|
||||
fn bad_process_column_config() {
|
||||
let config = r#"columns = ["MEM", "TWrite", "Cpuz", "read", "wps"]"#;
|
||||
toml_edit::de::from_str::<ProcessesConfig>(config).expect_err("Should error out!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_column_settings_3() {
|
||||
fn valid_process_column_config_2() {
|
||||
let config = r#"columns = ["Twrite", "T.Write"]"#;
|
||||
let generated: ProcessesConfig = toml_edit::de::from_str(config).unwrap();
|
||||
assert_eq!(
|
||||
|
|
|
@ -6,7 +6,7 @@ use super::IgnoreList;
|
|||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[cfg_attr(feature = "generate_schema", derive(schemars::JsonSchema))]
|
||||
#[cfg_attr(test, serde(deny_unknown_fields), derive(PartialEq, Eq))]
|
||||
pub struct TempConfig {
|
||||
pub(crate) struct TempConfig {
|
||||
/// A filter over the sensor names.
|
||||
pub sensor_filter: Option<IgnoreList>,
|
||||
pub(crate) sensor_filter: Option<IgnoreList>,
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ pub struct CpuWidgetState {
|
|||
}
|
||||
|
||||
impl CpuWidgetState {
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
config: &AppConfigFields, default_selection: CpuDefault, current_display_time: u64,
|
||||
autohide_timer: Option<Instant>, colours: &ColourPalette,
|
||||
) -> Self {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use std::{borrow::Cow, cmp::max, num::NonZeroU16};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
app::AppConfigFields,
|
||||
canvas::components::data_table::{
|
||||
|
@ -26,11 +28,7 @@ impl DiskWidgetData {
|
|||
fn total_space(&self) -> Cow<'static, str> {
|
||||
if let Some(total_bytes) = self.total_bytes {
|
||||
let converted_total_space = get_decimal_bytes(total_bytes);
|
||||
format!(
|
||||
"{:.*}{}",
|
||||
0, converted_total_space.0, converted_total_space.1
|
||||
)
|
||||
.into()
|
||||
format!("{:.0}{}", converted_total_space.0, converted_total_space.1).into()
|
||||
} else {
|
||||
"N/A".into()
|
||||
}
|
||||
|
@ -39,7 +37,7 @@ impl DiskWidgetData {
|
|||
fn free_space(&self) -> Cow<'static, str> {
|
||||
if let Some(free_bytes) = self.free_bytes {
|
||||
let converted_free_space = get_decimal_bytes(free_bytes);
|
||||
format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1).into()
|
||||
format!("{:.0}{}", converted_free_space.0, converted_free_space.1).into()
|
||||
} else {
|
||||
"N/A".into()
|
||||
}
|
||||
|
@ -48,7 +46,7 @@ impl DiskWidgetData {
|
|||
fn used_space(&self) -> Cow<'static, str> {
|
||||
if let Some(used_bytes) = self.used_bytes {
|
||||
let converted_free_space = get_decimal_bytes(used_bytes);
|
||||
format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1).into()
|
||||
format!("{:.0}{}", converted_free_space.0, converted_free_space.1).into()
|
||||
} else {
|
||||
"N/A".into()
|
||||
}
|
||||
|
@ -58,19 +56,16 @@ impl DiskWidgetData {
|
|||
if let (Some(free_bytes), Some(summed_total_bytes)) =
|
||||
(self.free_bytes, self.summed_total_bytes)
|
||||
{
|
||||
Some(free_bytes as f64 / summed_total_bytes as f64 * 100_f64)
|
||||
if summed_total_bytes > 0 {
|
||||
Some(free_bytes as f64 / summed_total_bytes as f64 * 100_f64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn free_percent_string(&self) -> Cow<'static, str> {
|
||||
match self.free_percent() {
|
||||
Some(val) => format!("{val:.1}%").into(),
|
||||
None => "N/A".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn used_percent(&self) -> Option<f64> {
|
||||
if let (Some(used_bytes), Some(summed_total_bytes)) =
|
||||
(self.used_bytes, self.summed_total_bytes)
|
||||
|
@ -84,16 +79,15 @@ impl DiskWidgetData {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn used_percent_string(&self) -> Cow<'static, str> {
|
||||
match self.used_percent() {
|
||||
Some(val) => format!("{val:.1}%").into(),
|
||||
None => "N/A".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DiskWidgetColumn {
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(
|
||||
feature = "generate_schema",
|
||||
derive(schemars::JsonSchema, strum::VariantArray)
|
||||
)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub enum DiskColumn {
|
||||
Disk,
|
||||
Mount,
|
||||
Used,
|
||||
|
@ -105,45 +99,91 @@ pub enum DiskWidgetColumn {
|
|||
IoWrite,
|
||||
}
|
||||
|
||||
impl ColumnHeader for DiskWidgetColumn {
|
||||
impl<'de> Deserialize<'de> for DiskColumn {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = String::deserialize(deserializer)?.to_lowercase();
|
||||
match value.as_str() {
|
||||
"disk" => Ok(DiskColumn::Disk),
|
||||
"mount" => Ok(DiskColumn::Mount),
|
||||
"used" => Ok(DiskColumn::Used),
|
||||
"free" => Ok(DiskColumn::Free),
|
||||
"total" => Ok(DiskColumn::Total),
|
||||
"usedpercent" | "used%" => Ok(DiskColumn::UsedPercent),
|
||||
"freepercent" | "free%" => Ok(DiskColumn::FreePercent),
|
||||
"r/s" => Ok(DiskColumn::IoRead),
|
||||
"w/s" => Ok(DiskColumn::IoWrite),
|
||||
_ => Err(serde::de::Error::custom(
|
||||
"doesn't match any disk column name",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskColumn {
|
||||
/// An ugly hack to generate the JSON schema.
|
||||
#[cfg(feature = "generate_schema")]
|
||||
pub fn get_schema_names(&self) -> &[&'static str] {
|
||||
match self {
|
||||
DiskColumn::Disk => &["Disk"],
|
||||
DiskColumn::Mount => &["Mount"],
|
||||
DiskColumn::Used => &["Used"],
|
||||
DiskColumn::Free => &["Free"],
|
||||
DiskColumn::Total => &["Total"],
|
||||
DiskColumn::UsedPercent => &["Used%"],
|
||||
DiskColumn::FreePercent => &["Free%"],
|
||||
DiskColumn::IoRead => &["R/s", "Read", "Rps"],
|
||||
DiskColumn::IoWrite => &["W/s", "Write", "Wps"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColumnHeader for DiskColumn {
|
||||
fn text(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
DiskWidgetColumn::Disk => "Disk(d)",
|
||||
DiskWidgetColumn::Mount => "Mount(m)",
|
||||
DiskWidgetColumn::Used => "Used(u)",
|
||||
DiskWidgetColumn::Free => "Free(n)",
|
||||
DiskWidgetColumn::UsedPercent => "Used%(p)",
|
||||
DiskWidgetColumn::FreePercent => "Free%",
|
||||
DiskWidgetColumn::Total => "Total(t)",
|
||||
DiskWidgetColumn::IoRead => "R/s(r)",
|
||||
DiskWidgetColumn::IoWrite => "W/s(w)",
|
||||
DiskColumn::Disk => "Disk(d)",
|
||||
DiskColumn::Mount => "Mount(m)",
|
||||
DiskColumn::Used => "Used(u)",
|
||||
DiskColumn::Free => "Free(n)",
|
||||
DiskColumn::Total => "Total(t)",
|
||||
DiskColumn::UsedPercent => "Used%(p)",
|
||||
DiskColumn::FreePercent => "Free%",
|
||||
DiskColumn::IoRead => "R/s(r)",
|
||||
DiskColumn::IoWrite => "W/s(w)",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl DataToCell<DiskWidgetColumn> for DiskWidgetData {
|
||||
impl DataToCell<DiskColumn> for DiskWidgetData {
|
||||
fn to_cell(
|
||||
&self, column: &DiskWidgetColumn, _calculated_width: NonZeroU16,
|
||||
&self, column: &DiskColumn, _calculated_width: NonZeroU16,
|
||||
) -> Option<Cow<'static, str>> {
|
||||
fn percent_string(value: Option<f64>) -> Cow<'static, str> {
|
||||
match value {
|
||||
Some(val) => format!("{val:.1}%").into(),
|
||||
None => "N/A".into(),
|
||||
}
|
||||
}
|
||||
|
||||
let text = match column {
|
||||
DiskWidgetColumn::Disk => self.name.clone(),
|
||||
DiskWidgetColumn::Mount => self.mount_point.clone(),
|
||||
DiskWidgetColumn::Used => self.used_space(),
|
||||
DiskWidgetColumn::Free => self.free_space(),
|
||||
DiskWidgetColumn::UsedPercent => self.used_percent_string(),
|
||||
DiskWidgetColumn::FreePercent => self.free_percent_string(),
|
||||
DiskWidgetColumn::Total => self.total_space(),
|
||||
DiskWidgetColumn::IoRead => self.io_read.clone(),
|
||||
DiskWidgetColumn::IoWrite => self.io_write.clone(),
|
||||
DiskColumn::Disk => self.name.clone(),
|
||||
DiskColumn::Mount => self.mount_point.clone(),
|
||||
DiskColumn::Used => self.used_space(),
|
||||
DiskColumn::Free => self.free_space(),
|
||||
DiskColumn::UsedPercent => percent_string(self.used_percent()),
|
||||
DiskColumn::FreePercent => percent_string(self.free_percent()),
|
||||
DiskColumn::Total => self.total_space(),
|
||||
DiskColumn::IoRead => self.io_read.clone(),
|
||||
DiskColumn::IoWrite => self.io_write.clone(),
|
||||
};
|
||||
|
||||
Some(text)
|
||||
}
|
||||
|
||||
fn column_widths<C: DataTableColumn<DiskWidgetColumn>>(
|
||||
data: &[Self], _columns: &[C],
|
||||
) -> Vec<u16>
|
||||
fn column_widths<C: DataTableColumn<DiskColumn>>(data: &[Self], _columns: &[C]) -> Vec<u16>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
|
@ -159,63 +199,85 @@ impl DataToCell<DiskWidgetColumn> for DiskWidgetData {
|
|||
}
|
||||
|
||||
pub struct DiskTableWidget {
|
||||
pub table: SortDataTable<DiskWidgetData, DiskWidgetColumn>,
|
||||
pub table: SortDataTable<DiskWidgetData, DiskColumn>,
|
||||
pub force_update_data: bool,
|
||||
}
|
||||
|
||||
impl SortsRow for DiskWidgetColumn {
|
||||
impl SortsRow for DiskColumn {
|
||||
type DataType = DiskWidgetData;
|
||||
|
||||
fn sort_data(&self, data: &mut [Self::DataType], descending: bool) {
|
||||
match self {
|
||||
DiskWidgetColumn::Disk => {
|
||||
DiskColumn::Disk => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(&a.name, &b.name));
|
||||
}
|
||||
DiskWidgetColumn::Mount => {
|
||||
DiskColumn::Mount => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(&a.mount_point, &b.mount_point));
|
||||
}
|
||||
DiskWidgetColumn::Used => {
|
||||
DiskColumn::Used => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(&a.used_bytes, &b.used_bytes));
|
||||
}
|
||||
DiskWidgetColumn::UsedPercent => {
|
||||
DiskColumn::UsedPercent => {
|
||||
data.sort_by(|a, b| {
|
||||
sort_partial_fn(descending)(&a.used_percent(), &b.used_percent())
|
||||
});
|
||||
}
|
||||
DiskWidgetColumn::Free => {
|
||||
DiskColumn::Free => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(&a.free_bytes, &b.free_bytes));
|
||||
}
|
||||
DiskWidgetColumn::FreePercent => {
|
||||
DiskColumn::FreePercent => {
|
||||
data.sort_by(|a, b| {
|
||||
sort_partial_fn(descending)(&a.free_percent(), &b.free_percent())
|
||||
});
|
||||
}
|
||||
DiskWidgetColumn::Total => {
|
||||
DiskColumn::Total => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(&a.total_bytes, &b.total_bytes));
|
||||
}
|
||||
DiskWidgetColumn::IoRead => {
|
||||
DiskColumn::IoRead => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(&a.io_read, &b.io_read));
|
||||
}
|
||||
DiskWidgetColumn::IoWrite => {
|
||||
DiskColumn::IoWrite => {
|
||||
data.sort_by(|a, b| sort_partial_fn(descending)(&a.io_write, &b.io_write));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskTableWidget {
|
||||
pub fn new(config: &AppConfigFields, palette: &ColourPalette) -> Self {
|
||||
let columns = [
|
||||
SortColumn::soft(DiskWidgetColumn::Disk, Some(0.2)),
|
||||
SortColumn::soft(DiskWidgetColumn::Mount, Some(0.2)),
|
||||
SortColumn::hard(DiskWidgetColumn::Used, 8).default_descending(),
|
||||
SortColumn::hard(DiskWidgetColumn::Free, 8).default_descending(),
|
||||
SortColumn::hard(DiskWidgetColumn::Total, 9).default_descending(),
|
||||
SortColumn::hard(DiskWidgetColumn::UsedPercent, 9).default_descending(),
|
||||
SortColumn::hard(DiskWidgetColumn::IoRead, 10).default_descending(),
|
||||
SortColumn::hard(DiskWidgetColumn::IoWrite, 11).default_descending(),
|
||||
];
|
||||
const fn create_column(column_type: &DiskColumn) -> SortColumn<DiskColumn> {
|
||||
match column_type {
|
||||
DiskColumn::Disk => SortColumn::soft(DiskColumn::Disk, Some(0.2)),
|
||||
DiskColumn::Mount => SortColumn::soft(DiskColumn::Mount, Some(0.2)),
|
||||
DiskColumn::Used => SortColumn::hard(DiskColumn::Used, 8).default_descending(),
|
||||
DiskColumn::Free => SortColumn::hard(DiskColumn::Free, 8).default_descending(),
|
||||
DiskColumn::Total => SortColumn::hard(DiskColumn::Total, 9).default_descending(),
|
||||
DiskColumn::UsedPercent => {
|
||||
SortColumn::hard(DiskColumn::UsedPercent, 9).default_descending()
|
||||
}
|
||||
DiskColumn::FreePercent => {
|
||||
SortColumn::hard(DiskColumn::FreePercent, 9).default_descending()
|
||||
}
|
||||
DiskColumn::IoRead => SortColumn::hard(DiskColumn::IoRead, 10).default_descending(),
|
||||
DiskColumn::IoWrite => SortColumn::hard(DiskColumn::IoWrite, 11).default_descending(),
|
||||
}
|
||||
}
|
||||
|
||||
const fn default_disk_columns() -> [SortColumn<DiskColumn>; 8] {
|
||||
[
|
||||
create_column(&DiskColumn::Disk),
|
||||
create_column(&DiskColumn::Mount),
|
||||
create_column(&DiskColumn::Used),
|
||||
create_column(&DiskColumn::Free),
|
||||
create_column(&DiskColumn::Total),
|
||||
create_column(&DiskColumn::UsedPercent),
|
||||
create_column(&DiskColumn::IoRead),
|
||||
create_column(&DiskColumn::IoWrite),
|
||||
]
|
||||
}
|
||||
|
||||
impl DiskTableWidget {
|
||||
pub fn new(
|
||||
config: &AppConfigFields, palette: &ColourPalette, columns: Option<&[DiskColumn]>,
|
||||
) -> Self {
|
||||
let props = SortDataTableProps {
|
||||
inner: DataTableProps {
|
||||
title: Some(" Disks ".into()),
|
||||
|
@ -231,9 +293,18 @@ impl DiskTableWidget {
|
|||
|
||||
let styling = DataTableStyling::from_palette(palette);
|
||||
|
||||
Self {
|
||||
table: SortDataTable::new_sortable(columns, props, styling),
|
||||
force_update_data: false,
|
||||
match columns {
|
||||
Some(columns) => {
|
||||
let columns = columns.iter().map(create_column).collect::<Vec<_>>();
|
||||
Self {
|
||||
table: SortDataTable::new_sortable(columns, props, styling),
|
||||
force_update_data: false,
|
||||
}
|
||||
}
|
||||
None => Self {
|
||||
table: SortDataTable::new_sortable(default_disk_columns(), props, styling),
|
||||
force_update_data: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,28 +94,14 @@ impl ColumnHeader for ProcColumn {
|
|||
|
||||
fn header(&self) -> Cow<'static, str> {
|
||||
match self {
|
||||
ProcColumn::CpuPercent => "CPU%(c)",
|
||||
ProcColumn::MemValue => "Mem(m)",
|
||||
ProcColumn::MemPercent => "Mem%(m)",
|
||||
ProcColumn::Pid => "PID(p)",
|
||||
ProcColumn::Count => "Count",
|
||||
ProcColumn::Name => "Name(n)",
|
||||
ProcColumn::Command => "Command(n)",
|
||||
ProcColumn::ReadPerSecond => "R/s",
|
||||
ProcColumn::WritePerSecond => "W/s",
|
||||
ProcColumn::TotalRead => "T.Read",
|
||||
ProcColumn::TotalWrite => "T.Write",
|
||||
ProcColumn::State => "State",
|
||||
ProcColumn::User => "User",
|
||||
ProcColumn::Time => "Time",
|
||||
#[cfg(feature = "gpu")]
|
||||
ProcColumn::GpuMemValue => "GMem",
|
||||
#[cfg(feature = "gpu")]
|
||||
ProcColumn::GpuMemPercent => "GMem%",
|
||||
#[cfg(feature = "gpu")]
|
||||
ProcColumn::GpuUtilPercent => "GPU%",
|
||||
ProcColumn::CpuPercent => "CPU%(c)".into(),
|
||||
ProcColumn::MemValue => "Mem(m)".into(),
|
||||
ProcColumn::MemPercent => "Mem%(m)".into(),
|
||||
ProcColumn::Pid => "PID(p)".into(),
|
||||
ProcColumn::Name => "Name(n)".into(),
|
||||
ProcColumn::Command => "Command(n)".into(),
|
||||
_ => self.text(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +200,9 @@ impl<'de> Deserialize<'de> for ProcColumn {
|
|||
"gmem" | "gmem%" => Ok(ProcColumn::GpuMemPercent),
|
||||
#[cfg(feature = "gpu")]
|
||||
"gpu%" => Ok(ProcColumn::GpuUtilPercent),
|
||||
_ => Err(serde::de::Error::custom("doesn't match any column type")),
|
||||
_ => Err(serde::de::Error::custom(
|
||||
"doesn't match any process column name",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,3 +131,11 @@ fn test_invalid_process_column() {
|
|||
.failure()
|
||||
.stderr(predicate::str::contains("doesn't match"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_disk_column() {
|
||||
btm_command(&["-C", "./tests/invalid_configs/invalid_disk_column.toml"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("doesn't match"));
|
||||
}
|
||||
|
|
2
tests/invalid_configs/invalid_disk_column.toml
Normal file
2
tests/invalid_configs/invalid_disk_column.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[disk]
|
||||
columns = ["disk", "fake"]
|
|
@ -1,4 +1,5 @@
|
|||
#:schema none
|
||||
# Adding this to avoid a warning from some schema linters
|
||||
|
||||
[styles]
|
||||
theme = "gruvbox"
|
||||
|
|
Loading…
Reference in a new issue