Skip to content

Commit 335eb83

Browse files
authored
0.2.0 tag (#15)
* v0.2.0 Signed-off-by: Daniel Guns <danbguns@gmail.com> * filling in rest of config Signed-off-by: Daniel Guns <danbguns@gmail.com> --------- Signed-off-by: Daniel Guns <danbguns@gmail.com>
1 parent 64abde6 commit 335eb83

9 files changed

Lines changed: 113 additions & 56 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "flux9s"
33

4-
version = "0.2.0"
4+
version = "0.2.1"
55
edition = "2021"
66
authors = ["Dan Guns <danbguns@gmail.com>"]
77
description = "A K9s-inspired terminal UI for monitoring Flux GitOps resources"

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ By default, `flux9s` watches the `flux-system` namespace. Use `:ns all` to view
105105

106106
- `:ns <namespace>` - Switch namespace
107107
- `:ns all` - View all namespaces
108+
- `:skin {skin-name}` - set skin (Must be in your systems flux9s/skins dir)
108109
- `:q` or `:q!` - Quit
109110
- `:help` - Show help
110111

@@ -115,6 +116,14 @@ By default, `flux9s` watches the `flux-system` namespace. Use `:ns all` to view
115116
- `R` - Reconcile resource
116117
- `d` - Delete resource (with confirmation)
117118

119+
### Terminal Commands
120+
121+
- `flux9s config --help` - Show the config options
122+
- `flux9s config set {KEY} {VALUE}` - set a yaml option with the cli.
123+
- `flux9s config set ui.skin {skinname}` - set a skin that is in your systems flux9s/skins.
124+
125+
> **Note:** Not all K9s skins are compatible with flux9s. flux9s skins follow a similar format but may require adjustments to work properly.
126+
118127
## Acknowledgments
119128

120129
This project is inspired by and built with the following excellent tools:

src/config/schema.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ impl Default for Config {
124124
impl Default for UiConfig {
125125
fn default() -> Self {
126126
Self {
127-
enable_mouse: default_true(),
127+
enable_mouse: default_false(),
128128
headless: default_false(),
129129
no_icons: default_false(),
130130
skin: default_skin(),

src/main.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,15 @@ async fn main() -> Result<()> {
183183
tracing::debug!("Initializing Kubernetes client");
184184
let client = kube::create_client().await?;
185185
let context = kube::get_context().await?;
186-
let default_namespace = kube::get_default_namespace().await;
186+
// Use config.default_namespace if set, otherwise fall back to environment/default
187+
let default_namespace = if config.default_namespace.is_empty()
188+
|| config.default_namespace == "all"
189+
|| config.default_namespace == "-A"
190+
{
191+
kube::get_default_namespace().await
192+
} else {
193+
Some(config.default_namespace.clone())
194+
};
187195

188196
if args.debug {
189197
tracing::info!("Connected to Kubernetes cluster: {}", context);
@@ -218,7 +226,7 @@ async fn main() -> Result<()> {
218226
default_namespace,
219227
watcher,
220228
client,
221-
read_only,
229+
config,
222230
theme,
223231
)
224232
.await?;

src/tui/app.rs

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub struct App {
4545
confirmation_pending: Option<(String, String, String, char)>, // (resource_type, namespace, name, operation_key)
4646
status_message: Option<(String, bool)>, // (message, is_error)
4747
theme: Theme,
48-
read_only: bool, // Readonly mode - prevents modification operations
48+
config: crate::config::Config, // Application configuration
4949
}
5050

5151
#[derive(Clone, Copy, PartialEq)]
@@ -61,7 +61,7 @@ impl App {
6161
state: ResourceState,
6262
context: String,
6363
namespace: Option<String>,
64-
read_only: bool,
64+
config: crate::config::Config,
6565
theme: Theme,
6666
) -> Self {
6767
Self {
@@ -85,16 +85,20 @@ impl App {
8585
yaml_fetch_pending: None,
8686
yaml_fetched: None,
8787
yaml_fetch_rx: None,
88-
show_splash: true,
89-
splash_start_time: Some(std::time::Instant::now()),
88+
show_splash: !config.ui.splashless, // Skip splash if splashless is true
89+
splash_start_time: if config.ui.splashless {
90+
None
91+
} else {
92+
Some(std::time::Instant::now())
93+
},
9094
operation_registry: OperationRegistry::new(),
9195
pending_operation: None,
9296
operation_result_rx: None,
9397
last_operation_key: None,
9498
confirmation_pending: None,
9599
status_message: None,
96100
theme,
97-
read_only,
101+
config,
98102
}
99103
}
100104

@@ -275,7 +279,7 @@ impl App {
275279
if let Some(operation) = self.operation_registry.get_by_keybinding(op_key) {
276280
if operation.is_valid_for(&resource.resource_type) {
277281
// Check readonly mode first
278-
if self.read_only {
282+
if self.config.read_only {
279283
self.status_message = Some((
280284
"Readonly mode is enabled. Operations are disabled."
281285
.to_string(),
@@ -429,7 +433,7 @@ impl App {
429433
match key.code {
430434
crossterm::event::KeyCode::Char('y') | crossterm::event::KeyCode::Char('Y') => {
431435
// Check readonly mode before confirming
432-
if self.read_only {
436+
if self.config.read_only {
433437
self.confirmation_pending = None;
434438
self.status_message = Some((
435439
"Readonly mode is enabled. Operations are disabled.".to_string(),
@@ -465,7 +469,7 @@ impl App {
465469
op_key: char,
466470
) {
467471
// Check readonly mode - prevent modification operations
468-
if self.read_only {
472+
if self.config.read_only {
469473
if self.operation_registry.get_by_keybinding(op_key).is_some() {
470474
// All operations are modifications, so block them all in readonly mode
471475
self.status_message = Some((
@@ -658,8 +662,8 @@ impl App {
658662

659663
// Handle readonly toggle command
660664
if cmd_lower == "readonly" || cmd_lower == "read-only" {
661-
self.read_only = !self.read_only;
662-
let status = if self.read_only {
665+
self.config.read_only = !self.config.read_only;
666+
let status = if self.config.read_only {
663667
"enabled"
664668
} else {
665669
"disabled"
@@ -871,25 +875,33 @@ impl App {
871875
let chunks = Layout::default()
872876
.direction(Direction::Vertical)
873877
.constraints([
874-
Constraint::Length(header_height as u16), // Dynamic header height
875-
Constraint::Min(0), // Main content (flexible)
878+
if self.config.ui.headless {
879+
Constraint::Length(0) // No header in headless mode
880+
} else {
881+
Constraint::Length(header_height as u16) // Dynamic header height
882+
},
883+
Constraint::Min(0), // Main content (flexible)
876884
Constraint::Length(footer_constraint as u16), // Footer (content + borders)
877885
])
878886
.split(f.size());
879887

880888
let resources = self.get_filtered_resources();
881-
render_header(
882-
f,
883-
chunks[0],
884-
&self.state,
885-
&self.context,
886-
&self.namespace,
887-
&self.filter,
888-
&self.selected_resource_type,
889-
resources.len(),
890-
self.read_only,
891-
&self.theme,
892-
);
889+
// Only render header if not in headless mode
890+
if !self.config.ui.headless {
891+
render_header(
892+
f,
893+
chunks[0],
894+
&self.state,
895+
&self.context,
896+
&self.namespace,
897+
&self.filter,
898+
&self.selected_resource_type,
899+
resources.len(),
900+
self.config.read_only,
901+
&self.theme,
902+
self.config.ui.no_icons,
903+
);
904+
}
893905
self.render_main(f, chunks[1]);
894906
render_footer(
895907
f,
@@ -937,6 +949,7 @@ impl App {
937949
&self.selected_resource_type,
938950
&self.resource_objects,
939951
&self.theme,
952+
self.config.ui.no_icons,
940953
);
941954
}
942955
View::ResourceDetail => {

src/tui/mod.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,24 @@ pub async fn run_tui(
7878
namespace: Option<String>,
7979
watcher: crate::watcher::ResourceWatcher,
8080
client: kube::Client,
81-
read_only: bool,
81+
config: crate::config::Config,
8282
theme: crate::tui::Theme,
8383
) -> Result<()> {
8484
tracing::debug!("Initializing TUI");
8585

8686
// Setup terminal
8787
enable_raw_mode()?;
8888
let mut stdout = io::stdout();
89-
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
89+
execute!(stdout, EnterAlternateScreen)?;
90+
// Conditionally enable mouse capture based on config
91+
if config.ui.enable_mouse {
92+
execute!(stdout, EnableMouseCapture)?;
93+
}
9094
let backend = CrosstermBackend::new(stdout);
9195
let mut terminal = Terminal::new(backend)?;
9296

93-
// Create app state with readonly mode and theme
94-
let mut app = App::new(state, context, namespace.clone(), read_only, theme);
97+
// Create app state with config and theme
98+
let mut app = App::new(state, context, namespace.clone(), config.clone(), theme);
9599
app.set_watcher(watcher);
96100
app.set_kube_client(client);
97101

@@ -265,11 +269,11 @@ pub async fn run_tui(
265269

266270
// Restore terminal
267271
disable_raw_mode()?;
268-
execute!(
269-
terminal.backend_mut(),
270-
LeaveAlternateScreen,
271-
DisableMouseCapture
272-
)?;
272+
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
273+
// Only disable mouse if it was enabled
274+
if config.ui.enable_mouse {
275+
execute!(terminal.backend_mut(), DisableMouseCapture)?;
276+
}
273277
terminal.show_cursor()?;
274278

275279
Ok(())

src/tui/views/header.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub fn render_header(
2222
filtered_count: usize, // Count of resources after filtering
2323
read_only: bool, // Readonly mode status
2424
theme: &Theme,
25+
no_icons: bool,
2526
) {
2627
// Split header into left (info) and right (ASCII art)
2728
let header_chunks = Layout::default()
@@ -111,8 +112,13 @@ pub fn render_header(
111112
// Add readonly indicator if enabled
112113
if read_only {
113114
context_line_spans.push(Span::raw(" "));
115+
let readonly_text = if no_icons {
116+
"[READONLY]"
117+
} else {
118+
"🔒 READONLY"
119+
};
114120
context_line_spans.push(Span::styled(
115-
"🔒 READONLY",
121+
readonly_text,
116122
Style::default()
117123
.fg(theme.status_error)
118124
.add_modifier(Modifier::BOLD),
@@ -139,9 +145,10 @@ pub fn render_header(
139145
""
140146
};
141147

148+
let filter_icon = if no_icons { "[FILTER]" } else { "⚠ Filter: " };
142149
header_lines.push(Line::from(vec![
143150
Span::styled(
144-
"⚠ Filter: ",
151+
filter_icon,
145152
Style::default()
146153
.fg(theme.header_filter)
147154
.add_modifier(Modifier::BOLD),

src/tui/views/resource_list.rs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub fn render_resource_list(
2424
selected_resource_type: &Option<String>,
2525
resource_objects: &Arc<RwLock<HashMap<String, serde_json::Value>>>,
2626
theme: &Theme,
27+
no_icons: bool,
2728
) {
2829
let visible_height = (area.height as usize).saturating_sub(2);
2930

@@ -109,7 +110,7 @@ pub fn render_resource_list(
109110

110111
// Status indicator
111112
let (status_indicator, status_color) =
112-
get_status_indicator(r.ready, r.suspended, theme);
113+
get_status_indicator(r.ready, r.suspended, theme, no_icons);
113114

114115
let message = r.message.as_deref().unwrap_or("-");
115116
let message_display = if message.len() > 40 {
@@ -131,14 +132,15 @@ pub fn render_resource_list(
131132
})
132133
.collect();
133134

135+
let status_width = if no_icons { 6 } else { 3 }; // "PAUSED" vs "●"
134136
let constraints: Vec<Constraint> = vec![
135-
Constraint::Length(3), // STATUS
136-
Constraint::Min(15), // NAMESPACE
137-
Constraint::Min(30), // NAME
138-
Constraint::Min(20), // TYPE
139-
Constraint::Length(10), // SUSPENDED
140-
Constraint::Length(6), // READY
141-
Constraint::Percentage(40), // MESSAGE
137+
Constraint::Length(status_width), // STATUS
138+
Constraint::Min(15), // NAMESPACE
139+
Constraint::Min(30), // NAME
140+
Constraint::Min(20), // TYPE
141+
Constraint::Length(10), // SUSPENDED
142+
Constraint::Length(6), // READY
143+
Constraint::Percentage(40), // MESSAGE
142144
];
143145

144146
(rows, header, constraints)
@@ -168,7 +170,7 @@ pub fn render_resource_list(
168170
};
169171

170172
let (status_indicator, status_color) =
171-
get_status_indicator(r.ready, r.suspended, theme);
173+
get_status_indicator(r.ready, r.suspended, theme, no_icons);
172174

173175
// Get resource-specific fields from stored object
174176
let key = crate::watcher::resource_key(&r.namespace, &r.name, &r.resource_type);
@@ -233,7 +235,7 @@ pub fn render_resource_list(
233235
let constraints: Vec<Constraint> = column_names
234236
.iter()
235237
.map(|col| match *col {
236-
"STATUS" => Constraint::Length(3),
238+
"STATUS" => Constraint::Length(if no_icons { 6 } else { 3 }),
237239
"NAMESPACE" => Constraint::Min(15),
238240
"NAME" => Constraint::Min(30),
239241
"TYPE" => Constraint::Min(20),
@@ -268,12 +270,25 @@ fn get_status_indicator(
268270
ready: Option<bool>,
269271
suspended: Option<bool>,
270272
theme: &Theme,
273+
no_icons: bool,
271274
) -> (&'static str, Color) {
272-
match (ready, suspended) {
273-
(Some(true), Some(false)) => ("●", theme.status_ready),
274-
(Some(true), Some(true)) => ("⏸", theme.status_suspended),
275-
(Some(false), _) => ("✗", theme.status_error),
276-
(None, Some(true)) => ("⏸", theme.status_suspended),
277-
_ => ("○", theme.status_unknown),
275+
if no_icons {
276+
// Use text alternatives when icons are disabled
277+
match (ready, suspended) {
278+
(Some(true), Some(false)) => ("OK", theme.status_ready),
279+
(Some(true), Some(true)) => ("PAUSED", theme.status_suspended),
280+
(Some(false), _) => ("ERR", theme.status_error),
281+
(None, Some(true)) => ("PAUSED", theme.status_suspended),
282+
_ => ("?", theme.status_unknown),
283+
}
284+
} else {
285+
// Use Unicode icons when enabled
286+
match (ready, suspended) {
287+
(Some(true), Some(false)) => ("●", theme.status_ready),
288+
(Some(true), Some(true)) => ("⏸", theme.status_suspended),
289+
(Some(false), _) => ("✗", theme.status_error),
290+
(None, Some(true)) => ("⏸", theme.status_suspended),
291+
_ => ("○", theme.status_unknown),
292+
}
278293
}
279294
}

0 commit comments

Comments
 (0)