mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-09-22 08:01:00 +00:00
feat: add customizable system monitoring position
- Add settings to position the system monitoring panel at the Bottom, Sidebar, or Hidden. - Support dynamically reallocating UI space when the monitor panel is moved. - Implement a simplified, compact card-style system monitoring view for the sidebar (similar to v0.3.5 design). - Add full localization for monitoring setting and sidebar metrics.
This commit is contained in:
@@ -68,6 +68,10 @@ system_default: "System Default"
|
||||
ui_font_family: "UI Font"
|
||||
terminal_font_family: "Terminal Font"
|
||||
theme_management_hint: "Theme mode and light/dark theme families are managed from the top-bar theme button."
|
||||
monitoring_position: "Monitoring Position"
|
||||
position_bottom: "Bottom"
|
||||
position_sidebar: "Sidebar"
|
||||
position_hidden: "Hidden"
|
||||
local_host: "local host"
|
||||
local_terminal_opened: "local terminal opened"
|
||||
connecting: "Connecting"
|
||||
|
||||
+5
-1
@@ -69,7 +69,11 @@ terminal_font_size: "终端字体大小"
|
||||
system_default: "系统默认"
|
||||
ui_font_family: "界面字体"
|
||||
terminal_font_family: "终端字体"
|
||||
theme_management_hint: "主题模式和亮/暗主题系列可通过顶栏的主题按钮管理。"
|
||||
theme_management_hint: "深色/浅色模式以及主题配色由顶部栏的样式按钮统一管理。"
|
||||
monitoring_position: "系统监控显示位置"
|
||||
position_bottom: "底部"
|
||||
position_sidebar: "侧边栏"
|
||||
position_hidden: "不显示"
|
||||
local_host: "本地主机"
|
||||
local_terminal_opened: "已打开本地终端"
|
||||
connecting: "正在连接"
|
||||
|
||||
@@ -127,6 +127,12 @@ pub struct ConfigFile {
|
||||
pub transfers: Vec<crate::terminal::Transfer>,
|
||||
#[serde(default)]
|
||||
pub show_hidden_files: bool,
|
||||
#[serde(default = "default_monitoring_position")]
|
||||
pub monitoring_position: String,
|
||||
}
|
||||
|
||||
fn default_monitoring_position() -> String {
|
||||
"Sidebar".to_string()
|
||||
}
|
||||
|
||||
fn default_locale() -> String {
|
||||
@@ -230,6 +236,14 @@ impl ConfigStore {
|
||||
self.cache.locale = locale.to_string();
|
||||
}
|
||||
|
||||
pub fn monitoring_position(&self) -> &str {
|
||||
&self.cache.monitoring_position
|
||||
}
|
||||
|
||||
pub fn set_monitoring_position(&mut self, pos: &str) {
|
||||
self.cache.monitoring_position = pos.to_string();
|
||||
}
|
||||
|
||||
pub fn terminal_font_size(&self) -> f32 {
|
||||
if self.cache.terminal_font_size <= 0.0 {
|
||||
default_terminal_font_size()
|
||||
|
||||
@@ -1148,6 +1148,72 @@ impl Ashell {
|
||||
.child(t!("copy_paste_hint", key = if cfg!(target_os = "macos") { "Command" } else { "Ctrl" }).to_string())
|
||||
)
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.child(div().w(px(240.)).child(t!("monitoring_position").to_string()))
|
||||
.child(
|
||||
Button::new("monitoring-position-dropdown")
|
||||
.small()
|
||||
.icon(IconName::PanelLeftOpen)
|
||||
.label({
|
||||
let pos = view.read(cx).config.monitoring_position().to_string();
|
||||
if pos == "Sidebar" {
|
||||
t!("position_sidebar").to_string()
|
||||
} else if pos == "Hidden" {
|
||||
t!("position_hidden").to_string()
|
||||
} else {
|
||||
t!("position_bottom").to_string()
|
||||
}
|
||||
})
|
||||
.dropdown_menu_with_anchor(Anchor::BottomRight, {
|
||||
let view = view.clone();
|
||||
move |mut menu, window, cx| {
|
||||
let pos = view.read(cx).config.monitoring_position().to_string();
|
||||
menu = menu
|
||||
.min_w(160.)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("position_bottom").to_string())
|
||||
.checked(pos == "Bottom")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _window, cx| {
|
||||
this.config.set_monitoring_position("Bottom");
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
},
|
||||
)),
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("position_sidebar").to_string())
|
||||
.checked(pos == "Sidebar")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _window, cx| {
|
||||
this.config.set_monitoring_position("Sidebar");
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
},
|
||||
)),
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t!("position_hidden").to_string())
|
||||
.checked(pos == "Hidden")
|
||||
.on_click(window.listener_for(
|
||||
&view,
|
||||
|this, _, _window, cx| {
|
||||
this.config.set_monitoring_position("Hidden");
|
||||
let _ = this.config.save();
|
||||
cx.notify();
|
||||
},
|
||||
)),
|
||||
);
|
||||
menu
|
||||
}
|
||||
}),
|
||||
)
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_center()
|
||||
|
||||
@@ -692,7 +692,7 @@ impl Ashell {
|
||||
.text_size(rems(0.833))
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.text_color(cpu_color)
|
||||
.child("CPU"),
|
||||
.child(t!("cpu").to_string()),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
@@ -756,7 +756,7 @@ impl Ashell {
|
||||
.text_size(rems(0.833))
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.text_color(mem_color)
|
||||
.child("MEM"),
|
||||
.child(t!("mem").to_string()),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
@@ -826,7 +826,7 @@ impl Ashell {
|
||||
.text_size(rems(0.833))
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.text_color(net_color)
|
||||
.child("NET"),
|
||||
.child(t!("net").to_string()),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
@@ -933,7 +933,7 @@ impl Ashell {
|
||||
.text_size(rems(0.833))
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.text_color(disk_color)
|
||||
.child("DISK"),
|
||||
.child(t!("disk").to_string()),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
@@ -1030,6 +1030,107 @@ impl Ashell {
|
||||
panel
|
||||
}
|
||||
|
||||
fn render_sidebar_monitoring_panel(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let cpu_pct = self.system.cpu_percent;
|
||||
let mem_pct = self.system.mem_percent;
|
||||
let swap_pct = self.system.swap_percent;
|
||||
let disk_pct = if let Some(root_disk) = self.system.disks.iter().find(|d| d.mount == "/") {
|
||||
if root_disk.total_bytes > 0 {
|
||||
(root_disk.total_bytes - root_disk.available_bytes) as f64 / root_disk.total_bytes as f64
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
} as f32;
|
||||
|
||||
let cpu_color = cx.theme().chart_1;
|
||||
let mem_color = cx.theme().chart_2;
|
||||
let swap_color = cx.theme().chart_3;
|
||||
let disk_color = cx.theme().chart_5;
|
||||
let net_color = cx.theme().chart_4;
|
||||
let muted_fg = cx.theme().muted_foreground;
|
||||
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.w_full()
|
||||
.p_2()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(div().text_size(rems(0.85)).text_color(cpu_color).child(t!("cpu").to_string()))
|
||||
.child(div().text_size(rems(0.85)).text_color(muted_fg).child(format!("{:.1}%", cpu_pct * 100.0))),
|
||||
)
|
||||
.child(Progress::new("sidebar-cpu").value(cpu_pct * 100.0).color(cpu_color).with_size(px(4.)).w_full())
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(div().text_size(rems(0.85)).text_color(mem_color).child(t!("mem").to_string()))
|
||||
.child(div().text_size(rems(0.85)).text_color(muted_fg).child(self.system.mem_detail.clone())),
|
||||
)
|
||||
.child(Progress::new("sidebar-mem").value(mem_pct * 100.0).color(mem_color).with_size(px(4.)).w_full())
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(div().text_size(rems(0.85)).text_color(swap_color).child(t!("swap").to_string()))
|
||||
.child(div().text_size(rems(0.85)).text_color(muted_fg).child(self.system.swap_detail.clone())),
|
||||
)
|
||||
.child(Progress::new("sidebar-swap").value(swap_pct * 100.0).color(swap_color).with_size(px(4.)).w_full())
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(div().text_size(rems(0.85)).text_color(disk_color).child(t!("disk").to_string()))
|
||||
.child(div().text_size(rems(0.85)).text_color(muted_fg).child(format!("{:.1}%", disk_pct * 100.0))),
|
||||
)
|
||||
.child(Progress::new("sidebar-disk").value(disk_pct * 100.0).color(disk_color).with_size(px(4.)).w_full())
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(div().text_size(rems(0.85)).text_color(net_color).child(t!("net").to_string()))
|
||||
.child(div().text_size(rems(0.85)).text_color(muted_fg).child(t!("live"))),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.min_w(px(0.))
|
||||
.gap_1()
|
||||
.child(div().flex_none().text_size(rems(0.75)).text_color(net_color).child("↓"))
|
||||
.child(div().text_size(rems(0.75)).child(self.system.net_rx.clone()))
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.min_w(px(0.))
|
||||
.gap_1()
|
||||
.child(div().flex_none().text_size(rems(0.75)).text_color(cx.theme().chart_5).child("↑"))
|
||||
.child(div().text_size(rems(0.75)).child(self.system.net_tx.clone()))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fn sidebar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let sessions = self.config.sessions().to_vec();
|
||||
let active_session_id = self.active_session_id().map(ToOwned::to_owned);
|
||||
@@ -1101,6 +1202,9 @@ impl Ashell {
|
||||
cx.listener(|this, _, window, cx| this.open_new_ssh_dialog(window, cx)),
|
||||
),
|
||||
)
|
||||
.when(self.config.monitoring_position() == "Sidebar", |this| {
|
||||
this.child(self.render_sidebar_monitoring_panel(cx))
|
||||
})
|
||||
.child(
|
||||
v_flex()
|
||||
.flex_1()
|
||||
@@ -1454,9 +1558,16 @@ impl Render for Ashell {
|
||||
|
||||
let monitoring_contents = v_flex()
|
||||
.size_full()
|
||||
.child(self.render_monitoring_panel(window.viewport_size().width, cx))
|
||||
.when(self.config.monitoring_position() == "Bottom", |this| {
|
||||
this.child(self.render_monitoring_panel(window.viewport_size().width, cx))
|
||||
})
|
||||
.child(self.render_sftp_panel(window, cx));
|
||||
|
||||
let is_monitor_bottom = self.config.monitoring_position() == "Bottom";
|
||||
let minimized_height = if is_monitor_bottom { 114. } else { 34. };
|
||||
let min_panel_height = if is_monitor_bottom { 260. } else { 180. };
|
||||
let default_panel_height = if is_monitor_bottom { 328. } else { 248. };
|
||||
|
||||
let body_panel = if self.sftp_panel_minimized {
|
||||
v_flex()
|
||||
.size_full()
|
||||
@@ -1470,7 +1581,7 @@ impl Render for Ashell {
|
||||
.child(
|
||||
div()
|
||||
.flex_none()
|
||||
.h(px(114.))
|
||||
.h(px(minimized_height))
|
||||
.w_full()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().border)
|
||||
@@ -1487,8 +1598,8 @@ impl Render for Ashell {
|
||||
.config
|
||||
.body_panels()
|
||||
.and_then(|s| s.get(1).copied())
|
||||
.unwrap_or(328.)))
|
||||
.size_range(px(260.)..px(1200.))
|
||||
.unwrap_or(default_panel_height)))
|
||||
.size_range(px(min_panel_height)..px(1200.))
|
||||
.child(monitoring_contents)
|
||||
)
|
||||
.into_any_element()
|
||||
|
||||
Reference in New Issue
Block a user