Skip to content

Commit 12d2616

Browse files
committed
- Refactor of Project Structure
- added sudo prompt if changing something
1 parent afb195c commit 12d2616

File tree

11 files changed

+1339
-1412
lines changed

11 files changed

+1339
-1412
lines changed

Improvements.md

Lines changed: 0 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,12 @@ On OSX, the information reported will not be accurate. The tool relies on the `/
1010

1111
## Plan
1212

13-
- **Goals**
14-
- Reach feature parity with the Go TUI: users/groups tabs, paging, search, vim/arrow keys.
15-
- Keep startup fast and memory usage modest; support Linux and BSDs. macOS remains best‑effort.
16-
1713
- **Tech Stack**
1814
- UI: [`ratatui`](https://github.com/ratatui-org/ratatui) + [`crossterm`](https://github.com/crossterm-rs/crossterm)
1915
- System users/groups: [`users`](https://github.com/ogham/rust-users) (respects NSS); optional file parsing fallback
2016
- CLI/logging/error: `clap`, `tracing` + `tracing-subscriber`, `anyhow`/`thiserror`
2117
- Search: `fuzzy-matcher` (optional), simple substring by default
2218

23-
- **Proposed Crate Layout**
24-
- `src/main.rs`: entry, args, tracing init, runs app
25-
- `src/app/`: app state, actions, update loop, key handling
26-
- `src/ui/`: table, status bar, search box, theming
27-
- `src/sys/`: adapters for users/groups via `users` crate
28-
- `src/parsers/`: `passwd.rs`, `group.rs` (feature `file-parse`, e.g. with `nom`)
29-
- `src/search.rs`: filter and optional fuzzy matching
30-
- `src/theme.rs`: colors and styles
31-
32-
- **Keybindings (parity)**
33-
- Exit: `Ctrl+C` / `q` / `Esc`
34-
- Switch tab: `Tab`
35-
- Navigation: `↑/k`, `↓/j`, `←/h`, `→/l`
36-
- Search: `/` to enter, `Enter` to apply
37-
3819
- **Testing Strategy**
3920
- Parser tests using fixture files; property tests for edge cases
4021
- Integration tests driving the update loop with synthetic input events
@@ -125,75 +106,6 @@ On OSX, the information reported will not be accurate. The tool relies on the `/
125106
- Health checks for PAM, shadow permissions, `login.defs` anomalies
126107
- Detect conflicting state (e.g., orphaned homes, duplicate UIDs)
127108

128-
If you share your top 3 priorities (e.g., safety, batch ops, LDAP), I can help turn them into a concrete MVP scope.
129-
130-
## Refactor Structure
131-
```
132-
src/
133-
main.rs # thin entry: parse CLI, init logging, run app
134-
cli.rs # Clap args and env
135-
app/
136-
mod.rs # re-exports
137-
state.rs # AppState, ActiveTab, focus enums, settings
138-
msg.rs # Msg enum (events/commands)
139-
update.rs # TEA-style update() handling keys/actions
140-
services.rs # high-level operations orchestrating sys + state
141-
ui/
142-
mod.rs
143-
layout.rs # root layout (header, split panes, footer)
144-
header.rs # top header/hints
145-
status_bar.rs # bottom status line
146-
help.rs # optional help popup
147-
widgets/
148-
table.rs # generic table helpers
149-
modal.rs # generic modal helpers
150-
users/
151-
table.rs # users list
152-
details.rs # user details pane
153-
member_of.rs # groups the user belongs to
154-
modals.rs # users actions: modify, groups add/remove, shell, etc.
155-
groups/
156-
table.rs
157-
details.rs
158-
members.rs
159-
modals.rs # create/delete group, add/remove members
160-
theme.rs # colors/styles
161-
domain/
162-
mod.rs
163-
user.rs # core types independent of UI/sys
164-
group.rs
165-
filters.rs # predicates for system vs human, locked, etc.
166-
search/
167-
mod.rs
168-
substring.rs
169-
fuzzy.rs # behind feature flag `fuzzy`
170-
sys/
171-
mod.rs # SystemAdapter facade
172-
users.rs # list users, user attrs
173-
groups.rs # list groups, membership
174-
shells.rs # read /etc/shells
175-
commands.rs # wrappers: usermod, gpasswd, groupadd/del
176-
parsers/ # only with feature `file-parse`
177-
mod.rs
178-
passwd.rs
179-
group.rs
180-
keymap.rs # keybindings -> Msg mapping (rebindable later)
181-
errors.rs # shared error types (thiserror) if you move off anyhow
182-
config.rs # persistent settings (rows/page, theme)
183-
logging.rs # tracing subscriber setup (optional)
184-
185-
tests/
186-
integration/
187-
update_loop.rs # drive Msg/update and assert state
188-
sys_commands.rs # command wrappers with dry-run
189-
ui_snapshots/
190-
users_table.snap # snapshot tests for rendering
191-
fixtures/
192-
etc/
193-
passwd.sample
194-
group.sample
195-
```
196-
197109
## Things to consider
198110
### Security and Risk Concerns
199111
User management is inherently high-risk from a security perspective

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,19 @@ Alpha means:
8383
- Build: `cargo build --release`
8484
- Run: `cargo run --release`
8585

