Skip to content

Commit b583de4

Browse files
authored
Merge pull request #19 from tomdeblauwe/feature/journalctl
Feature/journalctl
2 parents d7f649f + e79fcfd commit b583de4

13 files changed

Lines changed: 560 additions & 231 deletions

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ g-systemctl is a terminal user interface (TUI) for managing system services on L
1717
- Start/stop services with a single keypress
1818
- Cross-platform support (Linux via systemctl, macOS via launchctl)
1919
- Keyboard-driven navigation
20+
- Mouse support (scroll to navigate, click to select)
21+
- Initial filter via command line argument (`-f` / `--filter`)
22+
- View service logs via journalctl (requires tmux)
2023

2124
## Prerequisites
2225

2326
- CMake 3.14+
2427
- C++17 compatible compiler (GCC 7+, Clang 5+)
2528
- Linux with systemd or macOS
29+
- tmux (optional, required for viewing service logs)
2630

2731
## Installation
2832

@@ -63,16 +67,27 @@ sudo g-systemctl
6367
| `Down` / `j` | Move selection down |
6468
| `Enter` | Toggle selected service (start/stop) |
6569
| `r` | Refresh service list |
70+
| `l` | Open logs for selected service (tmux only) |
6671
| `?` | Show/hide help |
6772
| `q` / `Esc` | Quit |
6873
| Type | Filter services by name |
6974
| `Backspace` | Delete filter character |
7075

76+
### Mouse Support
77+
78+
| Action | Effect |
79+
|--------|--------|
80+
| Scroll up/down | Navigate the service list |
81+
| Left click | Select a service |
82+
7183
## Command Line Options
7284

7385
```bash
74-
g-systemctl --help # Show help
75-
g-systemctl --version # Show version
86+
g-systemctl --help # Show help
87+
g-systemctl --version # Show version
88+
g-systemctl --system # Show system services instead of user services
89+
g-systemctl -f <text> # Start with an initial filter
90+
g-systemctl --filter <text> # Start with an initial filter
7691
```
7792

7893
## Contributing
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <memory>
5+
#include <utility>
6+
7+
namespace gsystemctl {
8+
9+
class CommandExecutor; // forward
10+
11+
class LoggingManager {
12+
public:
13+
virtual ~LoggingManager() = default;
14+
15+
/// Open a log viewer for the given unit. When running inside tmux this will
16+
/// split the current pane and invoke "journalctl -u <unit> -f". Returns a
17+
/// pair of (success,message) where the message contains human readable error
18+
/// information in case of failure.
19+
virtual std::pair<bool, std::string> open_logs(const std::string& unit) = 0;
20+
21+
static std::unique_ptr<LoggingManager> create(
22+
std::shared_ptr<CommandExecutor> executor);
23+
};
24+
25+
} // namespace gsystemctl
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#pragma once
2+
3+
#include "g-systemctl/core/logging_manager.hpp"
4+
#include <memory>
5+
6+
namespace gsystemctl {
7+
8+
class LinuxLoggingManager : public LoggingManager {
9+
public:
10+
explicit LinuxLoggingManager(std::shared_ptr<CommandExecutor> executor);
11+
12+
std::pair<bool, std::string> open_logs(const std::string& unit) override;
13+
14+
private:
15+
std::shared_ptr<CommandExecutor> executor_;
16+
};
17+
18+
} // namespace gsystemctl
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#pragma once
2+
3+
#include "g-systemctl/core/logging_manager.hpp"
4+
#include <memory>
5+
6+
namespace gsystemctl {
7+
8+
class MacOSLoggingManager : public LoggingManager {
9+
public:
10+
explicit MacOSLoggingManager(std::shared_ptr<CommandExecutor> executor);
11+
12+
std::pair<bool, std::string> open_logs(const std::string& unit) override;
13+
14+
private:
15+
std::shared_ptr<CommandExecutor> executor_;
16+
};
17+
18+
} // namespace gsystemctl

include/g-systemctl/ui/app.hpp

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,49 @@
11
#pragma once
22

33
#include "g-systemctl/core/service_manager.hpp"
4+
#include "g-systemctl/core/logging_manager.hpp"
45
#include "g-systemctl/core/service.hpp"
56
#include <ftxui/component/component.hpp>
67
#include <ftxui/component/screen_interactive.hpp>
8+
#include <ftxui/screen/box.hpp>
79
#include <string>
810
#include <vector>
911

