mirror of
https://github.com/orhun/systeroid
synced 2024-09-30 04:33:31 +00:00
feat(tui): support saving changed values (#13)
This commit is contained in:
parent
612a11ea0a
commit
1fda373602
21
README.md
21
README.md
|
@ -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 |
|
||||
|
|
BIN
assets/systeroid-tui-save-value.gif
Normal file
BIN
assets/systeroid-tui-save-value.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 457 KiB |
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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(¶m_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)]
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue