Skip to content

Commit

Permalink
Merge pull request #28 from rgwood/edit-unit-file
Browse files Browse the repository at this point in the history
Add functionality for editing unit file
  • Loading branch information
rgwood authored Jan 14, 2025
2 parents 1e5fcb9 + ee36e82 commit 38f6942
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 47 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "systemctl-tui"
description = "A simple TUI for interacting with systemd services and their logs"
homepage = "https://github.com/rgwood/systemctl-tui"
repository = "https://github.com/rgwood/systemctl-tui"
version = "0.3.8"
version = "0.3.9"
edition = "2021"
authors = ["Reilly Wood"]
license = "MIT"
Expand Down
5 changes: 3 additions & 2 deletions src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ pub enum Action {
RefreshServices,
SetServices(Vec<UnitWithStatus>),
EnterMode(Mode),
EnterError { err: String },
EnterError(String),
CancelTask,
ToggleHelp,
SetUnitFilePath { unit: UnitId, path: String },
SetUnitFilePath { unit: UnitId, path: Result<String, String> },
CopyUnitFilePath,
SetLogs { unit: UnitId, logs: Vec<String> },
AppendLogLine { unit: UnitId, line: String },
Expand All @@ -33,5 +33,6 @@ pub enum Action {
ScrollDown(u16),
ScrollToTop,
ScrollToBottom,
EditUnitFile { path: String },
Noop,
}
24 changes: 22 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::sync::Arc;
use std::{process::Command, sync::Arc};

use anyhow::{Context, Result};
use tokio::sync::{mpsc, Mutex};
use tracing::debug;

use crate::{
action::Action,
components::{home::Home, Component},
components::{
home::{Home, Mode},
Component,
},
event::EventHandler,
systemd::{get_all_services, Scope},
terminal::TerminalHandler,
Expand Down Expand Up @@ -90,6 +93,23 @@ impl App {
Action::Suspend => self.should_suspend = true,
Action::Resume => self.should_suspend = false,
Action::Resize(_, _) => terminal.render().await,
Action::EditUnitFile { path } => {
event.stop();
let mut tui = terminal.tui.lock().await;
tui.exit()?;
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
match Command::new(editor).arg(path).status() {
Ok(_) => {
tui.enter()?;
tui.clear()?;
event = EventHandler::new(self.home.clone(), action_tx.clone());
action_tx.send(Action::EnterMode(Mode::ServiceList))?;
},
Err(e) => {
action_tx.send(Action::EnterError(format!("Failed to open editor: {}", e)))?;
},
}
},
_ => {
if let Some(_action) = self.home.lock().await.dispatch(action) {
action_tx.send(_action)?
Expand Down
68 changes: 37 additions & 31 deletions src/components/home.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ impl Home {
error_string.push_str("Try running this tool with sudo.");
}

tx.send(Action::EnterError { err: error_string }).unwrap();
tx.send(Action::EnterError(error_string)).unwrap();
},
}
spinner_task.abort();
Expand Down Expand Up @@ -366,11 +366,13 @@ impl Component for Home {
// get the unit file path
match systemd::get_unit_file_location(&unit) {
Ok(path) => {
let _ = tx.send(Action::SetUnitFilePath { unit: unit.clone(), path });
let _ = tx.send(Action::SetUnitFilePath { unit: unit.clone(), path: Ok(path) });
let _ = tx.send(Action::Render);
},
Err(e) => {
let _ = tx.send(Action::SetUnitFilePath { unit: unit.clone(), path: "(could not be determined)".into() });
// Fix this!!! Set the path to an error enum variant instead of a string
let _ =
tx.send(Action::SetUnitFilePath { unit: unit.clone(), path: Err("could not be determined".into()) });
let _ = tx.send(Action::Render);
error!("Error getting unit file path for {}: {}", unit.name, e);
},
Expand Down Expand Up @@ -557,31 +559,34 @@ impl Component for Home {
},
Action::EnterMode(mode) => {
if mode == Mode::ActionMenu {
let selected = match self.filtered_units.selected() {
Some(s) => s.id(),
None => return None,
};

// TODO: use current status to determine which actions are available?
let menu_items = vec![
MenuItem::new("Start", Action::StartService(selected.clone())),
MenuItem::new("Stop", Action::StopService(selected.clone())),
MenuItem::new("Restart", Action::RestartService(selected.clone())),
MenuItem::new("Copy unit file path to clipboard", Action::CopyUnitFilePath),
// TODO add these
// MenuItem::new("Reload", Action::ReloadService(selected.clone())),
// MenuItem::new("Enable", Action::EnableService(selected.clone())),
// MenuItem::new("Disable", Action::DisableService(selected.clone())),
];

self.menu_items = StatefulList::with_items(menu_items);
self.menu_items.state.select(Some(0));
if let Some(selected) = self.filtered_units.selected() {
let mut menu_items = vec![
MenuItem::new("Start", Action::StartService(selected.id())),
MenuItem::new("Stop", Action::StopService(selected.id())),
MenuItem::new("Restart", Action::RestartService(selected.id())),
// TODO add these
// MenuItem::new("Reload", Action::ReloadService(selected.clone())),
// MenuItem::new("Enable", Action::EnableService(selected.clone())),
// MenuItem::new("Disable", Action::DisableService(selected.clone())),
];

if let Some(Ok(file_path)) = &selected.file_path {
menu_items.push(MenuItem::new("Copy unit file path to clipboard", Action::CopyUnitFilePath));
menu_items.push(MenuItem::new("Edit unit file", Action::EditUnitFile { path: file_path.clone() }));
}

self.menu_items = StatefulList::with_items(menu_items);
self.menu_items.state.select(Some(0));
} else {
return None;
}
}

self.mode = mode;
return Some(Action::Render);
},
Action::EnterError { err } => {
Action::EnterError(err) => {
tracing::error!(err);
self.error_message = err;
return Some(Action::EnterMode(Mode::Error));
},
Expand All @@ -596,13 +601,13 @@ impl Component for Home {
},
Action::CopyUnitFilePath => {
if let Some(selected) = self.filtered_units.selected() {
if let Some(file_path) = &selected.file_path {
if let Some(Ok(file_path)) = &selected.file_path {
match clipboard_anywhere::set_clipboard(file_path) {
Ok(_) => return Some(Action::EnterMode(Mode::ServiceList)),
Err(e) => return Some(Action::EnterError { err: format!("Error copying to clipboard: {}", e) }),
Err(e) => return Some(Action::EnterError(format!("Error copying to clipboard: {}", e))),
}
} else {
return Some(Action::EnterError { err: "No unit file path available".into() });
return Some(Action::EnterError("No unit file path available".into()));
}
}
},
Expand Down Expand Up @@ -794,17 +799,18 @@ impl Component for Home {
UnitScope::User => "User",
};

let mut lines = vec![
let lines = vec![
colored_line(&i.description, Color::Reset),
colored_line(scope, Color::Reset),
colored_line(&i.load_state, load_color),
line_color_string(active_state_value, active_color),
match &i.file_path {
Some(Ok(file_path)) => Line::from(file_path.as_str()),
Some(Err(e)) => colored_line(e, Color::Red),
None => Line::from(""),
},
];

if let Some(file_path) = &i.file_path {
lines.push(Line::from(file_path.as_str()));
}

lines
} else {
vec![]
Expand Down
9 changes: 4 additions & 5 deletions src/systemd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ use zbus::{proxy, zvariant, Connection};

#[derive(Debug, Clone)]
pub struct UnitWithStatus {
pub name: String, // The primary unit name as string
pub scope: UnitScope, // System or user?
pub description: String, // The human readable description string
pub file_path: Option<String>, // The unit file path - populated later on demand
pub name: String, // The primary unit name as string
pub scope: UnitScope, // System or user?
pub description: String, // The human readable description string
pub file_path: Option<Result<String, String>>, // The unit file path - populated later on demand

pub load_state: String, // The load state (i.e. whether the unit file has been loaded successfully)

Expand All @@ -27,7 +27,6 @@ pub struct UnitWithStatus {
/// The other state all units have is called the "enablement state". It describes how the unit might be automatically started in the future. A unit is enabled if it has been added to the requirements list of any other unit though symlinks in the filesystem. The set of symlinks to be created when enabling a unit is described by the unit's [Install] section. A unit is disabled if no symlinks are present. Again there's a variety of other values other than these two (e.g. not all units even have [Install] sections).
/// Only populated when needed b/c this is much slower to get
pub enablement_state: Option<String>,

// We don't use any of these right now, might as well skip'em so there's less data to clone
// pub followed: String, // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string.
// pub path: String, // The unit object path
Expand Down
12 changes: 8 additions & 4 deletions src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ use tokio::{

use crate::components::{home::Home, Component};

// pub type Frame<'a> = ratatui::Frame<'a, Backend<std::io::Stderr>>;

// A struct that mostly exists to be a catch-all for terminal operations that should be synchronized
pub struct Tui {
pub terminal: ratatui::Terminal<Backend<std::io::Stderr>>,
}
Expand Down Expand Up @@ -47,7 +46,7 @@ impl Tui {
}

pub fn suspend(&self) -> Result<()> {
exit()?;
self.exit()?;
#[cfg(not(windows))]
signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?;
Ok(())
Expand All @@ -57,8 +56,13 @@ impl Tui {
self.enter()?;
Ok(())
}

pub fn exit(&self) -> Result<()> {
exit()
}
}

// This one's public because we want to expose it to the panic handler
pub fn exit() -> Result<()> {
crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, DisableMouseCapture, cursor::Show)?;
crossterm::terminal::disable_raw_mode()?;
Expand Down Expand Up @@ -96,7 +100,7 @@ pub struct TerminalHandler {
pub task: JoinHandle<()>,
tx: mpsc::UnboundedSender<Message>,
home: Arc<Mutex<Home>>,
tui: Arc<Mutex<Tui>>,
pub tui: Arc<Mutex<Tui>>,
}

impl TerminalHandler {
Expand Down

0 comments on commit 38f6942

Please sign in to comment.