86-
86+
## Project Structure
87+
```
88+
src/
89+
main.rs # Entry point
90+
app/
91+
mod.rs # AppState, core types
92+
update.rs # Event handling, business logic
93+
ui/
94+
mod.rs # Main render function, layout
95+
users.rs # Users tab (table + details + modals)
96+
groups.rs # Groups tab (table + details + modals)
97+
components.rs # Shared UI helpers (status bar, etc.)
98+
sys/
99+
mod.rs # Current SystemAdapter
100+
search.rs # Search functionality
101+
```

src/app/mod.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
pub mod update;
2+
3+
use ratatui::style::Color;
4+
use ratatui::widgets::TableState;
5+
use std::time::Instant;
6+
7+
use crate::sys;
8+
9+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
10+
pub enum ActiveTab {
11+
Users,
12+
Groups,
13+
}
14+
15+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16+
pub enum UsersFocus {
17+
UsersList,
18+
MemberOf,
19+
}
20+
21+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22+
pub enum InputMode {
23+
Normal,
24+
SearchUsers,
25+
SearchGroups,
26+
Modal,
27+
}
28+
29+
#[derive(Clone, Copy, Debug)]
30+
pub struct Theme {
31+
pub text: Color,
32+
pub _muted: Color,
33+
pub title: Color,
34+
pub border: Color,
35+
pub header_bg: Color,
36+
pub header_fg: Color,
37+
pub status_bg: Color,
38+
pub status_fg: Color,
39+
pub highlight_fg: Color,
40+
pub highlight_bg: Color,
41+
}
42+
43+
impl Theme {
44+
pub fn dark() -> Self {
45+
Self {
46+
text: Color::Gray,
47+
_muted: Color::DarkGray,
48+
title: Color::Cyan,
49+
border: Color::Gray,
50+
header_bg: Color::Black,
51+
header_fg: Color::Cyan,
52+
status_bg: Color::DarkGray,
53+
status_fg: Color::Black,
54+
highlight_fg: Color::Yellow,
55+
highlight_bg: Color::Reset,
56+
}
57+
}
58+
}
59+
60+
#[derive(Clone, Debug)]
61+
pub enum ModalState {
62+
Actions { selected: usize },
63+
ModifyMenu { selected: usize },
64+
ModifyGroupsAdd { selected: usize, offset: usize },
65+
ModifyGroupsRemove { selected: usize, offset: usize },
66+
ModifyDetailsMenu { selected: usize },
67+
ModifyShell { selected: usize, offset: usize, shells: Vec<String> },
68+
ModifyTextInput { field: ModifyField, value: String },
69+
DeleteConfirm { selected: usize, allowed: bool },
70+
Info { message: String },
71+
SudoPrompt { next: PendingAction, password: String, error: Option<String> },
72+
GroupsActions { selected: usize, target_gid: Option<u32> },
73+
GroupAddInput { name: String },
74+
GroupDeleteConfirm { selected: usize },
75+
GroupModifyMenu { selected: usize, target_gid: Option<u32> },
76+
GroupModifyAddMembers { selected: usize, offset: usize, target_gid: Option<u32> },
77+
GroupModifyRemoveMembers { selected: usize, offset: usize, target_gid: Option<u32> },
78+
}
79+
80+
#[derive(Clone, Debug)]
81+
pub enum ModifyField { Username, Fullname }
82+
83+
#[derive(Clone, Debug)]
84+
pub enum PendingAction {
85+
AddUserToGroup { username: String, groupname: String },
86+
RemoveUserFromGroup { username: String, groupname: String },
87+
ChangeShell { username: String, new_shell: String },
88+
ChangeFullname { username: String, new_fullname: String },
89+
ChangeUsername { old_username: String, new_username: String },
90+
CreateGroup { groupname: String },
91+
DeleteGroup { groupname: String },
92+
}
93+
94+
pub struct AppState {
95+
pub started_at: Instant,
96+
pub users_all: Vec<sys::SystemUser>,
97+
pub users: Vec<sys::SystemUser>,
98+
pub groups_all: Vec<sys::SystemGroup>,
99+
pub groups: Vec<sys::SystemGroup>,
100+
pub active_tab: ActiveTab,
101+
pub selected_user_index: usize,
102+
pub selected_group_index: usize,
103+
pub rows_per_page: usize,
104+
pub _table_state: TableState,
105+
pub input_mode: InputMode,
106+
pub search_query: String,
107+
pub theme: Theme,
108+
pub modal: Option<ModalState>,
109+
pub users_focus: UsersFocus,
110+
pub sudo_password: Option<String>,
111+
}
112+
113+
impl AppState {
114+
pub fn new() -> Self {
115+
let adapter = crate::sys::SystemAdapter::new();
116+
let mut users_all = adapter.list_users().unwrap_or_default();
117+
users_all.sort_by_key(|u| u.uid);
118+
let groups_all = adapter.list_groups().unwrap_or_default();
119+
Self {
120+
started_at: Instant::now(),
121+
users: users_all.clone(),
122+
users_all,
123+
groups: groups_all.clone(),
124+
groups_all,
125+
active_tab: ActiveTab::Users,
126+
selected_user_index: 0,
127+
selected_group_index: 0,
128+
rows_per_page: 10,
129+
_table_state: TableState::default(),
130+
input_mode: InputMode::Normal,
131+
search_query: String::new(),
132+
theme: Theme::dark(),
133+
modal: None,
134+
users_focus: UsersFocus::UsersList,
135+
sudo_password: None,
136+
}
137+
}
138+
}
139+
140+
pub use update::run_app as run;

0 commit comments

Comments
 (0)