10-
namespace gsystemctl::ui {
12+
namespace gsystemctl::ui
13+
{
1114

12-
class App {
13-
public:
14-
App(bool system_mode = false);
15-
int run();
15+
class App
16+
{
17+
public:
18+
App(bool system_mode = false, const std::string &initial_filter = "");
19+
int run();
1620

17-
private:
18-
ftxui::ScreenInteractive screen_;
19-
std::shared_ptr<ServiceManager> service_manager_;
20-
bool system_mode_;
21+
private:
22+
ftxui::ScreenInteractive screen_;
23+
std::shared_ptr<ServiceManager> service_manager_;
24+
std::unique_ptr<LoggingManager> logging_manager_;
25+
bool system_mode_;
2126

22-
std::vector<ServiceUnit> services_;
23-
std::vector<ServiceUnit> filtered_services_;
24-
std::string filter_text_;
25-
int selected_index_ = 0;
26-
std::string status_message_;
27-
std::string error_message_;
28-
bool show_help_ = false;
27+
std::vector<ServiceUnit> services_;
28+
std::vector<ServiceUnit> filtered_services_;
29+
std::string filter_text_;
30+
int selected_index_ = 0;
31+
std::string status_message_;
32+
std::string error_message_;
33+
bool show_help_ = false;
34+
std::vector<ftxui::Box> item_boxes_;
35+
std::vector<ftxui::Box> toggle_button_boxes_;
36+
std::vector<ftxui::Box> log_button_boxes_;
2937

30-
void refresh_services();
31-
void apply_filter();
32-
void toggle_selected_service();
33-
ftxui::Component create_main_component();
34-
ftxui::Element render();
35-
ftxui::Element render_service_list();
36-
ftxui::Element render_status_bar();
37-
ftxui::Element render_help();
38-
};
38+
void refresh_services();
39+
void apply_filter();
40+
void toggle_selected_service();
41+
void open_logs_for_selected_service();
42+
ftxui::Component create_main_component();
43+
ftxui::Element render();
44+
ftxui::Element render_service_list();
45+
ftxui::Element render_status_bar();
46+
ftxui::Element render_help();
47+
};
3948

4049
} // namespace gsystemctl::ui

include/g-systemctl/ui/styles.hpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
#pragma once
22

33
#include <ftxui/dom/elements.hpp>
4+
#include <ftxui/screen/box.hpp>
45

5-
namespace gsystemctl::ui {
6+
namespace gsystemctl::ui
7+
{
68

7-
struct Colors {
8-
static ftxui::Color running_fg();
9-
static ftxui::Color running_bg();
10-
static ftxui::Color stopped_fg();
11-
static ftxui::Color stopped_bg();
12-
static ftxui::Color selected_bg();
13-
static ftxui::Color error_fg();
14-
};
9+
struct Colors
10+
{
11+
static ftxui::Color running_fg();
12+
static ftxui::Color running_bg();
13+
static ftxui::Color stopped_fg();
14+
static ftxui::Color stopped_bg();
15+
static ftxui::Color selected_bg();
16+
static ftxui::Color error_fg();
17+
};
1518

16-
ftxui::Element service_card(const std::string& name, const std::string& status,
17-
const std::string& description, bool is_running, bool selected);
19+
ftxui::Element service_card(const std::string &name, const std::string &status,
20+
const std::string &description, bool is_running, bool selected,
21+
ftxui::Box &toggle_box, ftxui::Box &log_box);
1822

1923
} // namespace gsystemctl::ui

src/core/logging_manager.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include "g-systemctl/core/logging_manager.hpp"
2+
#include "g-systemctl/platform/platform.hpp"
3+
#include "g-systemctl/platform/linux_logging_manager.hpp"
4+
#include "g-systemctl/platform/macos_logging_manager.hpp"
5+
#include <stdexcept>
6+
7+
namespace gsystemctl {
8+
9+
std::unique_ptr<LoggingManager> LoggingManager::create(
10+
std::shared_ptr<CommandExecutor> executor) {
11+
Platform platform = detect_platform();
12+
13+
switch (platform) {
14+
case Platform::Linux:
15+
return std::make_unique<LinuxLoggingManager>(executor);
16+
case Platform::MacOS:
17+
return std::make_unique<MacOSLoggingManager>(executor);
18+
default:
19+
throw std::runtime_error("Unsupported platform");
20+
}
21+
}
22+
23+
} // namespace gsystemctl

src/main.cpp

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,58 @@
33
#include <iostream>
44
#include <cstring>
55

6-
void print_help() {
6+
void print_help()
7+
{
78
std::cout << "g-systemctl - Terminal UI for system service management\n\n";
89
std::cout << "Usage: g-systemctl [options]\n\n";
910
std::cout << "Options:\n";
10-
std::cout << " -h, --help Show this help message\n";
11-
std::cout << " -v, --version Show version information\n";
12-
std::cout << " --system Show system services instead of user services\n\n";
11+
std::cout << " -h, --help Show this help message\n";
12+
std::cout << " -v, --version Show version information\n";
13+
std::cout << " --system Show system services instead of user services\n";
14+
std::cout << " -f, --filter <text> Set the initial filter text\n\n";
1315
std::cout << "Note: Run with sudo for full functionality (start/stop services)\n";
1416
}
1517

16-
void print_version() {
18+
void print_version()
19+
{
1720
std::cout << "g-systemctl version 1.0.0\n";
1821
std::cout << "Platform: " << gsystemctl::platform_name(gsystemctl::detect_platform()) << "\n";
1922
}
2023

21-
int main(int argc, char* argv[]) {
24+
int main(int argc, char *argv[])
25+
{
2226
bool system_mode = false;
27+
std::string initial_filter;
2328

24-
for (int i = 1; i < argc; ++i) {
25-
if (std::strcmp(argv[i], "-h") == 0 || std::strcmp(argv[i], "--help") == 0) {
29+
for (int i = 1; i < argc; ++i)
30+
{
31+
if (std::strcmp(argv[i], "-h") == 0 || std::strcmp(argv[i], "--help") == 0)
32+
{
2633
print_help();
2734
return 0;
2835
}
29-
if (std::strcmp(argv[i], "-v") == 0 || std::strcmp(argv[i], "--version") == 0) {
36+
if (std::strcmp(argv[i], "-v") == 0 || std::strcmp(argv[i], "--version") == 0)
37+
{
3038
print_version();
3139
return 0;
3240
}
33-
if (std::strcmp(argv[i], "--system") == 0) {
41+
if (std::strcmp(argv[i], "--system") == 0)
42+
{
3443
system_mode = true;
3544
}
45+
if ((std::strcmp(argv[i], "-f") == 0 || std::strcmp(argv[i], "--filter") == 0) && i + 1 < argc)
46+
{
47+
initial_filter = argv[++i];
48+
}
3649
}
3750

38-
try {
39-
gsystemctl::ui::App app(system_mode);
51+
try
52+
{
53+
gsystemctl::ui::App app(system_mode, initial_filter);
4054
return app.run();
41-
} catch (const std::exception& e) {
55+
}
56+
catch (const std::exception &e)
57+
{
4258
std::cerr << "Error: " << e.what() << "\n";
4359
return 1;
4460
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include "g-systemctl/platform/linux_logging_manager.hpp"
2+
#include "g-systemctl/core/command_executor.hpp"
3+
#include <cstdlib>
4+
5+
namespace gsystemctl {
6+
7+
LinuxLoggingManager::LinuxLoggingManager(std::shared_ptr<CommandExecutor> executor)
8+
: executor_(std::move(executor)) {}
9+
10+
std::pair<bool, std::string> LinuxLoggingManager::open_logs(
11+
const std::string& unit) {
12+
// Ensure we're inside tmux before trying to split the pane.
13+
const char* tmux_env = std::getenv("TMUX");
14+
if (!tmux_env) {
15+
return {false, "Not running inside a tmux session"};
16+
}
17+
18+
// Build the journalctl command. Quote the unit name to avoid shell issues.
19+
std::string command = "tmux split-window -v 'journalctl -u " + unit + " -f'";
20+
if (!executor_) {
21+
// If we don't have an executor, just run the command directly via system().
22+
int rc = std::system(command.c_str());
23+
if (rc != 0) {
24+
return {false, "failed to execute tmux command"};
25+
}
26+
return {true, ""};
27+
}
28+
29+
auto result = executor_->execute(command);
30+
if (result.exit_code != 0) {
31+
std::string msg = "tmux command failed";
32+
if (!result.stderr_output.empty()) {
33+
msg = result.stderr_output;
34+
}
35+
return {false, msg};
36+
}
37+
return {true, ""};
38+
}
39+
40+
} // namespace gsystemctl
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include "g-systemctl/platform/macos_logging_manager.hpp"
2+
3+
namespace gsystemctl {
4+
5+
MacOSLoggingManager::MacOSLoggingManager(std::shared_ptr<CommandExecutor> executor)
6+
: executor_(std::move(executor)) {}
7+
8+
std::pair<bool, std::string> MacOSLoggingManager::open_logs(
9+
const std::string& /*unit*/) {
10+
return {false, "Logging manager is not supported on macOS"};
11+
}
12+
13+
} // namespace gsystemctl

0 commit comments

Comments
 (0)