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.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "C2LInspecz"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"eframe",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sysinfo",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ab_glyph"
|
name = "ab_glyph"
|
||||||
version = "0.2.32"
|
version = "0.2.32"
|
||||||
@@ -507,6 +496,17 @@ version = "1.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "c2linspecz"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"eframe",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sysinfo",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "calloop"
|
name = "calloop"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
|||||||
13
Cargo.toml
13
Cargo.toml
@@ -1,7 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "C2LInspecz"
|
name = "c2linspecz"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
authors = ["Code2Lab Team <support@code2lab.com>"]
|
||||||
|
description = "Automated POS Diagnostic & Health Monitoring Tool"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = "0.33.3"
|
eframe = "0.33.3"
|
||||||
@@ -9,3 +11,12 @@ serde = { version = "1.0.228", features = ["derive"] }
|
|||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
sysinfo = "0.38.0"
|
sysinfo = "0.38.0"
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
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",
|
"name": "Bar Printer",
|
||||||
"ip": "192.168.1.201"
|
"ip": "192.168.1.201"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test printe",
|
||||||
|
"ip": "192.168.1.202"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"receipt_printers": [
|
"receipt_printers": [
|
||||||
{
|
{
|
||||||
"name": "Main Cashier",
|
"name": "Main Cashier",
|
||||||
"usb_id": "1fc9:2016"
|
"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 serde::{Deserialize, Serialize};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use sysinfo::{Disks, System};
|
use sysinfo::{Disks, System};
|
||||||
use tokio::time::sleep;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||||
struct NetworkDevice {
|
struct NetworkDevice {
|
||||||
name: String,
|
name: String,
|
||||||
ip: String,
|
ip: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||||
struct UsbDevice {
|
struct UsbDevice {
|
||||||
name: String,
|
name: String,
|
||||||
usb_id: String,
|
usb_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone, Default)]
|
||||||
struct Config {
|
struct Config {
|
||||||
shop_server_ip: String,
|
shop_server_ip: String,
|
||||||
kitchen_printers: Vec<NetworkDevice>,
|
kitchen_printers: Vec<NetworkDevice>,
|
||||||
receipt_printers: Vec<UsbDevice>,
|
receipt_printers: Vec<UsbDevice>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone, Default)]
|
||||||
struct DeviceStatus {
|
struct DeviceStatus {
|
||||||
name: String,
|
name: String,
|
||||||
online: bool,
|
online: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone, Default)]
|
||||||
struct ScanResult {
|
struct ScanResult {
|
||||||
timestamp: String,
|
timestamp: String,
|
||||||
hardware: HardwareStatus,
|
hardware: HardwareStatus,
|
||||||
@@ -39,7 +41,7 @@ struct ScanResult {
|
|||||||
printers: Vec<PrinterStatus>,
|
printers: Vec<PrinterStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone, Default)]
|
||||||
struct HardwareStatus {
|
struct HardwareStatus {
|
||||||
ram_used_mb: u64,
|
ram_used_mb: u64,
|
||||||
ram_total_mb: u64,
|
ram_total_mb: u64,
|
||||||
@@ -47,7 +49,7 @@ struct HardwareStatus {
|
|||||||
disk_free_gb: f64,
|
disk_free_gb: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone, Default)]
|
||||||
struct NetworkStatus {
|
struct NetworkStatus {
|
||||||
lan_connected: bool,
|
lan_connected: bool,
|
||||||
server_online: bool,
|
server_online: bool,
|
||||||
@@ -55,7 +57,7 @@ struct NetworkStatus {
|
|||||||
internet_online: bool,
|
internet_online: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone, Default)]
|
||||||
struct PrinterStatus {
|
struct PrinterStatus {
|
||||||
name: String,
|
name: String,
|
||||||
usb_detected: bool,
|
usb_detected: bool,
|
||||||
@@ -65,11 +67,39 @@ struct PrinterStatus {
|
|||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
fn load() -> Self {
|
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();
|
let mut data = String::new();
|
||||||
file.read_to_string(&mut data)
|
if file.read_to_string(&mut data).is_ok() {
|
||||||
.expect("Unable to read config.json");
|
if let Ok(config) = serde_json::from_str(&data) {
|
||||||
serde_json::from_str(&data).expect("JSON format error")
|
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 total_ram = sys.total_memory() / 1024 / 1024;
|
||||||
let used_ram = sys.used_memory() / 1024 / 1024;
|
let used_ram = sys.used_memory() / 1024 / 1024;
|
||||||
sys.refresh_cpu_usage();
|
sys.refresh_cpu_usage();
|
||||||
sleep(Duration::from_millis(200)).await;
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
sys.refresh_cpu_usage();
|
sys.refresh_cpu_usage();
|
||||||
let cpu_load: f32 =
|
let cpu_load: f32 =
|
||||||
sys.cpus().iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / sys.cpus().len() as f32;
|
sys.cpus().iter().map(|cpu| cpu.cpu_usage()).sum::<f32>() / sys.cpus().len() as f32;
|
||||||
|
|
||||||
let disks = Disks::new_with_refreshed_list();
|
let disks = Disks::new_with_refreshed_list();
|
||||||
let root_disk = disks
|
let disk_free = disks
|
||||||
.iter()
|
.iter()
|
||||||
.find(|d| d.mount_point().to_string_lossy() == "/");
|
.find(|d| d.mount_point().to_string_lossy() == "/")
|
||||||
let disk_free = root_disk
|
|
||||||
.map(|d| d.available_space() as f64 / 1e9)
|
.map(|d| d.available_space() as f64 / 1e9)
|
||||||
.unwrap_or(0.0);
|
.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")
|
let usb_output = Command::new("lsusb")
|
||||||
.output()
|
.output()
|
||||||
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
|
.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 {
|
printer_results.push(PrinterStatus {
|
||||||
name: p.name.clone(),
|
name: p.name.clone(),
|
||||||
usb_detected: usb_output.contains(&p.usb_id),
|
usb_detected: usb_output.contains(&p.usb_id),
|
||||||
cups_ready: lpstat_output.contains("enabled")
|
cups_ready: lpstat_output.contains("enabled"),
|
||||||
&& (lpstat_output.contains("idle") || lpstat_output.contains("printing")),
|
pending_jobs: jobs_count,
|
||||||
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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,166 +211,264 @@ async fn run_diagnosis(config: &Config) -> ScanResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fix_printer_queue() -> bool {
|
fn fix_printer_queue() -> bool {
|
||||||
println!("\n[SPOOLER SHIELD] Attempting to clear stuck jobs...");
|
Command::new("cancel")
|
||||||
// 1. Purge all jobs
|
.args(["-a", "-x"])
|
||||||
let purge = Command::new("cancel").args(["-a", "-x"]).status();
|
.status()
|
||||||
match purge {
|
.map(|s| s.success())
|
||||||
Ok(status) if status.success() => {
|
.unwrap_or(false)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
println!("ERROR: Failed to clear queue automatically. Please check printer power.");
|
struct C2LApp {
|
||||||
false
|
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) {
|
fn trigger_scan(&self) {
|
||||||
println!("\n[ONSITE ESCALATION]");
|
let config = self.config.clone();
|
||||||
println!(
|
let result_store = self.result.clone();
|
||||||
"WARNING: This service may be billable if the issue is a hardware failure (cables, broken hardware)."
|
let scanning_status = self.is_scanning.clone();
|
||||||
);
|
|
||||||
print!("Do you wish to proceed with generating a Ticket? (y/N): ");
|
|
||||||
io::stdout().flush().unwrap();
|
|
||||||
|
|
||||||
let mut input = String::new();
|
self.rt.spawn(async move {
|
||||||
io::stdin().read_line(&mut input).unwrap();
|
*scanning_status.lock().unwrap() = true;
|
||||||
if input.trim().to_lowercase() != "y" {
|
let fresh_result = run_diagnosis(&config).await;
|
||||||
println!("Ticket cancelled.");
|
*result_store.lock().unwrap() = Some(fresh_result);
|
||||||
return;
|
*scanning_status.lock().unwrap() = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Packaging logs...");
|
impl eframe::App for C2LApp {
|
||||||
let payload = serde_json::json!({
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
"config": config,
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
"diagnostics": result,
|
ui.horizontal(|ui| {
|
||||||
"system_info": "Linux x86_64",
|
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
|
// Auto-refresh scan result
|
||||||
println!("SUCCESS: Support Ticket #C2L-{:04} generated.", 9999);
|
ctx.request_repaint_after(Duration::from_millis(500));
|
||||||
println!("Log Payload: {}", serde_json::to_string(&payload).unwrap());
|
}
|
||||||
println!("The Code2Lab team has been notified. Please keep your device online.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
impl C2LApp {
|
||||||
async fn main() {
|
fn ui_dashboard(&mut self, ui: &mut egui::Ui) {
|
||||||
let config = Config::load();
|
ui.heading("C2LInspecz - Automated Smartsales System Health");
|
||||||
|
|
||||||
println!("====================================================");
|
let scanning = *self.is_scanning.lock().unwrap();
|
||||||
println!(" C2LInspecz - Automated System Health Check");
|
if ui
|
||||||
println!(" Status: INITIALIZING DIAGNOSTICS...");
|
.add_enabled(!scanning, egui::Button::new("🔍 Run Full Audit"))
|
||||||
println!("====================================================");
|
.clicked()
|
||||||
|
{
|
||||||
// 1. Automatic Diagnosis
|
self.trigger_scan();
|
||||||
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)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scanning {
|
||||||
|
ui.add(
|
||||||
|
egui::ProgressBar::new(0.5)
|
||||||
|
.animate(true)
|
||||||
|
.text("Performing System Audit..."),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
print!(
|
ui.separator();
|
||||||
" [2/3] Communication: Shop Server: {}",
|
|
||||||
if result.network.server_online {
|
let result_opt = self.result.lock().unwrap().clone();
|
||||||
"OK"
|
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 {
|
} else {
|
||||||
"FAIL (Offline)"
|
"❌ Unplugged"
|
||||||
}
|
}
|
||||||
);
|
));
|
||||||
for p in &result.network.kitchen_printers {
|
ui.label(format!(
|
||||||
print!(", {}: {}", p.name, if p.online { "OK" } else { "FAIL" });
|
"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:");
|
ui.vertical(|ui| {
|
||||||
for p in &result.printers {
|
ui.heading("🖨️ Printers");
|
||||||
print!(
|
for p in &res.printers {
|
||||||
" [{}: USB: {}, Jobs: {}]",
|
ui.label(format!(
|
||||||
|
"{}: {}",
|
||||||
p.name,
|
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
|
p.pending_jobs
|
||||||
|
))
|
||||||
|
.color(egui::Color32::RED),
|
||||||
);
|
);
|
||||||
}
|
if ui.button("🛡️ Clear Spooler").clicked() {
|
||||||
println!();
|
fix_printer_queue();
|
||||||
|
self.trigger_scan();
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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());
|
|
||||||
}
|
}
|
||||||
|
ui.add_space(5.0);
|
||||||
for p in &result.network.kitchen_printers {
|
ui.label("Network Printers:");
|
||||||
if !p.online {
|
for kp in &res.network.kitchen_printers {
|
||||||
critical_issues.push(format!("NETWORK: Kitchen Printer '{}' is OFFLINE.", p.name));
|
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
|
ui.separator();
|
||||||
println!("\n[FINAL HEALTH REPORT]");
|
ui.collapsing("💻 Hardware Info", |ui| {
|
||||||
if critical_issues.is_empty() {
|
ui.label(format!("CPU Usage: {:.1}%", res.hardware.cpu_usage));
|
||||||
println!("✅ SUCCESS: All systems are operational.");
|
ui.add(egui::ProgressBar::new(res.hardware.cpu_usage / 100.0));
|
||||||
println!("The tool has verified your hardware and cleared minor software glitches.");
|
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 {
|
} else {
|
||||||
println!("❌ FAILURES DETECTED:");
|
ui.label("Waiting for first scan result...");
|
||||||
for issue in &critical_issues {
|
if !scanning {
|
||||||
println!(" - {}", issue);
|
self.trigger_scan();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\n----------------------------------------------------");
|
fn ui_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
println!("The tool could not resolve these physical or network issues.");
|
ui.heading("Shop Configuration");
|
||||||
println!("Would you like to open a ticket for Onsite Support?");
|
ui.label("Configure local IPs and device IDs here.");
|
||||||
print!("\nOpen Support Ticket #C2L-9999? (y/N): ");
|
|
||||||
io::stdout().flush().unwrap();
|
|
||||||
|
|
||||||
let mut input = String::new();
|
ui.add_space(10.0);
|
||||||
io::stdin().read_line(&mut input).unwrap();
|
ui.label("Shop Server IP:");
|
||||||
if input.trim().to_lowercase() == "y" {
|
ui.text_edit_singleline(&mut self.config.shop_server_ip);
|
||||||
send_support_ticket(&config, &result);
|
|
||||||
|
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 {
|
} 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