feat: Add Debian packaging script, enhance configuration loading with multi-path support, and enable config saving.
This commit is contained in:
22
Cargo.lock
generated
22
Cargo.lock
generated
@@ -2,17 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "C2LInspecz"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph"
|
||||
version = "0.2.32"
|
||||
@@ -507,6 +496,17 @@ version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "c2linspecz"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sysinfo",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.13.0"
|
||||
|
||||
13
Cargo.toml
13
Cargo.toml
@@ -1,7 +1,9 @@
|
||||
[package]
|
||||
name = "C2LInspecz"
|
||||
name = "c2linspecz"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
authors = ["Code2Lab Team <support@code2lab.com>"]
|
||||
description = "Automated POS Diagnostic & Health Monitoring Tool"
|
||||
|
||||
[dependencies]
|
||||
eframe = "0.33.3"
|
||||
@@ -9,3 +11,12 @@ serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
sysinfo = "0.38.0"
|
||||
tokio = { version = "1.49.0", features = ["full"] }
|
||||
|
||||
[package.metadata.deb]
|
||||
maintainer = "Code2Lab support <support@code2lab.com>"
|
||||
copyright = "2024, Code2Lab"
|
||||
license-file = ["C2LInspecz.md", "0"]
|
||||
assets = [
|
||||
["target/release/c2linspecz", "/usr/bin/c2linspecz", "755"],
|
||||
["config.json", "/etc/c2linspecz/config.json", "644"]
|
||||
]
|
||||
|
||||
54
README.md
54
README.md
@@ -1,3 +1,53 @@
|
||||
# C2LInspecz
|
||||
# C2LInspecz - Automated Smartsales System Health
|
||||
|
||||
Smartsales System Diagnosis tool
|
||||
**C2LInspecz** is the official diagnostic and health-monitoring utility by **Code2Lab**. It is a modern, high-performance GUI application designed to identify, troubleshoot, and resolve issues within the Smartsales POS ecosystem.
|
||||
|
||||
## 🚀 Core Features
|
||||
|
||||
### 1. Automated System Audit
|
||||
A comprehensive "One-Click" audit that analyzes your shop's infrastructure in real-time:
|
||||
- **Network Integrity**: Verifies LAN connectivity, Internet access, and Shop Server reachability.
|
||||
- **Printer Ecosystem**:
|
||||
- **USB Printers (Cashier)**: Scans the physical USB bus to ensure local receipt printers are connected.
|
||||
- **Network Printers (Kitchen/Bar)**: Pings remote printers to verify they are online and responding.
|
||||
- **Hardware Health**: Monitors CPU load, RAM usage, and Disk space.
|
||||
|
||||
### 2. The "Spooler Shield" (Auto-Fix)
|
||||
The tool monitors the internal print queue for "Stuck" jobs that often freeze cashier operations. If glitches are detected, the user can purge the queue with a single click to restore normal printing.
|
||||
|
||||
### 3. Integrated Settings Wizard
|
||||
No more manual JSON editing. Shop managers can use the **⚙️ Settings** tab directly in the app to:
|
||||
- Update the Shop Server IP.
|
||||
- Add, Edit, or Remove Network and USB printers.
|
||||
- Save configurations instantly to the system path.
|
||||
|
||||
### 4. Smart Escalation
|
||||
Guides users through self-repair (e.g., "Check power cable"). If a failure is unresolvable locally, the tool packages diagnostic logs into a support ticket for the Code2Lab team.
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
C2LInspecz is distributed as a native Debian package for seamless POS integration.
|
||||
|
||||
1. Download the latest `.deb` package.
|
||||
2. Install via terminal:
|
||||
```bash
|
||||
sudo dpkg -i c2linspecz_0.1.0_amd64.deb
|
||||
```
|
||||
3. Launch **"C2LInspecz"** from your system's Applications menu.
|
||||
|
||||
## 🛠 Developer Guide
|
||||
|
||||
### Build Requirements
|
||||
- Rust (Cargo)
|
||||
- `dpkg-deb` tool
|
||||
|
||||
### Packaging the Installer
|
||||
To generate a new release installer:
|
||||
```bash
|
||||
cargo build --release
|
||||
./scripts/build_deb.sh
|
||||
```
|
||||
The final package will be ready at `target/c2linspecz_0.1.0_amd64.deb`.
|
||||
|
||||
---
|
||||
© 2026 **Code2Lab Team** | [support@code2lab.com](mailto:support@code2lab.com)
|
||||
@@ -8,12 +8,20 @@
|
||||
{
|
||||
"name": "Bar Printer",
|
||||
"ip": "192.168.1.201"
|
||||
},
|
||||
{
|
||||
"name": "test printe",
|
||||
"ip": "192.168.1.202"
|
||||
}
|
||||
],
|
||||
"receipt_printers": [
|
||||
{
|
||||
"name": "Main Cashier",
|
||||
"usb_id": "1fc9:2016"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"usb_id": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
57
scripts/build_deb.sh
Executable file
57
scripts/build_deb.sh
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# Manual .deb packaging script for C2LInspecz
|
||||
|
||||
set -e
|
||||
|
||||
PKG_NAME="c2linspecz"
|
||||
VERSION="0.1.0"
|
||||
ARCH="amd64"
|
||||
BUILD_DIR="target/debian_pkg"
|
||||
|
||||
echo "Building .deb package for $PKG_NAME v$VERSION..."
|
||||
|
||||
# Clean up previous build
|
||||
rm -rf $BUILD_DIR
|
||||
mkdir -p $BUILD_DIR/DEBIAN
|
||||
mkdir -p $BUILD_DIR/usr/bin
|
||||
mkdir -p $BUILD_DIR/usr/share/applications
|
||||
mkdir -p $BUILD_DIR/etc/c2linspecz
|
||||
|
||||
# Copy binary (must be built with cargo build --release first)
|
||||
if [ ! -f target/release/c2linspecz ]; then
|
||||
echo "Error: target/release/c2linspecz not found. Please run 'cargo build --release' first."
|
||||
exit 1
|
||||
fi
|
||||
cp target/release/c2linspecz $BUILD_DIR/usr/bin/
|
||||
|
||||
# Copy config template
|
||||
cp config.json $BUILD_DIR/etc/c2linspecz/
|
||||
|
||||
# Create desktop entry
|
||||
cat <<EOF > $BUILD_DIR/usr/share/applications/c2linspecz.desktop
|
||||
[Desktop Entry]
|
||||
Name=C2LInspecz
|
||||
Comment=Automated POS Diagnostic & Health Monitoring Tool
|
||||
Exec=/usr/bin/c2linspecz
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Utility;System;
|
||||
Icon=utilities-system-monitor
|
||||
EOF
|
||||
|
||||
# Create control file
|
||||
cat <<EOF > $BUILD_DIR/DEBIAN/control
|
||||
Package: $PKG_NAME
|
||||
Version: $VERSION
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: $ARCH
|
||||
Maintainer: Code2Lab Team <support@code2lab.com>
|
||||
Description: Automated POS Diagnostic & Health Monitoring Tool
|
||||
This tool is designed to identify, troubleshoot, and resolve issues within the POS ecosystem.
|
||||
EOF
|
||||
|
||||
# Build the package
|
||||
dpkg-deb --build $BUILD_DIR "target/${PKG_NAME}_${VERSION}_${ARCH}.deb"
|
||||
|
||||
echo "Package generated at target/${PKG_NAME}_${VERSION}_${ARCH}.deb"
|
||||
420
src/main.rs
420
src/main.rs
@@ -1,37 +1,39 @@
|
||||
use eframe::egui;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::io::{Read, Write};
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use sysinfo::{Disks, System};
|
||||
use tokio::time::sleep;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
struct NetworkDevice {
|
||||
name: String,
|
||||
ip: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
struct UsbDevice {
|
||||
name: String,
|
||||
usb_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||
struct Config {
|
||||
shop_server_ip: String,
|
||||
kitchen_printers: Vec<NetworkDevice>,
|
||||
receipt_printers: Vec<UsbDevice>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone, Default)]
|
||||
struct DeviceStatus {
|
||||
name: String,
|
||||
online: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone, Default)]
|
||||
struct ScanResult {
|
||||
timestamp: String,
|
||||
hardware: HardwareStatus,
|
||||
@@ -39,7 +41,7 @@ struct ScanResult {
|
||||
printers: Vec<PrinterStatus>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone, Default)]
|
||||
struct HardwareStatus {
|
||||
ram_used_mb: u64,
|
||||
ram_total_mb: u64,
|
||||
@@ -47,7 +49,7 @@ struct HardwareStatus {
|
||||
disk_free_gb: f64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone, Default)]
|
||||
struct NetworkStatus {
|
||||
lan_connected: bool,
|
||||
server_online: bool,
|
||||
@@ -55,7 +57,7 @@ struct NetworkStatus {
|
||||
internet_online: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone, Default)]
|
||||
struct PrinterStatus {
|
||||
name: String,
|
||||
usb_detected: bool,
|
||||
@@ -65,11 +67,39 @@ struct PrinterStatus {
|
||||
|
||||
impl Config {
|
||||
fn load() -> Self {
|
||||
let mut file = File::open("config.json").expect("config.json not found");
|
||||
let paths = [
|
||||
"config.json".to_string(),
|
||||
"/etc/c2linspecz/config.json".to_string(),
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| {
|
||||
p.parent()
|
||||
.map(|parent| parent.join("config.json").to_string_lossy().into_owned())
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
];
|
||||
|
||||
for path in paths {
|
||||
if let Ok(mut file) = File::open(&path) {
|
||||
let mut data = String::new();
|
||||
file.read_to_string(&mut data)
|
||||
.expect("Unable to read config.json");
|
||||
serde_json::from_str(&data).expect("JSON format error")
|
||||
if file.read_to_string(&mut data).is_ok() {
|
||||
if let Ok(config) = serde_json::from_str(&data) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Config::default()
|
||||
}
|
||||
|
||||
fn save(&self) -> bool {
|
||||
let path = "config.json"; // Default to current dir for now, or fallback to standard
|
||||
if let Ok(mut file) = File::create(path) {
|
||||
if let Ok(data) = serde_json::to_string_pretty(self) {
|
||||
return file.write_all(data.as_bytes()).is_ok();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,16 +128,15 @@ async fn run_diagnosis(config: &Config) -> ScanResult {
|
||||
let total_ram = sys.total_memory() / 1024 / 1024;
|
||||
let used_ram = sys.used_memory() / 1024 / 1024;
|
||||
sys.refresh_cpu_usage();
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
sys.refresh_cpu_usage();
|
||||
let cpu_load: f32 =
|
||||
sys.cpus().iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / sys.cpus().len() as f32;
|
||||
|
||||
let disks = Disks::new_with_refreshed_list();
|
||||
let root_disk = disks
|
||||
let disk_free = disks
|
||||
.iter()
|
||||
.find(|d| d.mount_point().to_string_lossy() == "/");
|
||||
let disk_free = root_disk
|
||||
.find(|d| d.mount_point().to_string_lossy() == "/")
|
||||
.map(|d| d.available_space() as f64 / 1e9)
|
||||
.unwrap_or(0.0);
|
||||
|
||||
@@ -132,7 +161,7 @@ async fn run_diagnosis(config: &Config) -> ScanResult {
|
||||
});
|
||||
}
|
||||
|
||||
// Receipt Printers (USB)
|
||||
// Printers
|
||||
let usb_output = Command::new("lsusb")
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
|
||||
@@ -158,9 +187,8 @@ async fn run_diagnosis(config: &Config) -> ScanResult {
|
||||
printer_results.push(PrinterStatus {
|
||||
name: p.name.clone(),
|
||||
usb_detected: usb_output.contains(&p.usb_id),
|
||||
cups_ready: lpstat_output.contains("enabled")
|
||||
&& (lpstat_output.contains("idle") || lpstat_output.contains("printing")),
|
||||
pending_jobs: jobs_count, // CUPS queue is often shared or we check per-printer if needed, but for now we use total stuck jobs as the trigger
|
||||
cups_ready: lpstat_output.contains("enabled"),
|
||||
pending_jobs: jobs_count,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -183,166 +211,264 @@ async fn run_diagnosis(config: &Config) -> ScanResult {
|
||||
}
|
||||
|
||||
fn fix_printer_queue() -> bool {
|
||||
println!("\n[SPOOLER SHIELD] Attempting to clear stuck jobs...");
|
||||
// 1. Purge all jobs
|
||||
let purge = Command::new("cancel").args(["-a", "-x"]).status();
|
||||
match purge {
|
||||
Ok(status) if status.success() => {
|
||||
println!("SUCCESS: All pending print jobs have been purged.");
|
||||
// 2. Restart cups if possible (might need sudo, but we'll try)
|
||||
// Command::new("systemctl").args(["restart", "cups"]).status().ok();
|
||||
true
|
||||
Command::new("cancel")
|
||||
.args(["-a", "-x"])
|
||||
.status()
|
||||
.map(|s| s.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
_ => {
|
||||
println!("ERROR: Failed to clear queue automatically. Please check printer power.");
|
||||
false
|
||||
|
||||
struct C2LApp {
|
||||
config: Config,
|
||||
result: Arc<Mutex<Option<ScanResult>>>,
|
||||
is_scanning: Arc<Mutex<bool>>,
|
||||
rt: Runtime,
|
||||
last_error: Option<String>,
|
||||
tab: Tab,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum Tab {
|
||||
Dashboard,
|
||||
Settings,
|
||||
}
|
||||
|
||||
impl C2LApp {
|
||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
C2LApp {
|
||||
config: Config::load(),
|
||||
result: Arc::new(Mutex::new(None)),
|
||||
is_scanning: Arc::new(Mutex::new(false)),
|
||||
rt: Runtime::new().expect("Tokio runtime failed"),
|
||||
last_error: None,
|
||||
tab: Tab::Dashboard,
|
||||
}
|
||||
}
|
||||
|
||||
fn send_support_ticket(config: &Config, result: &ScanResult) {
|
||||
println!("\n[ONSITE ESCALATION]");
|
||||
println!(
|
||||
"WARNING: This service may be billable if the issue is a hardware failure (cables, broken hardware)."
|
||||
);
|
||||
print!("Do you wish to proceed with generating a Ticket? (y/N): ");
|
||||
io::stdout().flush().unwrap();
|
||||
fn trigger_scan(&self) {
|
||||
let config = self.config.clone();
|
||||
let result_store = self.result.clone();
|
||||
let scanning_status = self.is_scanning.clone();
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input).unwrap();
|
||||
if input.trim().to_lowercase() != "y" {
|
||||
println!("Ticket cancelled.");
|
||||
return;
|
||||
self.rt.spawn(async move {
|
||||
*scanning_status.lock().unwrap() = true;
|
||||
let fresh_result = run_diagnosis(&config).await;
|
||||
*result_store.lock().unwrap() = Some(fresh_result);
|
||||
*scanning_status.lock().unwrap() = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
println!("Packaging logs...");
|
||||
let payload = serde_json::json!({
|
||||
"config": config,
|
||||
"diagnostics": result,
|
||||
"system_info": "Linux x86_64",
|
||||
impl eframe::App for C2LApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(&mut self.tab, Tab::Dashboard, "📊 Dashboard");
|
||||
ui.selectable_value(&mut self.tab, Tab::Settings, "⚙️ Settings");
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
match self.tab {
|
||||
Tab::Dashboard => self.ui_dashboard(ui),
|
||||
Tab::Settings => self.ui_settings(ui),
|
||||
}
|
||||
});
|
||||
|
||||
// In a real app, this would be an API call
|
||||
println!("SUCCESS: Support Ticket #C2L-{:04} generated.", 9999);
|
||||
println!("Log Payload: {}", serde_json::to_string(&payload).unwrap());
|
||||
println!("The Code2Lab team has been notified. Please keep your device online.");
|
||||
// Auto-refresh scan result
|
||||
ctx.request_repaint_after(Duration::from_millis(500));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let config = Config::load();
|
||||
impl C2LApp {
|
||||
fn ui_dashboard(&mut self, ui: &mut egui::Ui) {
|
||||
ui.heading("C2LInspecz - Automated Smartsales System Health");
|
||||
|
||||
println!("====================================================");
|
||||
println!(" C2LInspecz - Automated System Health Check");
|
||||
println!(" Status: INITIALIZING DIAGNOSTICS...");
|
||||
println!("====================================================");
|
||||
|
||||
// 1. Automatic Diagnosis
|
||||
println!("> Running System Audit...");
|
||||
let mut result = run_diagnosis(&config).await;
|
||||
|
||||
// Detailed Status Logs
|
||||
println!(
|
||||
" [1/3] Network: LAN: {}, WAN: {}",
|
||||
if result.network.lan_connected {
|
||||
"OK"
|
||||
} else {
|
||||
"FAIL (Unplugged)"
|
||||
},
|
||||
if result.network.internet_online {
|
||||
"OK"
|
||||
} else {
|
||||
"FAIL (No Internet)"
|
||||
let scanning = *self.is_scanning.lock().unwrap();
|
||||
if ui
|
||||
.add_enabled(!scanning, egui::Button::new("🔍 Run Full Audit"))
|
||||
.clicked()
|
||||
{
|
||||
self.trigger_scan();
|
||||
}
|
||||
|
||||
if scanning {
|
||||
ui.add(
|
||||
egui::ProgressBar::new(0.5)
|
||||
.animate(true)
|
||||
.text("Performing System Audit..."),
|
||||
);
|
||||
}
|
||||
|
||||
print!(
|
||||
" [2/3] Communication: Shop Server: {}",
|
||||
if result.network.server_online {
|
||||
"OK"
|
||||
ui.separator();
|
||||
|
||||
let result_opt = self.result.lock().unwrap().clone();
|
||||
if let Some(res) = result_opt {
|
||||
ui.group(|ui| {
|
||||
ui.label(format!("Last Scan: {}", res.timestamp));
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.set_min_width(200.0);
|
||||
ui.heading("🌐 Network");
|
||||
ui.label(format!(
|
||||
"LAN: {}",
|
||||
if res.network.lan_connected {
|
||||
"✅ OK"
|
||||
} else {
|
||||
"FAIL (Offline)"
|
||||
"❌ Unplugged"
|
||||
}
|
||||
);
|
||||
for p in &result.network.kitchen_printers {
|
||||
print!(", {}: {}", p.name, if p.online { "OK" } else { "FAIL" });
|
||||
));
|
||||
ui.label(format!(
|
||||
"WAN: {}",
|
||||
if res.network.internet_online {
|
||||
"✅ Online"
|
||||
} else {
|
||||
"❌ Offline"
|
||||
}
|
||||
println!();
|
||||
));
|
||||
ui.label(format!(
|
||||
"Server: {}",
|
||||
if res.network.server_online {
|
||||
"✅ REACHABLE"
|
||||
} else {
|
||||
"❌ OFFLINE"
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
print!(" [3/3] Printers:");
|
||||
for p in &result.printers {
|
||||
print!(
|
||||
" [{}: USB: {}, Jobs: {}]",
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("🖨️ Printers");
|
||||
for p in &res.printers {
|
||||
ui.label(format!(
|
||||
"{}: {}",
|
||||
p.name,
|
||||
if p.usb_detected { "OK" } else { "FAIL" },
|
||||
if p.usb_detected {
|
||||
"✅ Found"
|
||||
} else {
|
||||
"❌ NOT DETECTED"
|
||||
}
|
||||
));
|
||||
if p.pending_jobs > 0 {
|
||||
ui.label(
|
||||
egui::RichText::new(format!(
|
||||
"⚠️ {} jobs stuck!",
|
||||
p.pending_jobs
|
||||
))
|
||||
.color(egui::Color32::RED),
|
||||
);
|
||||
}
|
||||
println!();
|
||||
|
||||
// 2. Automatic Auto-Fix (Printer Spooler)
|
||||
let any_stuck = result.printers.iter().any(|p| p.pending_jobs > 0);
|
||||
if any_stuck {
|
||||
println!("> Stuck jobs detected. Triggering Spooler Shield...");
|
||||
if fix_printer_queue() {
|
||||
println!("> Auto-fix applied. Re-verifying systems...");
|
||||
let fresh_scan = run_diagnosis(&config).await;
|
||||
result.printers = fresh_scan.printers;
|
||||
if ui.button("🛡️ Clear Spooler").clicked() {
|
||||
fix_printer_queue();
|
||||
self.trigger_scan();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Evaluation
|
||||
let mut critical_issues: Vec<String> = Vec::new();
|
||||
|
||||
if !result.network.lan_connected {
|
||||
critical_issues.push("HARDWARE: Ethernet cable is UNPLUGGED or DAMAGED.".to_string());
|
||||
} else if !result.network.server_online {
|
||||
critical_issues.push("COMMUNICATION: Local Shop Server is UNREACHABLE.".to_string());
|
||||
}
|
||||
|
||||
for p in &result.network.kitchen_printers {
|
||||
if !p.online {
|
||||
critical_issues.push(format!("NETWORK: Kitchen Printer '{}' is OFFLINE.", p.name));
|
||||
ui.add_space(5.0);
|
||||
ui.label("Network Printers:");
|
||||
for kp in &res.network.kitchen_printers {
|
||||
ui.label(format!(
|
||||
"{}: {}",
|
||||
kp.name,
|
||||
if kp.online {
|
||||
"✅ Online"
|
||||
} else {
|
||||
"❌ Offline"
|
||||
}
|
||||
}
|
||||
|
||||
for p in &result.printers {
|
||||
if !p.usb_detected {
|
||||
critical_issues.push(format!(
|
||||
"HARDWARE: Receipt Printer '{}' is NOT DETECTED.",
|
||||
p.name
|
||||
));
|
||||
} else if p.pending_jobs > 0 {
|
||||
critical_issues.push(format!(
|
||||
"SPOOLER: Jobs remain STUCK for printer '{}'.",
|
||||
p.name
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 4. Reporting
|
||||
println!("\n[FINAL HEALTH REPORT]");
|
||||
if critical_issues.is_empty() {
|
||||
println!("✅ SUCCESS: All systems are operational.");
|
||||
println!("The tool has verified your hardware and cleared minor software glitches.");
|
||||
ui.separator();
|
||||
ui.collapsing("💻 Hardware Info", |ui| {
|
||||
ui.label(format!("CPU Usage: {:.1}%", res.hardware.cpu_usage));
|
||||
ui.add(egui::ProgressBar::new(res.hardware.cpu_usage / 100.0));
|
||||
ui.label(format!(
|
||||
"RAM: {} / {} MB",
|
||||
res.hardware.ram_used_mb, res.hardware.ram_total_mb
|
||||
));
|
||||
ui.label(format!("Disk Free: {:.1} GB", res.hardware.disk_free_gb));
|
||||
});
|
||||
} else {
|
||||
println!("❌ FAILURES DETECTED:");
|
||||
for issue in &critical_issues {
|
||||
println!(" - {}", issue);
|
||||
ui.label("Waiting for first scan result...");
|
||||
if !scanning {
|
||||
self.trigger_scan();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n----------------------------------------------------");
|
||||
println!("The tool could not resolve these physical or network issues.");
|
||||
println!("Would you like to open a ticket for Onsite Support?");
|
||||
print!("\nOpen Support Ticket #C2L-9999? (y/N): ");
|
||||
io::stdout().flush().unwrap();
|
||||
fn ui_settings(&mut self, ui: &mut egui::Ui) {
|
||||
ui.heading("Shop Configuration");
|
||||
ui.label("Configure local IPs and device IDs here.");
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input).unwrap();
|
||||
if input.trim().to_lowercase() == "y" {
|
||||
send_support_ticket(&config, &result);
|
||||
ui.add_space(10.0);
|
||||
ui.label("Shop Server IP:");
|
||||
ui.text_edit_singleline(&mut self.config.shop_server_ip);
|
||||
|
||||
ui.separator();
|
||||
ui.label("Network Printers (Kitchen/Bar):");
|
||||
let mut to_remove_kp = None;
|
||||
for (i, p) in self.config.kitchen_printers.iter_mut().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.text_edit_singleline(&mut p.name).on_hover_text("Name");
|
||||
ui.text_edit_singleline(&mut p.ip)
|
||||
.on_hover_text("IP Address");
|
||||
if ui.button("🗑").clicked() {
|
||||
to_remove_kp = Some(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(i) = to_remove_kp {
|
||||
self.config.kitchen_printers.remove(i);
|
||||
}
|
||||
if ui.button("➕ Add Network Printer").clicked() {
|
||||
self.config.kitchen_printers.push(NetworkDevice::default());
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
ui.label("USB Printers (Cashier):");
|
||||
let mut to_remove_rp = None;
|
||||
for (i, p) in self.config.receipt_printers.iter_mut().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.text_edit_singleline(&mut p.name);
|
||||
ui.text_edit_singleline(&mut p.usb_id)
|
||||
.on_hover_text("USB ID (e.g. 1fc9:2016)");
|
||||
if ui.button("🗑").clicked() {
|
||||
to_remove_rp = Some(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(i) = to_remove_rp {
|
||||
self.config.receipt_printers.remove(i);
|
||||
}
|
||||
if ui.button("➕ Add USB Printer").clicked() {
|
||||
self.config.receipt_printers.push(UsbDevice::default());
|
||||
}
|
||||
|
||||
ui.add_space(20.0);
|
||||
if ui.button("💾 Save Configuration").clicked() {
|
||||
if self.config.save() {
|
||||
self.last_error = Some("Configuration saved successfully!".to_string());
|
||||
} else {
|
||||
println!("Process ended. Please contact your manager if issues persist.");
|
||||
self.last_error = Some("Failed to save configuration.".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(msg) = &self.last_error {
|
||||
ui.label(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([600.0, 500.0])
|
||||
.with_title("C2LInspecz"),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"c2linspecz",
|
||||
native_options,
|
||||
Box::new(|cc| Ok(Box::new(C2LApp::new(cc)))),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user