feat(tui): support saving changed values (#13)

This commit is contained in:
Orhun Parmaksız 2022-09-17 20:36:03 +02:00
parent 612a11ea0a
commit 1fda373602
No known key found for this signature in database
GPG key ID: F83424824B3E4B90
10 changed files with 133 additions and 13 deletions

View file

@ -88,6 +88,7 @@ Although **systeroid** does not need the parameter section to be specified expli
- [Toggling the kernel section](#toggling-the-kernel-section)
- [Searching](#searching)
- [Setting values](#setting-values-1)
- [Saving values](#saving-values)
- [Running commands](#running-commands)
- [Copying to clipboard](#copying-to-clipboard)
- [Changing the colors](#changing-the-colors)
@ -409,6 +410,8 @@ systeroid-tui [options]
-t, --tick-rate <ms>
set the tick rate of the terminal [default: 250]
-D, --docs <path> set the path of the kernel documentation
--save-path <path>
set the path for saving the changed parameters
-s, --section <section>
set the section to filter
-q, --query <query> set the query to search
@ -433,8 +436,9 @@ systeroid-tui [options]
| <kbd>left/right</kbd>, <kbd>h/l</kbd> | scroll documentation |
| <kbd>tab</kbd>, <kbd>`</kbd> | next/previous section |
| <kbd>:</kbd> | command |
| <kbd>/</kbd>, <kbd>s</kbd> | search |
| <kbd>/</kbd> | search |
| <kbd>enter</kbd> | select / set parameter value |
| <kbd>s</kbd> | save parameter value |
| <kbd>c</kbd> | copy to clipboard |
| <kbd>r</kbd>, <kbd>f5</kbd> | refresh |
| <kbd>esc</kbd> | cancel / exit |
@ -500,6 +504,20 @@ Press <kbd>enter</kbd> to select a parameter and set its value via command promp
You can press <kbd>r</kbd> to refresh the values in the parameter list.
#### Saving values
Press <kbd>s</kbd> to set a parameter value via command prompt and save its value to a file specified via `--save-path`.
![Save value](assets/systeroid-tui-save-value.gif)
Default save path is `/etc/sysctl.conf` and the values can be loaded via `systeroid --load`.
```sh
$ systeroid-tui --save-path /etc/sysctl.d/custom.conf
$ systeroid --system
```
#### Running commands
Press <kbd>:</kbd> to open the command prompt for running a command. Available commands are:
@ -510,6 +528,7 @@ Press <kbd>:</kbd> to open the command prompt for running a command. Available c
| `:search` | Enable search |
| `:select` | Select the current parameter in the list |
| `:set <name> <value>` | Set parameter value |
| `:save <name> <value>` | Save parameter value to file |
| `:scroll [area] [direction] <amount>` | Scroll the list or text<br>- areas: `list`, `docs`, `section`<br>- directions: `up`, `down`, `top`, `bottom`, `right`, `left` |
| `:copy` | Copy to clipboard |
| `:refresh` | Refresh values |

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

View file

@ -57,6 +57,8 @@ section_unknown = "white"
tick_rate = 250
; disable showing the parameter documentation
no_docs = true
; path for saving the changed kernel parameters
save_path = "/etc/sysctl.conf"
[tui.colors]
; available colors are defined in https://docs.rs/tui/latest/tui/style/enum.Color.html

View file

@ -20,6 +20,9 @@ Use this option to set the tick rate of the terminal. [default: 250]
\fB\-D\fR, \fB\-\-docs\fR <path>
Use this option to set a custom path for the kernel documentation.
.TP
\fB\-\-save\-path\fR <path>
Use this option to set the path for saving the changed parameters.
.TP
\fB\-s\fR, \fB\-\-section\fR <section>
Use this option to set the section to filter.
.HP
@ -56,12 +59,14 @@ systeroid-tui \-\-bg-color ffff99 \-\-fg-color 003366
.br
systeroid-tui \-D /usr/share/doc/linux
.br
systeroid-tui \-\-save-path 99-local.conf
.br
systeroid-tui -n
.SH KEY BINDINGS
.TS
tab(@);
l l.
lw(47.2n) lw(22.8n).
T{
Key
T}@T{
@ -99,7 +104,7 @@ T}@T{
command
T}
T{
/, s
/
T}@T{
search
T}
@ -109,6 +114,11 @@ T}@T{
select / set parameter value
T}
T{
s
T}@T{
save parameter value
T}
T{
c
T}@T{
copy to clipboard

View file

@ -86,6 +86,8 @@ pub struct TuiConfig {
pub tick_rate: u64,
/// Do not parse/show Linux kernel documentation.
pub no_docs: bool,
/// Path for saving the changed kernel parameters.
pub save_path: Option<PathBuf>,
/// Color configuration.
pub color: TuiColorConfig,
}
@ -157,6 +159,9 @@ impl Config {
if let Some(tick_rate) = section.get("tick_rate").and_then(|v| v.parse().ok()) {
self.tui.tick_rate = tick_rate;
}
if let Some(save_path) = section.get("save_path") {
self.tui.save_path = Some(PathBuf::from(save_path));
}
parse_ini_flag!(self, tui, section, no_docs);
}
if let Some(section) = ini.section(Some("tui.colors")) {
@ -201,6 +206,7 @@ impl Default for Config {
tui: TuiConfig {
tick_rate: 250,
no_docs: false,
save_path: None,
color: TuiColorConfig {
fg_color: String::from("white"),
bg_color: String::from("black"),

View file

@ -4,13 +4,17 @@ use crate::error::Result;
use crate::parsers::{parse_kernel_docs, KERNEL_DOCS_PATH};
use crate::sysctl::parameter::Parameter;
use crate::sysctl::section::Section;
use crate::sysctl::DEPRECATED_PARAMS;
use crate::sysctl::{DISABLE_CACHE_ENV, PARAMETERS_CACHE_LABEL, PROC_PATH};
use crate::sysctl::{
DEFAULT_PRELOAD, DEPRECATED_PARAMS, DISABLE_CACHE_ENV, PARAMETERS_CACHE_LABEL, PROC_PATH,
};
use parseit::globwalk;
use parseit::reader;
use rayon::prelude::*;
use std::convert::TryFrom;
use std::env;
use std::path::Path;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::result::Result as StdResult;
use sysctl::{CtlFlags, CtlIter, Sysctl as SysctlImpl};
@ -172,6 +176,38 @@ impl Sysctl {
});
Ok(())
}
/// Saves the parameter values to the given file.
pub fn save_to_file(
&self,
param_name: String,
new_value: String,
save_path: &Option<PathBuf>,
) -> Result<PathBuf> {
let save_path = save_path
.clone()
.unwrap_or_else(|| PathBuf::from(DEFAULT_PRELOAD));
let data = format!("{} = {}", param_name, new_value);
if save_path.exists() {
let contents = reader::read_to_string(&save_path)?;
let mut lines = contents.split('\n').collect::<Vec<&str>>();
if let Some(line) = lines.iter_mut().find(|v| v.starts_with(&param_name)) {
*line = &data;
} else {
lines.push(&data);
}
let mut file = OpenOptions::new()
.write(true)
.create(false)
.truncate(false)
.open(&save_path)?;
file.write_all(lines.join("\n").as_bytes())?;
} else {
let mut file = File::create(&save_path)?;
file.write_all(data.as_bytes())?;
}
Ok(save_path)
}
}
#[cfg(test)]

View file

@ -67,6 +67,10 @@ pub const KEY_BINDINGS: &[&KeyBinding] = &[
key: "enter",
action: "select / set value",
},
&KeyBinding {
key: "s",
action: "save value to file",
},
&KeyBinding {
key: "c",
action: "copy to clipboard",
@ -266,7 +270,14 @@ impl<'a> App<'a> {
self.input = Some(format!("set {} {}", parameter.name, parameter.value));
}
}
Command::Set(param_name, new_value) => {
Command::Save => {
if let Some(parameter) = self.parameter_list.selected() {
self.search_mode = false;
self.input_time = None;
self.input = Some(format!("save {} {}", parameter.name, parameter.value));
}
}
Command::Set(param_name, new_value, save_to_file) => {
if let Some(parameter) = self
.parameter_list
.items
@ -282,6 +293,21 @@ impl<'a> App<'a> {
self.input_time = Some(Instant::now());
}
}
if save_to_file {
match self.sysctl.save_to_file(
param_name,
new_value,
&self.sysctl.config.tui.save_path,
) {
Ok(path) => {
self.input = Some(format!("Saved to file: {:?}", path));
}
Err(e) => {
self.input = Some(format!("Failed to save: {}", e));
}
}
self.input_time = Some(Instant::now());
}
} else {
self.input = Some(String::from("Unknown parameter"));
self.input_time = Some(Instant::now());

View file

@ -24,6 +24,8 @@ pub struct Args {
pub tick_rate: u64,
/// Path of the Linux kernel documentation.
pub kernel_docs: Option<PathBuf>,
/// Path for the changed parameters.
pub save_path: Option<PathBuf>,
/// Sysctl section to filter.
pub section: Option<Section>,
/// Query to search on startup.
@ -54,6 +56,12 @@ impl Args {
"set the path of the kernel documentation",
"<path>",
);
opts.optopt(
"",
"save-path",
"set the path for saving the changed parameters",
"<path>",
);
opts.optopt("s", "section", "set the section to filter", "<section>");
opts.optopt("q", "query", "set the query to search", "<query>");
opts.optopt(
@ -114,6 +122,7 @@ impl Args {
.opt_str("D")
.or_else(|| env::var(KERNEL_DOCS_ENV).ok())
.map(PathBuf::from),
save_path: matches.opt_str("save-path").map(PathBuf::from),
section: matches.opt_str("s").map(Section::from),
search_query: matches.opt_str("q"),
fg_color: matches

View file

@ -9,8 +9,10 @@ pub enum Command {
Help,
/// Perform an action based on the selected entry.
Select,
/// Save the value of a parameter to a file.
Save,
/// Set the value of a parameter.
Set(String, String),
Set(String, String, bool),
/// Scroll the widget.
Scroll(ScrollArea, Direction, u8),
/// Move cursor..
@ -46,12 +48,16 @@ impl FromStr for Command {
"refresh" => Ok(Command::Refresh),
"exit" | "quit" | "q" | "q!" => Ok(Command::Exit),
_ => {
if s.starts_with("set") {
let values: Vec<&str> =
s.trim_start_matches("set").split_whitespace().collect();
if s.starts_with("set") || s.starts_with("save") {
let values: Vec<&str> = s
.trim_start_matches("set")
.trim_start_matches("save")
.split_whitespace()
.collect();
Ok(Command::Set(
values.first().ok_or(())?.to_string(),
values[1..].join(" "),
s.starts_with("save"),
))
} else if s.starts_with("scroll") {
let mut values = s.trim_start_matches("scroll").split_whitespace();
@ -100,7 +106,8 @@ impl Command {
Key::Char('`') => Command::Scroll(ScrollArea::Section, Direction::Left, 1),
Key::Char('\t') => Command::Scroll(ScrollArea::Section, Direction::Right, 1),
Key::Char(':') => Command::UpdateInput(' '),
Key::Char('/') | Key::Char('s') => Command::Search,
Key::Char('s') => Command::Save,
Key::Char('/') => Command::Search,
Key::Char('\n') => Command::Select,
Key::Char('c') => Command::Copy,
Key::Char('r') | Key::F(5) => Command::Refresh,
@ -134,9 +141,13 @@ mod tests {
(Command::Refresh, "refresh"),
(Command::Exit, "quit"),
(
Command::Set(String::from("a"), String::from("b c")),
Command::Set(String::from("a"), String::from("b c"), false),
"set a b c",
),
(
Command::Set(String::from("a"), String::from("b c"), true),
"save a b c",
),
(
Command::Scroll(ScrollArea::List, Direction::Up, 1),
"scroll list up 1",

View file

@ -41,6 +41,7 @@ pub fn run<B: Backend>(args: Args, backend: B) -> Result<()> {
..Default::default()
};
config.tui.tick_rate = args.tick_rate;
config.tui.save_path = args.save_path;
config.tui.no_docs = args.no_docs;
config.tui.color.fg_color = args.fg_color;
config.tui.color.bg_color = args.bg_color;