Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/demos/infoping/model.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ actions:
args:
host: "claim(connectivity.target)"
count: "claim(connectivity.ping-count)"
internet:
opts:
- ping
args:
host: google.com
count: 2

check-sshd:
descr: Verify SSH daemon status
Expand Down
43 changes: 43 additions & 0 deletions libsysinspect/src/console/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ pub struct ConsoleMinionProcessSignalRequest {
pub signal: i32,
}

/// Request parameters for a minion self-upgrade via the master fileserver.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsoleMinionUpgradeSelfRequest {
/// Relative subpath of the new binary under the master fileserver root.
pub subpath: String,
/// Expected SHA-256 checksum of the new binary.
pub checksum: String,
/// Version label of the new binary.
pub version: String,
}

fn default_top_process_limit() -> usize {
24
}
Expand Down Expand Up @@ -336,6 +347,32 @@ pub enum ConsolePayload {
#[serde(default)]
failures: Vec<String>,
},
/// Current cluster upgrade counts derived from master-side Sled markers.
UpgradeStatus {
/// Number of minions still marked as requiring upgrade/sync.
required: usize,
/// Number of marked minions last seen as unreachable during upgrade.
unreachable: usize,
/// Number of minions pending post-upgrade auto-hopstart.
#[serde(default)]
pending_post_upgrade: usize,
},
/// Result summary for one cluster upgrade run.
UpgradeSummary {
/// Number of offline minions upgraded successfully over SSH.
updated: usize,
/// Number of online minions that received a self-upgrade command.
dispatched: usize,
/// Number of minions skipped because they were unavailable or unmanaged.
skipped: usize,
/// Number of upgrade attempts that failed unexpectedly.
failed: usize,
/// Number of minions that were offline and had no SSH fallback.
offline: usize,
/// Human-readable details for skipped or failed minions.
#[serde(default)]
items: Vec<String>,
},
}

/// One online-minion summary row returned by the master.
Expand Down Expand Up @@ -363,6 +400,12 @@ pub struct ConsoleOnlineMinionRow {
/// Whether the current runtime version is older than the matching repository version.
#[serde(default)]
pub outdated: bool,
/// Whether this minion is currently marked in the master's upgrade queue.
#[serde(default)]
pub upgrade_required: bool,
/// Whether the latest upgrade attempt marked this minion unreachable.
#[serde(default)]
pub upgrade_unreachable: bool,
/// OS distribution reported by minion traits, if present.
#[serde(default)]
pub os_distribution: String,
Expand Down
12 changes: 12 additions & 0 deletions libsysproto/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ pub mod commands {
// Send a Unix signal to one process on one specific minion
pub const CLUSTER_MINION_PROCESS_SIGNAL: &str = "cluster/minion/process/signal";

// Mark all selected minions as requiring a cluster upgrade/sync
pub const CLUSTER_MARK_UPGRADE_REQUIRED: &str = "cluster/upgrade/mark";

// Run cluster upgrade/sync across marked minions
pub const CLUSTER_UPGRADE_MINIONS: &str = "cluster/upgrade/run";

// Read current cluster upgrade status counts
pub const CLUSTER_UPGRADE_STATUS: &str = "cluster/upgrade/status";

// Ask a minion to download, verify, and replace its own binary
pub const CLUSTER_MINION_UPGRADE_SELF: &str = "cluster/minion/upgrade/self";

// Force all online minions to reconnect (cluster-wide broadcast)
pub const CLUSTER_RECONNECT: &str = "cluster/reconnect";

Expand Down
17 changes: 17 additions & 0 deletions src/clifmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,5 +416,22 @@ pub fn render_console_payload(payload: &ConsolePayload) -> String {
ConsolePayload::MasterLogs { snapshot: _ } => String::new(),
ConsolePayload::MasterModuleIndex { .. } => String::new(),
ConsolePayload::MasterLibraryIndex { .. } => String::new(),
ConsolePayload::UpgradeStatus { required, unreachable, .. } => {
format!("Cluster upgrade status: required={required}, unreachable={unreachable}")
}
ConsolePayload::UpgradeSummary { updated, dispatched, skipped, failed, offline, items } => {
let mut out = vec![
format!("SSH upgraded: {updated}"),
format!("Dispatched to online: {dispatched}"),
format!("Skipped: {skipped}"),
format!("Failed: {failed}"),
format!("Offline: {offline}"),
];
if !items.is_empty() {
out.push(String::new());
out.extend(items.clone());
}
out.join("\n")
}
}
}
38 changes: 38 additions & 0 deletions src/ui/alert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,44 @@ impl SysInspectUX {
Self::draw_popup_shadow(buf, canvas, height);
}

pub fn dialog_cluster_upgrade_progress(&self, parent: Rect, buf: &mut Buffer) {
if !self.cluster_upgrade_progress.visible {
return;
}

let text = Line::from(vec![Span::styled(
format!("{} {}", self.cluster_upgrade_progress.spinner.view(), self.cluster_upgrade_progress.message),
Style::default().fg(palette::FG),
)]);
let width = (UnicodeWidthStr::width(self.cluster_upgrade_progress.message.as_str()) as u16 + 12).max(48);
let height = 5u16;
let x = parent.x + (parent.width.saturating_sub(width)) / 2;
let y = parent.y + (parent.height.saturating_sub(height)) / 2;
let canvas = Rect { x, y, width, height };

Clear.render(canvas, buf);

let popup_block = Block::default()
.borders(Borders::ALL)
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(palette::WARNING_PEAK))
.padding(Padding::horizontal(2))
.style(Style::default().bg(palette::POPUP_BG_BASE));
let popup_inner = popup_block.inner(canvas);
popup_block.render(canvas, buf);

let [_, text_area, _]: [Rect; 3] = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Length(1), Constraint::Min(0)])
.split(popup_inner)
.as_ref()
.try_into()
.unwrap();
Paragraph::new(text).alignment(Alignment::Center).render(text_area, buf);

