feat: refine system specs screen layout
Build And Push Container / build-and-push (push) Successful in 56s
Build And Push Container / build-and-push (push) Successful in 56s
This commit is contained in:
@@ -177,7 +177,7 @@ fn run_display_session(state: &AppState, screen: &mut AooScreen) -> Result<()> {
|
|||||||
if send_due {
|
if send_due {
|
||||||
if active_frame.image_name == SYSTEM_FRAME_NAME {
|
if active_frame.image_name == SYSTEM_FRAME_NAME {
|
||||||
let panel_name = SYSTEM_FRAME_NAME.to_string();
|
let panel_name = SYSTEM_FRAME_NAME.to_string();
|
||||||
let rgb_img = render_system_panel();
|
let rgb_img = render_system_panel(&snapshot.specs_mode);
|
||||||
prepare_screen_for_frame(screen, &active_frame);
|
prepare_screen_for_frame(screen, &active_frame);
|
||||||
screen
|
screen
|
||||||
.send_image(&rgb_img)
|
.send_image(&rgb_img)
|
||||||
@@ -660,6 +660,7 @@ mod tests {
|
|||||||
custom_panel: true,
|
custom_panel: true,
|
||||||
switch_time: 7,
|
switch_time: 7,
|
||||||
specs_enabled: false,
|
specs_enabled: false,
|
||||||
|
specs_mode: "cards".into(),
|
||||||
memes_enabled: false,
|
memes_enabled: false,
|
||||||
gifs_enabled: true,
|
gifs_enabled: true,
|
||||||
active_images: vec![manifest_name.into()],
|
active_images: vec![manifest_name.into()],
|
||||||
@@ -702,6 +703,7 @@ mod tests {
|
|||||||
custom_panel: true,
|
custom_panel: true,
|
||||||
switch_time: 7,
|
switch_time: 7,
|
||||||
specs_enabled: false,
|
specs_enabled: false,
|
||||||
|
specs_mode: "cards".into(),
|
||||||
memes_enabled: false,
|
memes_enabled: false,
|
||||||
gifs_enabled: false,
|
gifs_enabled: false,
|
||||||
active_images: vec![manifest_name.into()],
|
active_images: vec![manifest_name.into()],
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ pub(crate) async fn ensure_layout(state: &AppState) -> Result<()> {
|
|||||||
"language": 1,
|
"language": 1,
|
||||||
"switchTime": "10",
|
"switchTime": "10",
|
||||||
"nativeSpecs": false,
|
"nativeSpecs": false,
|
||||||
|
"nativeSpecsMode": "cards",
|
||||||
"nativeMemes": true,
|
"nativeMemes": true,
|
||||||
"nativeGifs": true,
|
"nativeGifs": true,
|
||||||
"operationMode": 0,
|
"operationMode": 0,
|
||||||
@@ -93,6 +94,7 @@ pub(crate) async fn build_state_response(state: &AppState) -> Result<StateRespon
|
|||||||
.unwrap_or("10")
|
.unwrap_or("10")
|
||||||
.to_string();
|
.to_string();
|
||||||
let specs_enabled = current_specs_enabled(&monitor);
|
let specs_enabled = current_specs_enabled(&monitor);
|
||||||
|
let specs_mode = current_specs_mode(&monitor);
|
||||||
let memes_enabled = current_memes_enabled(&monitor);
|
let memes_enabled = current_memes_enabled(&monitor);
|
||||||
let gifs_enabled = current_gifs_enabled(&monitor);
|
let gifs_enabled = current_gifs_enabled(&monitor);
|
||||||
|
|
||||||
@@ -103,6 +105,7 @@ pub(crate) async fn build_state_response(state: &AppState) -> Result<StateRespon
|
|||||||
custom_panel,
|
custom_panel,
|
||||||
switch_time,
|
switch_time,
|
||||||
specs_enabled,
|
specs_enabled,
|
||||||
|
specs_mode,
|
||||||
memes_enabled,
|
memes_enabled,
|
||||||
gifs_enabled,
|
gifs_enabled,
|
||||||
},
|
},
|
||||||
@@ -162,6 +165,16 @@ pub(crate) fn current_memes_enabled(monitor: &Value) -> bool {
|
|||||||
.unwrap_or_else(|| !current_active_images(monitor).is_empty())
|
.unwrap_or_else(|| !current_active_images(monitor).is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn current_specs_mode(monitor: &Value) -> String {
|
||||||
|
monitor
|
||||||
|
.get("setup")
|
||||||
|
.and_then(Value::as_object)
|
||||||
|
.and_then(|setup| setup.get("nativeSpecsMode"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.map(normalize_specs_mode)
|
||||||
|
.unwrap_or_else(|| "cards".into())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn current_gifs_enabled(monitor: &Value) -> bool {
|
pub(crate) fn current_gifs_enabled(monitor: &Value) -> bool {
|
||||||
monitor
|
monitor
|
||||||
.get("setup")
|
.get("setup")
|
||||||
@@ -176,6 +189,7 @@ pub(crate) fn rotation_snapshot(monitor: &Value) -> RotationSnapshot {
|
|||||||
custom_panel: current_custom_panel_enabled(monitor),
|
custom_panel: current_custom_panel_enabled(monitor),
|
||||||
switch_time: current_switch_time(monitor),
|
switch_time: current_switch_time(monitor),
|
||||||
specs_enabled: current_specs_enabled(monitor),
|
specs_enabled: current_specs_enabled(monitor),
|
||||||
|
specs_mode: current_specs_mode(monitor),
|
||||||
memes_enabled: current_memes_enabled(monitor),
|
memes_enabled: current_memes_enabled(monitor),
|
||||||
gifs_enabled: current_gifs_enabled(monitor),
|
gifs_enabled: current_gifs_enabled(monitor),
|
||||||
active_images: current_active_images(monitor),
|
active_images: current_active_images(monitor),
|
||||||
@@ -186,6 +200,7 @@ pub(crate) fn set_custom_panels(
|
|||||||
monitor: &mut Value,
|
monitor: &mut Value,
|
||||||
switch_time: u32,
|
switch_time: u32,
|
||||||
specs_enabled: bool,
|
specs_enabled: bool,
|
||||||
|
specs_mode: &str,
|
||||||
memes_enabled: bool,
|
memes_enabled: bool,
|
||||||
gifs_enabled: bool,
|
gifs_enabled: bool,
|
||||||
images: &[String],
|
images: &[String],
|
||||||
@@ -201,6 +216,10 @@ pub(crate) fn set_custom_panels(
|
|||||||
setup.insert("customPanel".into(), Value::Bool(enabled));
|
setup.insert("customPanel".into(), Value::Bool(enabled));
|
||||||
setup.insert("switchTime".into(), Value::String(switch_time.to_string()));
|
setup.insert("switchTime".into(), Value::String(switch_time.to_string()));
|
||||||
setup.insert("nativeSpecs".into(), Value::Bool(specs_enabled));
|
setup.insert("nativeSpecs".into(), Value::Bool(specs_enabled));
|
||||||
|
setup.insert(
|
||||||
|
"nativeSpecsMode".into(),
|
||||||
|
Value::String(normalize_specs_mode(specs_mode)),
|
||||||
|
);
|
||||||
setup.insert("nativeMemes".into(), Value::Bool(memes_enabled));
|
setup.insert("nativeMemes".into(), Value::Bool(memes_enabled));
|
||||||
setup.insert("nativeGifs".into(), Value::Bool(gifs_enabled));
|
setup.insert("nativeGifs".into(), Value::Bool(gifs_enabled));
|
||||||
}
|
}
|
||||||
@@ -218,6 +237,13 @@ pub(crate) fn set_custom_panels(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_specs_mode(value: &str) -> String {
|
||||||
|
match value {
|
||||||
|
"meters" => "meters".into(),
|
||||||
|
_ => "cards".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn load_monitor_json_sync(path: &PathBuf) -> Result<Value> {
|
pub(crate) fn load_monitor_json_sync(path: &PathBuf) -> Result<Value> {
|
||||||
let raw = std::fs::read_to_string(path)
|
let raw = std::fs::read_to_string(path)
|
||||||
.with_context(|| format!("Failed to read {}", path.display()))?;
|
.with_context(|| format!("Failed to read {}", path.display()))?;
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ use crate::{
|
|||||||
},
|
},
|
||||||
monitor::{
|
monitor::{
|
||||||
build_state_response, current_active_images, current_gifs_enabled, current_memes_enabled,
|
build_state_response, current_active_images, current_gifs_enabled, current_memes_enabled,
|
||||||
current_specs_enabled, current_switch_time, load_monitor_json, save_monitor_json,
|
current_specs_enabled, current_specs_mode, current_switch_time, load_monitor_json,
|
||||||
set_custom_panels,
|
save_monitor_json, set_custom_panels,
|
||||||
},
|
},
|
||||||
types::{ActivateRequest, AppState, DeleteRequest, ErrorResponse},
|
types::{ActivateRequest, AppState, DeleteRequest, ErrorResponse},
|
||||||
ui::index_response,
|
ui::index_response,
|
||||||
@@ -103,6 +103,17 @@ async fn api_activate(
|
|||||||
) -> Response {
|
) -> Response {
|
||||||
let switch_time = payload.switch_time.unwrap_or(10).clamp(1, 600);
|
let switch_time = payload.switch_time.unwrap_or(10).clamp(1, 600);
|
||||||
let specs_enabled = payload.specs_enabled.unwrap_or(false);
|
let specs_enabled = payload.specs_enabled.unwrap_or(false);
|
||||||
|
let specs_mode = payload
|
||||||
|
.specs_mode
|
||||||
|
.as_deref()
|
||||||
|
.map(|value| {
|
||||||
|
if value == "meters" {
|
||||||
|
"meters".to_string()
|
||||||
|
} else {
|
||||||
|
"cards".to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "cards".to_string());
|
||||||
let available = match list_images(&state).await {
|
let available = match list_images(&state).await {
|
||||||
Ok(items) => items,
|
Ok(items) => items,
|
||||||
Err(err) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
|
Err(err) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
|
||||||
@@ -140,6 +151,7 @@ async fn api_activate(
|
|||||||
&mut monitor,
|
&mut monitor,
|
||||||
switch_time,
|
switch_time,
|
||||||
specs_enabled,
|
specs_enabled,
|
||||||
|
&specs_mode,
|
||||||
memes_enabled,
|
memes_enabled,
|
||||||
gifs_enabled,
|
gifs_enabled,
|
||||||
&valid,
|
&valid,
|
||||||
@@ -153,6 +165,7 @@ async fn api_activate(
|
|||||||
"ok": true,
|
"ok": true,
|
||||||
"switchTime": switch_time,
|
"switchTime": switch_time,
|
||||||
"specsEnabled": specs_enabled,
|
"specsEnabled": specs_enabled,
|
||||||
|
"specsMode": specs_mode,
|
||||||
"memesEnabled": memes_enabled,
|
"memesEnabled": memes_enabled,
|
||||||
"gifsEnabled": gifs_enabled,
|
"gifsEnabled": gifs_enabled,
|
||||||
"activeImages": valid,
|
"activeImages": valid,
|
||||||
@@ -167,11 +180,13 @@ async fn api_disable(State(state): State<Arc<AppState>>) -> Response {
|
|||||||
};
|
};
|
||||||
let switch_time = current_switch_time(&monitor);
|
let switch_time = current_switch_time(&monitor);
|
||||||
let active_images = current_active_images(&monitor);
|
let active_images = current_active_images(&monitor);
|
||||||
|
let specs_mode = current_specs_mode(&monitor);
|
||||||
|
|
||||||
set_custom_panels(
|
set_custom_panels(
|
||||||
&mut monitor,
|
&mut monitor,
|
||||||
switch_time,
|
switch_time,
|
||||||
false,
|
false,
|
||||||
|
&specs_mode,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
&active_images,
|
&active_images,
|
||||||
@@ -209,6 +224,7 @@ async fn api_delete(
|
|||||||
let mut active_images = current_active_images(&monitor);
|
let mut active_images = current_active_images(&monitor);
|
||||||
let switch_time = current_switch_time(&monitor);
|
let switch_time = current_switch_time(&monitor);
|
||||||
let specs_enabled = current_specs_enabled(&monitor);
|
let specs_enabled = current_specs_enabled(&monitor);
|
||||||
|
let specs_mode = current_specs_mode(&monitor);
|
||||||
let memes_enabled = current_memes_enabled(&monitor);
|
let memes_enabled = current_memes_enabled(&monitor);
|
||||||
let gifs_enabled = current_gifs_enabled(&monitor);
|
let gifs_enabled = current_gifs_enabled(&monitor);
|
||||||
let before = active_images.len();
|
let before = active_images.len();
|
||||||
@@ -218,6 +234,7 @@ async fn api_delete(
|
|||||||
&mut monitor,
|
&mut monitor,
|
||||||
switch_time,
|
switch_time,
|
||||||
specs_enabled,
|
specs_enabled,
|
||||||
|
&specs_mode,
|
||||||
memes_enabled,
|
memes_enabled,
|
||||||
gifs_enabled,
|
gifs_enabled,
|
||||||
&active_images,
|
&active_images,
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ use crate::types::SystemView;
|
|||||||
pub(crate) const DISPLAY_WIDTH: u32 = 960;
|
pub(crate) const DISPLAY_WIDTH: u32 = 960;
|
||||||
pub(crate) const DISPLAY_HEIGHT: u32 = 376;
|
pub(crate) const DISPLAY_HEIGHT: u32 = 376;
|
||||||
|
|
||||||
|
pub(crate) fn normalize_specs_mode(mode: &str) -> &'static str {
|
||||||
|
match mode {
|
||||||
|
"meters" => "meters",
|
||||||
|
_ => "cards",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn collect_system_view() -> SystemView {
|
pub(crate) fn collect_system_view() -> SystemView {
|
||||||
fn mount_usage(disks: &Disks, mount_point: &str) -> (String, String, String) {
|
fn mount_usage(disks: &Disks, mount_point: &str) -> (String, String, String) {
|
||||||
disks
|
disks
|
||||||
@@ -140,7 +147,14 @@ fn format_bytes(bytes: u64) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render_system_panel() -> RgbImage {
|
pub(crate) fn render_system_panel(mode: &str) -> RgbImage {
|
||||||
|
match normalize_specs_mode(mode) {
|
||||||
|
"meters" => render_system_panel_meters(),
|
||||||
|
_ => render_system_panel_cards(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_system_panel_cards() -> RgbImage {
|
||||||
let system = collect_system_view();
|
let system = collect_system_view();
|
||||||
let mut canvas = RgbImage::from_pixel(DISPLAY_WIDTH, DISPLAY_HEIGHT, Rgb([10, 14, 18]));
|
let mut canvas = RgbImage::from_pixel(DISPLAY_WIDTH, DISPLAY_HEIGHT, Rgb([10, 14, 18]));
|
||||||
let host_name = System::host_name().unwrap_or_else(|| "Unraid".into());
|
let host_name = System::host_name().unwrap_or_else(|| "Unraid".into());
|
||||||
@@ -229,7 +243,10 @@ pub(crate) fn render_system_panel() -> RgbImage {
|
|||||||
236,
|
236,
|
||||||
"System",
|
"System",
|
||||||
system.uptime.clone(),
|
system.uptime.clone(),
|
||||||
format!("{} CPU / {} proc", system.cpu_count, system.process_count),
|
format!(
|
||||||
|
"{} cores | {} procs",
|
||||||
|
system.cpu_count, system.process_count
|
||||||
|
),
|
||||||
Rgb([216, 253, 114]),
|
Rgb([216, 253, 114]),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -241,6 +258,109 @@ pub(crate) fn render_system_panel() -> RgbImage {
|
|||||||
canvas
|
canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_system_panel_meters() -> RgbImage {
|
||||||
|
let system = collect_system_view();
|
||||||
|
let mut canvas = RgbImage::from_pixel(DISPLAY_WIDTH, DISPLAY_HEIGHT, Rgb([9, 13, 17]));
|
||||||
|
let host_name = System::host_name().unwrap_or_else(|| "Unraid".into());
|
||||||
|
let timestamp = Local::now().format("%d.%m.%Y %H:%M:%S").to_string();
|
||||||
|
|
||||||
|
draw_filled_rect_mut(
|
||||||
|
&mut canvas,
|
||||||
|
Rect::at(0, 0).of_size(DISPLAY_WIDTH, 72),
|
||||||
|
Rgb([15, 23, 30]),
|
||||||
|
);
|
||||||
|
draw_text_line(
|
||||||
|
&mut canvas,
|
||||||
|
Rgb([216, 253, 114]),
|
||||||
|
26,
|
||||||
|
18,
|
||||||
|
32.0,
|
||||||
|
"AOOSTAR System Monitor",
|
||||||
|
);
|
||||||
|
draw_text_line(
|
||||||
|
&mut canvas,
|
||||||
|
Rgb([146, 163, 181]),
|
||||||
|
28,
|
||||||
|
48,
|
||||||
|
18.0,
|
||||||
|
&format!("{host_name} | {timestamp}"),
|
||||||
|
);
|
||||||
|
|
||||||
|
draw_meter_row(
|
||||||
|
&mut canvas,
|
||||||
|
26,
|
||||||
|
102,
|
||||||
|
"CPU",
|
||||||
|
&format!("{}%", system.cpu_usage_percent),
|
||||||
|
parse_metric_percent(&system.cpu_usage_percent),
|
||||||
|
Rgb([110, 231, 183]),
|
||||||
|
&format!(
|
||||||
|
"Load {} / {} / {} {} cores {} procs",
|
||||||
|
system.load_avg_one,
|
||||||
|
system.load_avg_five,
|
||||||
|
system.load_avg_fifteen,
|
||||||
|
system.cpu_count,
|
||||||
|
system.process_count
|
||||||
|
),
|
||||||
|
);
|
||||||
|
draw_meter_row(
|
||||||
|
&mut canvas,
|
||||||
|
26,
|
||||||
|
170,
|
||||||
|
"RAM",
|
||||||
|
&format!("{}%", system.mem_usage_percent),
|
||||||
|
parse_metric_percent(&system.mem_usage_percent),
|
||||||
|
Rgb([120, 189, 247]),
|
||||||
|
&format!("{} / {}", system.mem_used, system.mem_total),
|
||||||
|
);
|
||||||
|
draw_meter_row(
|
||||||
|
&mut canvas,
|
||||||
|
26,
|
||||||
|
238,
|
||||||
|
"GPU",
|
||||||
|
system.temperature_gpu.as_deref().unwrap_or("-"),
|
||||||
|
normalize_temperature_bar(system.temperature_gpu.as_deref()),
|
||||||
|
Rgb([241, 132, 121]),
|
||||||
|
&format!(
|
||||||
|
"Temp meter CPU {} Cache {}% User {}%",
|
||||||
|
system.temperature_cpu.as_deref().unwrap_or("-"),
|
||||||
|
system.cache_usage_percent,
|
||||||
|
system.user_usage_percent
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
draw_filled_rect_mut(
|
||||||
|
&mut canvas,
|
||||||
|
Rect::at(26, 308).of_size(908, 42),
|
||||||
|
Rgb([12, 18, 24]),
|
||||||
|
);
|
||||||
|
draw_hollow_rect_mut(
|
||||||
|
&mut canvas,
|
||||||
|
Rect::at(26, 308).of_size(908, 42),
|
||||||
|
Rgb([31, 44, 58]),
|
||||||
|
);
|
||||||
|
draw_text_line(
|
||||||
|
&mut canvas,
|
||||||
|
Rgb([146, 163, 181]),
|
||||||
|
42,
|
||||||
|
321,
|
||||||
|
18.0,
|
||||||
|
&format!(
|
||||||
|
"Swap {}% {} / {} Cache {} / {} User {} / {} Uptime {}",
|
||||||
|
system.swap_usage_percent,
|
||||||
|
system.swap_used,
|
||||||
|
system.swap_total,
|
||||||
|
system.cache_used,
|
||||||
|
system.cache_total,
|
||||||
|
system.user_used,
|
||||||
|
system.user_total,
|
||||||
|
system.uptime
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn overlay_system_specs(canvas: &mut RgbImage) {
|
pub(crate) fn overlay_system_specs(canvas: &mut RgbImage) {
|
||||||
let system = collect_system_view();
|
let system = collect_system_view();
|
||||||
let host_name = System::host_name().unwrap_or_else(|| "Unraid".into());
|
let host_name = System::host_name().unwrap_or_else(|| "Unraid".into());
|
||||||
@@ -309,6 +429,69 @@ pub(crate) fn overlay_system_specs(canvas: &mut RgbImage) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_meter_row(
|
||||||
|
canvas: &mut RgbImage,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
label: &str,
|
||||||
|
value: &str,
|
||||||
|
percent: f32,
|
||||||
|
accent: Rgb<u8>,
|
||||||
|
meta: &str,
|
||||||
|
) {
|
||||||
|
draw_text_line(canvas, accent, x, y, 19.0, label);
|
||||||
|
draw_text_line(canvas, Rgb([237, 242, 247]), x + 112, y, 19.0, value);
|
||||||
|
draw_meter_bar(canvas, x, y + 28, 840, 20, percent, accent);
|
||||||
|
draw_text_line(canvas, Rgb([146, 163, 181]), x, y + 56, 17.0, meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_meter_bar(
|
||||||
|
canvas: &mut RgbImage,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
percent: f32,
|
||||||
|
accent: Rgb<u8>,
|
||||||
|
) {
|
||||||
|
draw_filled_rect_mut(
|
||||||
|
canvas,
|
||||||
|
Rect::at(x, y).of_size(width, height),
|
||||||
|
Rgb([19, 27, 35]),
|
||||||
|
);
|
||||||
|
draw_hollow_rect_mut(
|
||||||
|
canvas,
|
||||||
|
Rect::at(x, y).of_size(width, height),
|
||||||
|
Rgb([36, 50, 67]),
|
||||||
|
);
|
||||||
|
let fill_width =
|
||||||
|
((width.saturating_sub(2)) as f32 * (percent.clamp(0.0, 100.0) / 100.0)).round() as u32;
|
||||||
|
if fill_width > 0 {
|
||||||
|
draw_filled_rect_mut(
|
||||||
|
canvas,
|
||||||
|
Rect::at(x + 1, y + 1).of_size(fill_width, height.saturating_sub(2)),
|
||||||
|
accent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_metric_percent(value: &str) -> f32 {
|
||||||
|
value.parse::<f32>().unwrap_or(0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_temperature_bar(value: Option<&str>) -> f32 {
|
||||||
|
let Some(raw) = value else {
|
||||||
|
return 0.0;
|
||||||
|
};
|
||||||
|
let numeric = raw
|
||||||
|
.replace(" °C", "")
|
||||||
|
.replace('C', "")
|
||||||
|
.trim()
|
||||||
|
.parse::<f32>()
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
(numeric / 100.0 * 100.0).clamp(0.0, 100.0)
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_metric_card(
|
fn draw_metric_card(
|
||||||
canvas: &mut RgbImage,
|
canvas: &mut RgbImage,
|
||||||
x: i32,
|
x: i32,
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ pub(crate) struct RotationSnapshot {
|
|||||||
pub(crate) custom_panel: bool,
|
pub(crate) custom_panel: bool,
|
||||||
pub(crate) switch_time: u32,
|
pub(crate) switch_time: u32,
|
||||||
pub(crate) specs_enabled: bool,
|
pub(crate) specs_enabled: bool,
|
||||||
|
pub(crate) specs_mode: String,
|
||||||
pub(crate) memes_enabled: bool,
|
pub(crate) memes_enabled: bool,
|
||||||
pub(crate) gifs_enabled: bool,
|
pub(crate) gifs_enabled: bool,
|
||||||
pub(crate) active_images: Vec<String>,
|
pub(crate) active_images: Vec<String>,
|
||||||
@@ -107,6 +108,7 @@ pub(crate) struct SetupView {
|
|||||||
pub(crate) custom_panel: bool,
|
pub(crate) custom_panel: bool,
|
||||||
pub(crate) switch_time: String,
|
pub(crate) switch_time: String,
|
||||||
pub(crate) specs_enabled: bool,
|
pub(crate) specs_enabled: bool,
|
||||||
|
pub(crate) specs_mode: String,
|
||||||
pub(crate) memes_enabled: bool,
|
pub(crate) memes_enabled: bool,
|
||||||
pub(crate) gifs_enabled: bool,
|
pub(crate) gifs_enabled: bool,
|
||||||
}
|
}
|
||||||
@@ -173,6 +175,7 @@ pub(crate) struct ActivateRequest {
|
|||||||
pub(crate) images: Vec<String>,
|
pub(crate) images: Vec<String>,
|
||||||
pub(crate) switch_time: Option<u32>,
|
pub(crate) switch_time: Option<u32>,
|
||||||
pub(crate) specs_enabled: Option<bool>,
|
pub(crate) specs_enabled: Option<bool>,
|
||||||
|
pub(crate) specs_mode: Option<String>,
|
||||||
pub(crate) memes_enabled: Option<bool>,
|
pub(crate) memes_enabled: Option<bool>,
|
||||||
pub(crate) gifs_enabled: Option<bool>,
|
pub(crate) gifs_enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
--surface: #10161d;
|
--surface: #10161d;
|
||||||
--surface-2: #141b24;
|
--surface-2: #141b24;
|
||||||
--surface-3: #0c1117;
|
--surface-3: #0c1117;
|
||||||
|
--surface-4: #172230;
|
||||||
--line: #223042;
|
--line: #223042;
|
||||||
--line-soft: #182333;
|
--line-soft: #182333;
|
||||||
--text: #eef3f8;
|
--text: #eef3f8;
|
||||||
@@ -37,6 +38,7 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
--faint: #667789;
|
--faint: #667789;
|
||||||
--accent: #6ee7b7;
|
--accent: #6ee7b7;
|
||||||
--accent-2: #b9f66a;
|
--accent-2: #b9f66a;
|
||||||
|
--accent-3: #7ec8ff;
|
||||||
--warn: #f6c65b;
|
--warn: #f6c65b;
|
||||||
--danger: #f18479;
|
--danger: #f18479;
|
||||||
--shadow: 0 18px 60px rgba(0, 0, 0, .28);
|
--shadow: 0 18px 60px rgba(0, 0, 0, .28);
|
||||||
@@ -130,6 +132,8 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid rgba(34, 48, 66, .72);
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -192,6 +196,15 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
.select-wrap select {
|
||||||
|
min-height: 42px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: var(--surface-3);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 10px 12px;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
.toggle-pill {
|
.toggle-pill {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -221,6 +234,16 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
display: grid;
|
display: grid;
|
||||||
align-content: space-between;
|
align-content: space-between;
|
||||||
background: rgba(16, 22, 30, .86);
|
background: rgba(16, 22, 30, .86);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.status::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 auto 0 0;
|
||||||
|
width: 3px;
|
||||||
|
background: linear-gradient(180deg, var(--accent), var(--accent-3));
|
||||||
|
opacity: .9;
|
||||||
}
|
}
|
||||||
.status span, .metric-label {
|
.status span, .metric-label {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -244,6 +267,7 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
.metric-card {
|
.metric-card {
|
||||||
min-height: 104px;
|
min-height: 104px;
|
||||||
border-left: 3px solid var(--accent);
|
border-left: 3px solid var(--accent);
|
||||||
|
background: linear-gradient(180deg, rgba(17, 24, 32, .98), rgba(12, 17, 24, .98));
|
||||||
}
|
}
|
||||||
.metric-card:nth-child(2) { border-left-color: #78bdf7; }
|
.metric-card:nth-child(2) { border-left-color: #78bdf7; }
|
||||||
.metric-card:nth-child(3) { border-left-color: var(--warn); }
|
.metric-card:nth-child(3) { border-left-color: var(--warn); }
|
||||||
@@ -376,13 +400,31 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
}
|
}
|
||||||
.nyan-wrap {
|
.nyan-wrap {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 14px;
|
right: 18px;
|
||||||
bottom: 10px;
|
bottom: 18px;
|
||||||
width: 132px;
|
width: 184px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
opacity: .42;
|
opacity: .92;
|
||||||
filter: drop-shadow(0 8px 14px rgba(0, 0, 0, .26));
|
padding: 10px 12px 8px;
|
||||||
|
border: 1px solid rgba(126, 200, 255, .22);
|
||||||
|
border-radius: 8px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(15, 22, 30, .88), rgba(10, 15, 22, .72));
|
||||||
|
box-shadow:
|
||||||
|
0 16px 36px rgba(0, 0, 0, .28),
|
||||||
|
inset 0 0 0 1px rgba(110, 231, 183, .08);
|
||||||
|
filter: drop-shadow(0 10px 18px rgba(0, 0, 0, .22));
|
||||||
|
}
|
||||||
|
.nyan-wrap::before {
|
||||||
|
content: "UI mascot";
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: .68rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .08em;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.nyan-wrap img {
|
.nyan-wrap img {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -406,7 +448,8 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
.gallery { grid-template-columns: 1fr; }
|
.gallery { grid-template-columns: 1fr; }
|
||||||
.order-item { grid-template-columns: 28px minmax(0, 1fr); }
|
.order-item { grid-template-columns: 28px minmax(0, 1fr); }
|
||||||
.order-item .mini { grid-column: 2; }
|
.order-item .mini { grid-column: 2; }
|
||||||
.nyan-wrap { width: 96px; opacity: .32; }
|
.nyan-wrap { width: 128px; right: 10px; bottom: 10px; opacity: .78; padding: 8px; }
|
||||||
|
.nyan-wrap::before { margin-bottom: 4px; font-size: .62rem; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -452,12 +495,21 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
<button id="disable" class="danger" type="button">Disable</button>
|
<button id="disable" class="danger" type="button">Disable</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toggle-row">
|
<div class="toggle-row">
|
||||||
<label class="toggle-pill"><input id="specsEnabled" type="checkbox"> System Specs</label>
|
<label class="toggle-pill"><input id="specsEnabled" type="checkbox"> System Specs</label>
|
||||||
<label class="toggle-pill"><input id="memesEnabled" type="checkbox"> Memes</label>
|
<label class="toggle-pill"><input id="memesEnabled" type="checkbox"> Memes</label>
|
||||||
<label class="toggle-pill"><input id="gifsEnabled" type="checkbox"> GIFs</label>
|
<label class="toggle-pill"><input id="gifsEnabled" type="checkbox"> GIFs</label>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="toggle-row">
|
||||||
|
<label class="select-wrap">
|
||||||
|
<span>Screen Layout</span>
|
||||||
|
<select id="specsMode">
|
||||||
|
<option value="cards">Cards</option>
|
||||||
|
<option value="meters">Meters</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="status-grid">
|
<aside class="status-grid">
|
||||||
@@ -521,7 +573,7 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
const state = {
|
const state = {
|
||||||
images: [],
|
images: [],
|
||||||
activeImages: [],
|
activeImages: [],
|
||||||
setup: { switch_time: "10", custom_panel: false, specs_enabled: false, memes_enabled: false, gifs_enabled: true },
|
setup: { switch_time: "10", custom_panel: false, specs_enabled: false, specs_mode: "cards", memes_enabled: false, gifs_enabled: true },
|
||||||
display: {},
|
display: {},
|
||||||
system: {},
|
system: {},
|
||||||
activeTab: "screen-control",
|
activeTab: "screen-control",
|
||||||
@@ -534,6 +586,7 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
tabPanels: Array.from(document.querySelectorAll("[data-tab-panel]")),
|
tabPanels: Array.from(document.querySelectorAll("[data-tab-panel]")),
|
||||||
switchTime: document.getElementById("switchTime"),
|
switchTime: document.getElementById("switchTime"),
|
||||||
specsEnabled: document.getElementById("specsEnabled"),
|
specsEnabled: document.getElementById("specsEnabled"),
|
||||||
|
specsMode: document.getElementById("specsMode"),
|
||||||
memesEnabled: document.getElementById("memesEnabled"),
|
memesEnabled: document.getElementById("memesEnabled"),
|
||||||
gifsEnabled: document.getElementById("gifsEnabled"),
|
gifsEnabled: document.getElementById("gifsEnabled"),
|
||||||
customPanelValue: document.getElementById("customPanelValue"),
|
customPanelValue: document.getElementById("customPanelValue"),
|
||||||
@@ -677,6 +730,7 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
...data.setup,
|
...data.setup,
|
||||||
switch_time: el.switchTime.value || data.setup.switch_time || "10",
|
switch_time: el.switchTime.value || data.setup.switch_time || "10",
|
||||||
specs_enabled: el.specsEnabled.checked,
|
specs_enabled: el.specsEnabled.checked,
|
||||||
|
specs_mode: el.specsMode.value || data.setup.specs_mode || "cards",
|
||||||
memes_enabled: el.memesEnabled.checked,
|
memes_enabled: el.memesEnabled.checked,
|
||||||
gifs_enabled: el.gifsEnabled.checked,
|
gifs_enabled: el.gifsEnabled.checked,
|
||||||
}
|
}
|
||||||
@@ -686,6 +740,7 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
if (!preserveDraft) {
|
if (!preserveDraft) {
|
||||||
el.switchTime.value = data.setup.switch_time || "10";
|
el.switchTime.value = data.setup.switch_time || "10";
|
||||||
el.specsEnabled.checked = !!data.setup.specs_enabled;
|
el.specsEnabled.checked = !!data.setup.specs_enabled;
|
||||||
|
el.specsMode.value = data.setup.specs_mode || "cards";
|
||||||
el.memesEnabled.checked = !!data.setup.memes_enabled;
|
el.memesEnabled.checked = !!data.setup.memes_enabled;
|
||||||
el.gifsEnabled.checked = data.setup.gifs_enabled !== false;
|
el.gifsEnabled.checked = data.setup.gifs_enabled !== false;
|
||||||
}
|
}
|
||||||
@@ -712,8 +767,8 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
el.diskMetaValue.textContent = `${state.system.cache_used || "-"} / ${state.system.cache_total || "-"} | ${state.system.user_used || "-"} / ${state.system.user_total || "-"}`;
|
el.diskMetaValue.textContent = `${state.system.cache_used || "-"} / ${state.system.cache_total || "-"} | ${state.system.user_used || "-"} / ${state.system.user_total || "-"}`;
|
||||||
el.swapUsageValue.textContent = `${state.system.swap_usage_percent || "-"} %`;
|
el.swapUsageValue.textContent = `${state.system.swap_usage_percent || "-"} %`;
|
||||||
el.swapMetaValue.textContent = `${state.system.swap_used || "-"} / ${state.system.swap_total || "-"}`;
|
el.swapMetaValue.textContent = `${state.system.swap_used || "-"} / ${state.system.swap_total || "-"}`;
|
||||||
el.loadValue.textContent = `${state.system.cpu_count || "-"} CPU / ${state.system.process_count || "-"} proc`;
|
el.loadValue.textContent = `${state.system.load_avg_one || "-"} / ${state.system.load_avg_five || "-"} / ${state.system.load_avg_fifteen || "-"}`;
|
||||||
el.uptimeValue.textContent = `Uptime ${state.system.uptime || "-"}`;
|
el.uptimeValue.textContent = `${state.system.cpu_count || "-"} cores • ${state.system.process_count || "-"} procs • Uptime ${state.system.uptime || "-"}`;
|
||||||
el.tempCpuValue.textContent = `CPU ${state.system.temperature_cpu || "-"}`;
|
el.tempCpuValue.textContent = `CPU ${state.system.temperature_cpu || "-"}`;
|
||||||
el.tempGpuValue.textContent = `GPU ${state.system.temperature_gpu || "-"}`;
|
el.tempGpuValue.textContent = `GPU ${state.system.temperature_gpu || "-"}`;
|
||||||
el.displayStatusMirror.textContent = el.displayStatusValue.textContent;
|
el.displayStatusMirror.textContent = el.displayStatusValue.textContent;
|
||||||
@@ -745,6 +800,7 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
images: state.activeImages,
|
images: state.activeImages,
|
||||||
switch_time: Number.parseInt(el.switchTime.value || "10", 10),
|
switch_time: Number.parseInt(el.switchTime.value || "10", 10),
|
||||||
specs_enabled: el.specsEnabled.checked,
|
specs_enabled: el.specsEnabled.checked,
|
||||||
|
specs_mode: el.specsMode.value,
|
||||||
memes_enabled: el.memesEnabled.checked,
|
memes_enabled: el.memesEnabled.checked,
|
||||||
gifs_enabled: el.gifsEnabled.checked,
|
gifs_enabled: el.gifsEnabled.checked,
|
||||||
}),
|
}),
|
||||||
@@ -869,6 +925,7 @@ const INDEX_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
});
|
});
|
||||||
el.switchTime.addEventListener("change", markDirty);
|
el.switchTime.addEventListener("change", markDirty);
|
||||||
el.specsEnabled.addEventListener("change", markDirty);
|
el.specsEnabled.addEventListener("change", markDirty);
|
||||||
|
el.specsMode.addEventListener("change", markDirty);
|
||||||
el.memesEnabled.addEventListener("change", markDirty);
|
el.memesEnabled.addEventListener("change", markDirty);
|
||||||
el.gifsEnabled.addEventListener("change", markDirty);
|
el.gifsEnabled.addEventListener("change", markDirty);
|
||||||
el.tabButtons.forEach((button) => {
|
el.tabButtons.forEach((button) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user