zilliz 1.4.3

TUI and CLI tool for managing Zilliz Cloud clusters and Milvus operations
Documentation
//! Single-row header strip rendered above the home body.
//!
//! Visual sibling of the status bar: left zone (identity) is left-aligned and
//! truncates with an ellipsis on overflow; right zone (version) is flush-right
//! and never truncates.

use ratatui::prelude::*;
use ratatui::widgets::Paragraph;
use unicode_width::UnicodeWidthStr;

use crate::tui::app::{App, AuthMethod, AuthState};

#[derive(Default)]
pub struct Header {
    pub left: Vec<Span<'static>>,
    pub right: Vec<Span<'static>>,
}

/// Build a header for the current app state. Pure — no IO.
pub fn build_header(app: &App) -> Header {
    let dim = Style::default().fg(Color::DarkGray);
    let gray = Style::default().fg(Color::Gray);
    let sep = || Span::styled(" · ", dim);

    let left: Vec<Span<'static>> = match &app.auth.state {
        AuthState::SignedOut => {
            let cred = shorten_home(&app.auth.credentials_path.display().to_string());
            vec![
                Span::styled("", Style::default().fg(Color::DarkGray)),
                Span::styled("Not signed in".to_string(), gray),
                sep(),
                Span::styled(cred, Style::default().fg(Color::Rgb(110, 180, 255))),
            ]
        }
        AuthState::SignedIn {
            method,
            user,
            email,
            org,
            region,
            masked_api_key,
            ..
        } => {
            let dot = Span::styled("", Style::default().fg(Color::Green));
            let mut spans = vec![dot];
            match method {
                AuthMethod::Auth0 => {
                    let identity = email
                        .clone()
                        .or_else(|| user.clone())
                        .unwrap_or_else(|| "signed in".to_string());
                    spans.push(Span::styled(identity, gray));
                    if let Some(org_name) = org.as_ref().filter(|s| !s.is_empty()) {
                        spans.push(sep());
                        spans.push(Span::styled(org_name.clone(), gray));
                    }
                    spans.push(sep());
                    spans.push(Span::styled(region.slug(), gray));
                }
                AuthMethod::ApiKey => {
                    spans.push(Span::styled("API key".to_string(), gray));
                    if let Some(mk) = masked_api_key.as_ref() {
                        spans.push(sep());
                        spans.push(Span::styled(mk.clone(), gray));
                    }
                    spans.push(sep());
                    spans.push(Span::styled(region.slug(), gray));
                }
            }
            spans
        }
    };

    let version = format!("zilliz v{}", env!("CARGO_PKG_VERSION"));
    let right = vec![Span::styled(version, dim)];

    Header { left, right }
}

pub fn render(frame: &mut Frame, area: Rect, header: &Header) {
    let total = area.width as usize;
    let right_width: usize = header
        .right
        .iter()
        .map(|s| UnicodeWidthStr::width(s.content.as_ref()))
        .sum();
    let left_budget = total.saturating_sub(right_width + 2);

    let mut left: Vec<Span<'static>> = Vec::new();
    let mut used = 0usize;
    for span in header.left.iter().cloned() {
        let w = UnicodeWidthStr::width(span.content.as_ref());
        if used + w > left_budget {
            if left_budget > used + 1 {
                left.push(Span::styled("", Style::default().fg(Color::DarkGray)));
            }
            break;
        }
        used += w;
        left.push(span);
    }

    let pad = total.saturating_sub(used + right_width).max(1);
    let mut all = left;
    all.push(Span::raw(" ".repeat(pad)));
    all.extend(header.right.iter().cloned());

    let para = Paragraph::new(Line::from(all));
    frame.render_widget(para, area);
}

fn shorten_home(path: &str) -> String {
    if let Some(home) = dirs::home_dir() {
        let home_s = home.display().to_string();
        if let Some(rest) = path.strip_prefix(&home_s) {
            return format!("~{}", rest);
        }
    }
    path.to_string()
}