Self::draw_popup_shadow(buf, canvas, height);
}

pub fn dialog_master_confirm(&self, parent: Rect, buf: &mut Buffer) {
if !self.master_confirm_visible {
return;
Expand Down
10 changes: 9 additions & 1 deletion src/ui/elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ impl CycleListItem {
return vec![Span::styled(display, Style::default().fg(palette::FG))];
}

if let Some((model, label)) = display.split_once(':') {
return vec![
Span::styled(model.to_string(), Style::default().fg(palette::PROCESSING_BASE)),
Span::styled(":", Style::default().fg(palette::FG)),
Span::styled(label.to_string(), Style::default().fg(palette::PROCESSING_PEAK)),
];
}

let parts: Vec<&str> = display.split('/').collect();
match parts.as_slice() {
[model, target] => vec![
Expand All @@ -76,7 +84,7 @@ impl CycleListItem {
Span::styled("/", Style::default().fg(palette::FG)),
Span::styled((*target).to_string(), Style::default().fg(palette::PROCESSING_HEAT)),
Span::styled("/", Style::default().fg(palette::FG)),
Span::styled((*state).to_string(), Style::default().fg(palette::PROCESSING_PEAK)),
Span::styled((*state).to_string(), Style::default().fg(palette::PRIMARY)),
],
_ => vec![Span::styled(display, Style::default().fg(palette::FG))],
}
Expand Down
12 changes: 9 additions & 3 deletions src/ui/macts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ const MENU_SECTIONS: &[MenuSection] = &[
const MASTER_MENU_SECTIONS: &[MenuSection] = &[
MenuSection {
title: "Operations",
items: &[("View master logs online", "^O"), ("View local logs", "^L"), ("Register a minion", "^R"), ("Artefacts Manager", "^A")],
items: &[
("View master logs online", "^O"),
("View local logs", "^L"),
("Register a minion", "^R"),
("Artefacts Manager", "^A"),
("Cluster upgrade", "^U"),
],
},
MenuSection { title: "System", items: &[("Start", "^T"), ("Stop", "^S"), ("Restart", "^E")] },
];
Expand Down Expand Up @@ -222,15 +228,15 @@ impl SysInspectUX {
let max_item_w = max_label_w + 20;

let title_style = TitleStyle::cyberpunk(palette::PROCESSING_GLOW);
let is_system = self.master_menu_sel >= 4;
let is_system = self.master_menu_sel >= 5;
let sub_title = if is_system { " System " } else { " Operations " };
let segments = vec![
TitleSegment { text: " Master ".into(), bg: palette::PROCESSING_GLOW, fg: palette::FG, modifier: Modifier::empty() },
TitleSegment { text: sub_title.into(), bg: palette::PROCESSING_HEAT, fg: palette::FG, modifier: Modifier::empty() },
];

let local_logs_available = self.cfg.logfile_std().exists() || self.cfg.logfile_err().exists();
let disabled = [!local_logs_available, false, false, false, false, false, false];
let disabled = [!local_logs_available, false, false, false, false, false, false, false];

render_menu_popup(parent, buf, MASTER_MENU_SECTIONS, self.master_menu_sel, &segments, &title_style, max_item_w, &disabled);
}
Expand Down
Loading
Loading