Skip to content

Commit 69764d8

Browse files
authored
refactor: deduplicate shared driver and TUI helpers (#1741)
1 parent 76d7453 commit 69764d8

10 files changed

Lines changed: 68 additions & 101 deletions

File tree

crates/openshell-core/src/driver_utils.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,38 @@ pub fn sandbox_log_level(sandbox: &DriverSandbox, default_level: &str) -> String
8989
.unwrap_or(default_level)
9090
.to_string()
9191
}
92+
93+
// ---------------------------------------------------------------------------
94+
// Supervisor image helpers (shared by Docker and Podman drivers)
95+
// ---------------------------------------------------------------------------
96+
97+
/// Return the tag portion of a supervisor image reference, or `None` if the
98+
/// reference uses a digest (`@sha256:...`).
99+
///
100+
/// Examples:
101+
/// - `"ghcr.io/org/image:1.2.3"` → `Some("1.2.3")`
102+
/// - `"ghcr.io/org/image:latest"` → `Some("latest")`
103+
/// - `"ghcr.io/org/image"` → `Some("latest")` (implied tag)
104+
/// - `"ghcr.io/org/image@sha256:abc"` → `None` (pinned by digest)
105+
/// - `"ghcr.io/org/image:"` → `None` (empty tag)
106+
pub fn supervisor_image_tag(image: &str) -> Option<&str> {
107+
if image.contains('@') {
108+
return None;
109+
}
110+
111+
let image_name = image.rsplit('/').next().unwrap_or(image);
112+
image_name
113+
.rsplit_once(':')
114+
.map_or(Some("latest"), |(_, tag)| {
115+
if tag.is_empty() { None } else { Some(tag) }
116+
})
117+
}
118+
119+
/// Return `true` if the supervisor image should be refreshed before each use.
120+
///
121+
/// Mutable tags (`dev`, `latest`) are always re-pulled so that the running
122+
/// container tracks the latest pushed version. Digest-pinned references and
123+
/// all other versioned tags are treated as immutable and pulled at most once.
124+
pub fn supervisor_image_should_refresh(image: &str) -> bool {
125+
matches!(supervisor_image_tag(image), Some("dev" | "latest"))
126+
}

crates/openshell-driver-docker/src/lib.rs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use openshell_core::config::{
2323
};
2424
use openshell_core::driver_utils::{
2525
LABEL_MANAGED_BY, LABEL_MANAGED_BY_VALUE, LABEL_SANDBOX_ID, LABEL_SANDBOX_NAME,
26-
LABEL_SANDBOX_NAMESPACE, SUPERVISOR_IMAGE_BINARY_PATH,
26+
LABEL_SANDBOX_NAMESPACE, SUPERVISOR_IMAGE_BINARY_PATH, supervisor_image_should_refresh,
2727
};
2828
use openshell_core::gpu::cdi_gpu_device_ids;
2929
use openshell_core::progress::{
@@ -2572,23 +2572,6 @@ async fn extract_supervisor_bin_from_image(docker: &Docker, image: &str) -> Core
25722572
Ok(cache_path)
25732573
}
25742574

2575-
fn supervisor_image_should_refresh(image: &str) -> bool {
2576-
matches!(supervisor_image_tag(image), Some("dev" | "latest"))
2577-
}
2578-
2579-
fn supervisor_image_tag(image: &str) -> Option<&str> {
2580-
if image.contains('@') {
2581-
return None;
2582-
}
2583-
2584-
let image_name = image.rsplit('/').next().unwrap_or(image);
2585-
image_name
2586-
.rsplit_once(':')
2587-
.map_or(Some("latest"), |(_, tag)| {
2588-
if tag.is_empty() { None } else { Some(tag) }
2589-
})
2590-
}
2591-
25922575
async fn pull_supervisor_image(docker: &Docker, image: &str) -> CoreResult<()> {
25932576
let mut stream = docker.create_image(
25942577
Some(CreateImageOptions {

crates/openshell-driver-podman/src/driver.rs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::watcher::{
1010
self, WatchStream, driver_sandbox_from_inspect, driver_sandbox_from_list_entry,
1111
};
1212
use openshell_core::ComputeDriverError;
13+
use openshell_core::driver_utils::supervisor_image_should_refresh;
1314
use openshell_core::proto::compute::v1::{DriverSandbox, GetCapabilitiesResponse};
1415
use std::path::PathBuf;
1516
use std::time::Duration;
@@ -592,23 +593,6 @@ fn supervisor_image_pull_policy(image: &str) -> &'static str {
592593
}
593594
}
594595

595-
fn supervisor_image_should_refresh(image: &str) -> bool {
596-
matches!(supervisor_image_tag(image), Some("dev" | "latest"))
597-
}
598-
599-
fn supervisor_image_tag(image: &str) -> Option<&str> {
600-
if image.contains('@') {
601-
return None;
602-
}
603-
604-
let image_name = image.rsplit('/').next().unwrap_or(image);
605-
image_name
606-
.rsplit_once(':')
607-
.map_or(Some("latest"), |(_, tag)| {
608-
if tag.is_empty() { None } else { Some(tag) }
609-
})
610-
}
611-
612596
/// Check whether the current user has subuid/subgid ranges configured.
613597
///
614598
/// Rootless Podman requires entries in `/etc/subuid` and `/etc/subgid` for

crates/openshell-server/src/ssh_sessions.rs

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use openshell_core::ObjectId;
77
use openshell_core::proto::SshSession;
8+
use openshell_core::time::now_ms;
89
use prost::Message;
910
use std::sync::Arc;
1011
use std::time::Duration;
@@ -33,7 +34,7 @@ pub fn spawn_session_reaper(store: Arc<Store>, interval: Duration) {
3334
}
3435

3536
async fn reap_expired_sessions(store: &Store) -> Result<(), String> {
36-
let now_ms = unix_epoch_millis();
37+
let now_ms = now_ms();
3738

3839
let records = store
3940
.list(SshSession::object_type(), 1000, 0)
@@ -68,16 +69,6 @@ async fn reap_expired_sessions(store: &Store) -> Result<(), String> {
6869
Ok(())
6970
}
7071

71-
fn unix_epoch_millis() -> i64 {
72-
i64::try_from(
73-
std::time::SystemTime::now()
74-
.duration_since(std::time::UNIX_EPOCH)
75-
.unwrap_or_default()
76-
.as_millis(),
77-
)
78-
.unwrap_or(i64::MAX)
79-
}
80-
8172
#[cfg(test)]
8273
mod tests {
8374
use super::*;
@@ -103,10 +94,6 @@ mod tests {
10394
}
10495
}
10596

106-
fn now_ms() -> i64 {
107-
unix_epoch_millis()
108-
}
109-
11097
#[tokio::test]
11198
async fn reaper_deletes_expired_sessions() {
11299
let store = test_store().await;

crates/openshell-tui/src/ui/dashboard.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
use ratatui::Frame;
55
use ratatui::layout::{Constraint, Direction, Layout, Rect};
66
use ratatui::text::{Line, Span};
7-
use ratatui::widgets::{Block, Borders, Cell, Padding, Paragraph, Row, Table};
7+
use ratatui::widgets::{Block, Borders, Cell, Padding, Row, Table};
88

9+
use super::draw_empty_message;
910
use crate::app::{App, Focus, MiddlePaneTab};
1011

1112
pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect) {
@@ -118,13 +119,6 @@ fn draw_gateway_list(frame: &mut Frame<'_>, app: &App, area: Rect) {
118119
frame.render_widget(table, area);
119120

120121
if app.gateways.is_empty() {
121-
let inner = Rect {
122-
x: area.x + 2,
123-
y: area.y + 2,
124-
width: area.width.saturating_sub(4),
125-
height: area.height.saturating_sub(3),
126-
};
127-
let msg = Paragraph::new(Span::styled(" No gateways found.", t.muted));
128-
frame.render_widget(msg, inner);
122+
draw_empty_message(frame, area, " No gateways found.", t.muted);
129123
}
130124
}

crates/openshell-tui/src/ui/global_settings.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use ratatui::layout::{Constraint, Rect};
66
use ratatui::text::{Line, Span};
77
use ratatui::widgets::{Block, Borders, Cell, Clear, Padding, Paragraph, Row, Table};
88

9+
use super::draw_empty_message;
10+
911
use crate::app::{App, MiddlePaneTab};
1012

1113
pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect, focused: bool) {
@@ -72,14 +74,7 @@ pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect, focused: bool) {
7274
frame.render_widget(table, area);
7375

7476
if app.global_settings.is_empty() {
75-
let inner = Rect {
76-
x: area.x + 2,
77-
y: area.y + 2,
78-
width: area.width.saturating_sub(4),
79-
height: area.height.saturating_sub(3),
80-
};
81-
let msg = Paragraph::new(Span::styled(" No settings available.", t.muted));
82-
frame.render_widget(msg, inner);
77+
draw_empty_message(frame, area, " No settings available.", t.muted);
8378
}
8479

8580
// Draw edit overlay if active.

crates/openshell-tui/src/ui/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod splash;
1616

1717
use ratatui::Frame;
1818
use ratatui::layout::{Constraint, Direction, Layout, Rect};
19+
use ratatui::style::Style;
1920
use ratatui::text::{Line, Span};
2021
use ratatui::widgets::{Block, Borders, Paragraph};
2122

@@ -474,6 +475,21 @@ fn draw_command_bar(frame: &mut Frame<'_>, app: &App, area: Rect) {
474475
frame.render_widget(bar, area);
475476
}
476477

478+
/// Render an empty-state message inside a bordered panel.
479+
///
480+
/// Calculates an inset [`Rect`] that clears the panel border and renders
481+
/// `text` with the given `style`. Use this whenever a table or list has no
482+
/// rows to display.
483+
pub fn draw_empty_message(frame: &mut Frame<'_>, area: Rect, text: &str, style: Style) {
484+
let inner = Rect {
485+
x: area.x + 2,
486+
y: area.y + 2,
487+
width: area.width.saturating_sub(4),
488+
height: area.height.saturating_sub(3),
489+
};
490+
frame.render_widget(Paragraph::new(Span::styled(text, style)), inner);
491+
}
492+
477493
/// Center a popup rectangle within `area` using absolute width and height (in
478494
/// terminal columns/rows).
479495
pub fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {

crates/openshell-tui/src/ui/providers.rs

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use ratatui::Frame;
55
use ratatui::layout::{Constraint, Rect};
66
use ratatui::text::{Line, Span};
7-
use ratatui::widgets::{Block, Borders, Cell, Padding, Paragraph, Row, Table};
7+
use ratatui::widgets::{Block, Borders, Cell, Padding, Row, Table};
88

99
use crate::app::App;
1010

@@ -83,14 +83,7 @@ pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect, focused: bool) {
8383
frame.render_widget(table, area);
8484

8585
if app.provider_count == 0 {
86-
let inner = Rect {
87-
x: area.x + 2,
88-
y: area.y + 2,
89-
width: area.width.saturating_sub(4),
90-
height: area.height.saturating_sub(3),
91-
};
92-
let msg = Paragraph::new(Span::styled(" No providers. Press [c] to create.", t.muted));
93-
frame.render_widget(msg, inner);
86+
super::draw_empty_message(frame, area, " No providers. Press [c] to create.", t.muted);
9487
}
9588
}
9689

@@ -172,13 +165,6 @@ fn draw_v2(frame: &mut Frame<'_>, app: &App, area: Rect, focused: bool) {
172165
frame.render_widget(table, area);
173166

174167
if app.provider_count == 0 {
175-
let inner = Rect {
176-
x: area.x + 2,
177-
y: area.y + 2,
178-
width: area.width.saturating_sub(4),
179-
height: area.height.saturating_sub(3),
180-
};
181-
let msg = Paragraph::new(Span::styled(" No providers found.", t.muted));
182-
frame.render_widget(msg, inner);
168+
super::draw_empty_message(frame, area, " No providers found.", t.muted);
183169
}
184170
}

crates/openshell-tui/src/ui/sandbox_settings.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use ratatui::layout::{Constraint, Rect};
66
use ratatui::text::{Line, Span};
77
use ratatui::widgets::{Block, Borders, Cell, Clear, Padding, Paragraph, Row, Table};
88

9+
use super::draw_empty_message;
910
use crate::app::{App, SandboxPolicyTab, SettingScope};
1011

1112
pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect) {
@@ -83,14 +84,7 @@ pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect) {
8384
frame.render_widget(table, area);
8485

8586
if app.sandbox_settings.is_empty() {
86-
let inner = Rect {
87-
x: area.x + 2,
88-
y: area.y + 2,
89-
width: area.width.saturating_sub(4),
90-
height: area.height.saturating_sub(3),
91-
};
92-
let msg = Paragraph::new(Span::styled(" No settings available.", t.muted));
93-
frame.render_widget(msg, inner);
87+
draw_empty_message(frame, area, " No settings available.", t.muted);
9488
}
9589

9690
// Overlays.

crates/openshell-tui/src/ui/sandboxes.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use ratatui::Frame;
55
use ratatui::layout::{Constraint, Rect};
66
use ratatui::text::{Line, Span};
7-
use ratatui::widgets::{Block, Borders, Cell, Padding, Paragraph, Row, Table};
7+
use ratatui::widgets::{Block, Borders, Cell, Padding, Row, Table};
88

99
use crate::app::App;
1010

@@ -102,13 +102,6 @@ pub fn draw(frame: &mut Frame<'_>, app: &App, area: Rect, focused: bool) {
102102
frame.render_widget(table, area);
103103

104104
if app.sandbox_count == 0 {
105-
let inner = Rect {
106-
x: area.x + 2,
107-
y: area.y + 2,
108-
width: area.width.saturating_sub(4),
109-
height: area.height.saturating_sub(3),
110-
};
111-
let msg = Paragraph::new(Span::styled(" No sandboxes. Press [c] to create.", t.muted));
112-
frame.render_widget(msg, inner);
105+
super::draw_empty_message(frame, area, " No sandboxes. Press [c] to create.", t.muted);
113106
}
114107
}

0 commit comments

Comments
 (0)