From d906232bf9de8f200e1c908bfe23a0e845174781 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 17 Jan 2026 12:53:09 -0800 Subject: [PATCH] Move relevant code to osticket.rs --- Cargo.lock | 13 +- core/Cargo.toml | 1 + core/src/action.rs | 2 + core/src/cli.rs | 9 -- core/src/components/select_ticket.rs | 81 ++++++----- core/src/lib.rs | 1 + core/src/osticket.rs | 97 +++++++++++++ deja_vu/src/app.rs | 195 +++++++++++++-------------- deja_vu/src/main.rs | 2 +- 9 files changed, 237 insertions(+), 164 deletions(-) create mode 100644 core/src/osticket.rs diff --git a/Cargo.lock b/Cargo.lock index 8bc861a..f2e6295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -540,6 +540,7 @@ dependencies = [ "serde", "serde_json", "signal-hook", + "sqlx", "strip-ansi-escapes", "strum 0.26.3", "tempfile", @@ -935,7 +936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2327,7 +2328,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2675,7 +2676,7 @@ dependencies = [ [[package]] name = "ost-db-connector" version = "0.1.0" -source = "git+ssh://git@git.1201.com:2222/1201/ost-db-connector.git#8cfec8b5456c0b6b713f4b9c5463219f06c05fd5" +source = "git+ssh://git@git.1201.com:2222/1201/ost-db-connector.git#8078a52efaf8bcdb93705d8fbdd99e17a94998d7" dependencies = [ "chrono", "clap", @@ -3154,7 +3155,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3752,7 +3753,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.3", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4394,7 +4395,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/core/Cargo.toml b/core/Cargo.toml index accec0e..8fd8209 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -48,6 +48,7 @@ regex = "1.11.1" serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.125" signal-hook = "0.3.17" +sqlx = { version = "0.8", features = ["mysql", "runtime-tokio"] } strip-ansi-escapes = "0.2.0" strum = { version = "0.26.3", features = ["derive"] } tempfile = "3.13.0" diff --git a/core/src/action.rs b/core/src/action.rs index f9eba8f..0df6928 100644 --- a/core/src/action.rs +++ b/core/src/action.rs @@ -13,6 +13,7 @@ // You should have received a copy of the GNU General Public License // along with Deja-Vu. If not, see . // +use ost_db_connector::ResponseColor; use serde::{Deserialize, Serialize}; use strum::Display; @@ -60,6 +61,7 @@ pub enum Action { FindWimNetwork, SetUserName(String), // osTicket + PostResponse { color: ResponseColor, text: String }, ResetTicket, SelectTicket(u16), // Screens diff --git a/core/src/cli.rs b/core/src/cli.rs index 730f86e..b544d7b 100644 --- a/core/src/cli.rs +++ b/core/src/cli.rs @@ -27,15 +27,6 @@ pub struct Cli { /// Frame rate, i.e. number of frames per second #[arg(short, long, value_name = "FLOAT", default_value_t = 60.0)] pub frame_rate: f64, - - /// osTicket server - #[arg( - short, - long, - value_name = "IP_OR_FQDN", - default_value_t = String::from("ost.1201.com"), - )] - pub ost_server: String, } const VERSION_MESSAGE: &str = concat!( diff --git a/core/src/components/select_ticket.rs b/core/src/components/select_ticket.rs index 2e320b4..da15709 100644 --- a/core/src/components/select_ticket.rs +++ b/core/src/components/select_ticket.rs @@ -14,7 +14,6 @@ // along with Deja-Vu. If not, see . use color_eyre::Result; use crossterm::event::KeyCode; -use ost_db_connector::Ticket; use ratatui::{ prelude::*, widgets::{Block, Borders, Clear, Paragraph}, @@ -24,7 +23,7 @@ use tokio::sync::mpsc::UnboundedSender; use tui_input::{Input, InputRequest}; use super::Component; -use crate::{action::Action, config::Config, state::Mode, tui::Event}; +use crate::{action::Action, config::Config, osticket::OsTicket, state::Mode, tui::Event}; #[derive(Default)] pub struct TicketSelection { @@ -32,16 +31,14 @@ pub struct TicketSelection { config: Config, input: Input, mode: Mode, - ost_status: Arc>, - ost_ticket: Arc>, + osticket_arc: Arc>, } impl TicketSelection { #[must_use] - pub fn new(ost_status: Arc>, ost_ticket: Arc>) -> Self { + pub fn new(osticket_arc: Arc>) -> Self { Self { - ost_status, - ost_ticket, + osticket_arc, ..Default::default() } } @@ -98,8 +95,8 @@ impl Component for TicketSelection { self.mode = mode; self.input.reset(); if self.mode == Mode::SelectTicket - && let Ok(ost_ticket) = self.ost_ticket.lock() - && let Some(id) = &ost_ticket.id + && let Ok(osticket) = self.osticket_arc.lock() + && let Some(id) = &osticket.ticket.id { id.to_string().chars().for_each(|c| { let _ = self.input.handle(InputRequest::InsertChar(c)); @@ -143,41 +140,39 @@ impl Component for TicketSelection { frame.render_widget(input, input_area); // Info Box - let mut ost_connected = false; - if let Ok(ost_status) = self.ost_status.lock() { - ost_connected = !ost_status.is_empty(); - } - let mut lines = Vec::with_capacity(3); - if !ost_connected { - lines.push(Line::from("Connecting...").style(Style::new().yellow())); - } else if let Ok(ost_ticket) = self.ost_ticket.lock() { - let ticket_id = if let Some(id) = &ost_ticket.id { - id.to_string() + let mut lines = Vec::with_capacity(4); + if let Ok(osticket) = self.osticket_arc.lock() { + if !osticket.connected { + lines.push(Line::from("Connecting...").style(Style::new().yellow())); } else { - String::from("...") - }; - let ticket_name = if let Some(name) = &ost_ticket.name { - name.clone() - } else { - String::from("...") - }; - let ticket_subject = if let Some(subject) = &ost_ticket.subject { - subject.clone() - } else { - String::from("...") - }; - lines.push(Line::from(vec![ - Span::from("Ticket ID: ").bold(), - Span::from(ticket_id), - ])); - lines.push(Line::from(vec![ - Span::from("Customer Name: ").bold(), - Span::from(ticket_name), - ])); - lines.push(Line::from(vec![ - Span::from("Subject: ").bold(), - Span::from(ticket_subject), - ])); + let ticket_id = if let Some(id) = &osticket.ticket.id { + id.to_string() + } else { + String::from("...") + }; + let ticket_name = if let Some(name) = &osticket.ticket.name { + name.clone() + } else { + String::from("...") + }; + let ticket_subject = if let Some(subject) = &osticket.ticket.subject { + subject.clone() + } else { + String::from("...") + }; + lines.push(Line::from(vec![ + Span::from("Ticket ID: ").bold(), + Span::from(ticket_id), + ])); + lines.push(Line::from(vec![ + Span::from("Customer Name: ").bold(), + Span::from(ticket_name), + ])); + lines.push(Line::from(vec![ + Span::from("Subject: ").bold(), + Span::from(ticket_subject), + ])); + } } let info = Paragraph::new(lines) .white() diff --git a/core/src/lib.rs b/core/src/lib.rs index 25535bb..bca3db0 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -20,6 +20,7 @@ pub mod config; pub mod errors; pub mod line; pub mod logging; +pub mod osticket; pub mod state; pub mod system; pub mod tasks; diff --git a/core/src/osticket.rs b/core/src/osticket.rs new file mode 100644 index 0000000..97666d8 --- /dev/null +++ b/core/src/osticket.rs @@ -0,0 +1,97 @@ +// This file is part of Deja-Vu. +// +// Deja-Vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-Vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-Vu. If not, see . +// + +use ost_db_connector::Ticket; +use sqlx::{MySql, Pool}; + +use crate::system::disk::Disk; + +pub enum ReportType { + Source, + Destination { label: String }, +} + +#[derive(Debug, Default)] +pub struct OsTicket { + pub connected: bool, + pub server_url: String, + pub report: Vec, + pub tech_note: String, + pub ticket: Ticket, +} + +impl OsTicket { + pub fn new(ost_server: &str) -> Self { + Self { + connected: false, + server_url: ost_server.to_string(), + ..Default::default() + } + } + + pub fn set_db_pool(&mut self, pool: Option>) { + self.ticket.pool = pool; + if self.ticket.pool.is_some() { + self.connected = true + } + } + + pub fn get_report(&self) -> String { + self.report.join("\n") + } + + pub fn update_report(&mut self, disk: &Disk, report_type: ReportType) { + // Header + if self.report.is_empty() { + self.report.push(String::from("[Deja-Vu Report]")); + if !self.tech_note.is_empty() { + self.report.push(format!("Tech note: {}", self.tech_note)); + } + } + + // Disk Label + match report_type { + ReportType::Source => { + self.report.push(String::new()); + self.report.push(String::from("Source:")); + } + ReportType::Destination { label } => { + self.report.push(String::new()); + self.report.push(String::from("---")); + self.report.push(String::new()); + self.report.push(format!("Destination ({label}):")); + } + } + + // Disk Details + self.report.push(format!( + "... {:<8} {:>11} {:<4} {:<4} {}", + "Disk ID", "Size", "Conn", "Type", "Model (Serial)" + )); + self.report.push(format!("... {}", disk.description)); + self.report.push(String::new()); + self.report.push(format!( + "... {:<8} {:>11} {:<7} {}", + "Part ID", "Size", "(FS)", "\"Label\"" + )); + disk.parts_description.iter().for_each(|desc| { + self.report.push(format!("... {desc}")); + }); + if disk.parts_description.is_empty() { + self.report.push(String::from("... -None-")); + }; + } +} diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index eba128c..44d27d4 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -27,11 +27,12 @@ use core::{ }, config::Config, line::{DVLine, get_disk_description_right, get_part_description}, + osticket::{OsTicket, ReportType}, state::Mode, system::{ boot, cpu::get_cpu_name, - disk::{Disk, PartitionTableType}, + disk::PartitionTableType, diskpart::{FormatUseCase, build_dest_format_script}, drivers, }, @@ -46,7 +47,7 @@ use std::{ }; use color_eyre::Result; -use ost_db_connector::{ResponseColor, Ticket}; +use ost_db_connector::ResponseColor; use ratatui::{ crossterm::event::KeyEvent, layout::{Constraint, Direction, Layout}, @@ -71,10 +72,7 @@ pub struct App { tick_rate: f64, // osTicket connected: bool, - ost_server: String, - ost_status: Arc>, - ost_ticket: Arc>, - report: Vec, + osticket_arc: Arc>, // App cur_mode: Mode, list: StatefulList, @@ -85,13 +83,13 @@ pub struct App { } impl App { - pub fn new(tick_rate: f64, frame_rate: f64, ost_server: &str) -> Result { + pub fn new(tick_rate: f64, frame_rate: f64) -> Result { let (action_tx, action_rx) = mpsc::unbounded_channel(); + let config = Config::new()?; let disk_list_arc = Arc::new(Mutex::new(Vec::new())); + let osticket = OsTicket::new(&config.ost_server); + let osticket_arc = Arc::new(Mutex::new(osticket)); let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); - let ost_server = String::from(ost_server); - let ost_status = Arc::new(Mutex::new(String::new())); - let ost_ticket = Arc::new(Mutex::new(Ticket::new())); Ok(Self { // TUI action_rx, @@ -101,7 +99,7 @@ impl App { Box::new(Left::new()), Box::new(Right::new()), Box::new(Footer::new()), - Box::new(TicketSelection::new(ost_status.clone(), ost_ticket.clone())), + Box::new(TicketSelection::new(osticket_arc.clone())), Box::new(popup::Popup::new()), ], config: Config::new()?, @@ -112,10 +110,7 @@ impl App { tick_rate, // osTicket connected: false, - ost_server, - ost_status, - ost_ticket, - report: Vec::new(), + osticket_arc, // App state: State::new(disk_list_arc), cur_mode: Mode::default(), @@ -195,7 +190,7 @@ impl App { } } - pub async fn set_mode(&mut self, new_mode: Mode) -> Result<()> { + pub fn set_mode(&mut self, new_mode: Mode) -> Result<()> { info!("Setting mode to {new_mode:?}"); self.cur_mode = new_mode; match new_mode { @@ -216,22 +211,6 @@ impl App { String::from("Formatting destination disk"), ))?; - // Update osTicket report - let disk_list = self.state.disk_list.lock().unwrap(); - if let Some(source_index) = self.state.disk_index_source - && let Some(source_disk) = disk_list.get(source_index) - && let Some(dest_index) = self.state.disk_index_dest - && let Some(dest_disk) = disk_list.get(dest_index) - { - self.report.push(String::from("[Deja-Vu Report]")); - self.report.push(String::new()); - self.report.push(String::from("Source Disk:")); - self.report.append(&mut build_disk_report(source_disk)); - self.report.push(String::new()); - self.report.push(String::from("Dest Disk (before):")); - self.report.append(&mut build_disk_report(dest_disk)); - } - // Get System32 path let system32 = get_system32_path(&self.action_tx); @@ -271,34 +250,6 @@ impl App { String::from("Updating boot configuration"), ))?; - // Update osTicket report - let disk_list = self.state.disk_list.lock().unwrap(); - if let Some(dest_index) = self.state.disk_index_dest - && let Some(dest_disk) = disk_list.get(dest_index) - { - self.report.push(String::from("[Deja-Vu Report]")); - self.report.push(String::new()); - self.report.push(String::from("Dest Disk (After):")); - self.report.append(&mut build_disk_report(dest_disk)); - } - - // Upload osTicket report - if let Ok(ticket) = self.ost_ticket.lock() { - if ticket.pool.is_some() { - let result = ticket - .post_response(ResponseColor::Normal, &self.report.join("\n")) - .await; - match result { - Ok(_) => { - info!("Posted results to osTicket!"); - } - Err(err) => { - warn!("Failed to post results to osTicket: {err}"); - } - }; - } - } - // Get System32 path let system32 = get_system32_path(&self.action_tx); @@ -341,12 +292,6 @@ impl App { )))?; } } - - // Post to ticket - //self.tasks.add(TaskType::CommandWait( - // PathBuf::from("X:/Windows/System32/sync64.exe"), - // vec![String::from("-r")], - //)); } } Mode::Done => { @@ -376,12 +321,13 @@ impl App { } // Start background task to connect to osTicket - let ost_ticket_arc = self.ost_ticket.clone(); - let ost_server = self.ost_server.clone(); + let osticket_arc = self.osticket_arc.clone(); + let ost_server = self.config.ost_server.clone(); tokio::spawn(async move { - let pool = ost_db_connector::connect(Some(&ost_server)).await; - let mut ticket = ost_ticket_arc.lock().unwrap(); - ticket.pool = pool.ok(); + let pool = ost_db_connector::connect(Some(ost_server), true).await; + if let Ok(mut osticket) = osticket_arc.lock() { + osticket.set_db_pool(pool); + } }); let action_tx = self.action_tx.clone(); @@ -461,13 +407,10 @@ impl App { } if self.cur_mode == Mode::SelectTicket && !self.connected - && let Ok(ticket) = self.ost_ticket.try_lock() - && ticket.pool.is_some() + && let Ok(osticket) = self.osticket_arc.try_lock() + && osticket.ticket.pool.is_some() { self.connected = true; - if let Ok(mut ost_status) = self.ost_status.lock() { - *ost_status = String::from("Connected"); - } } } Action::Quit => self.should_quit = true, @@ -495,6 +438,21 @@ impl App { }, Action::Resize(w, h) => self.handle_resize(tui, w, h)?, Action::Render => self.render(tui)?, + Action::PostResponse { color, ref text } => { + if let Ok(osticket) = self.osticket_arc.lock() + && osticket.connected + { + let result = osticket.ticket.post_response(color, &text).await; + match result { + Ok(_) => { + info!("Posted results to osTicket!"); + } + Err(err) => { + warn!("Failed to post results to osTicket: {err}"); + } + }; + } + } Action::PrevScreen => { if let Some(new_mode) = self.prev_mode() { self.prev_mode = new_mode; @@ -523,10 +481,59 @@ impl App { } } Mode::SelectDisks => { + if two.is_some() + && let Ok(mut osticket) = self.osticket_arc.lock() + && osticket.connected + { + // Update osTicket report + info!("Updating OST Report (Before)..."); + let disk_list = self.state.disk_list.lock().unwrap(); + if let Some(source_index) = one + && let Some(source_disk) = disk_list.get(source_index) + && let Some(dest_index) = two + && let Some(dest_disk) = disk_list.get(dest_index) + { + osticket.update_report(source_disk, ReportType::Source); + osticket.update_report( + dest_disk, + ReportType::Destination { + label: String::from("Before"), + }, + ); + } + } + + // Update state self.state.disk_index_source = one; self.state.disk_index_dest = two; } Mode::SelectParts => { + if two.is_some() + && let Ok(mut osticket) = self.osticket_arc.lock() + && osticket.connected + { + // Update osTicket report + info!("Updating OST Report (After)..."); + let disk_list = self.state.disk_list.lock().unwrap(); + if let Some(dest_index) = self.state.disk_index_dest + && let Some(dest_disk) = disk_list.get(dest_index) + { + osticket.update_report( + dest_disk, + ReportType::Destination { + label: String::from("After"), + }, + ); + + // Upload osTicket report + self.action_tx.send(Action::PostResponse { + color: ResponseColor::Normal, + text: osticket.get_report(), + })?; + } + } + + // Update state self.state.part_index_boot = one; self.state.part_index_os = two; } @@ -551,8 +558,8 @@ impl App { self.selections[1] = two; } Action::SelectTicket(id) => { - let mut ticket = self.ost_ticket.lock().unwrap(); - let _ = ticket.select_id(id).await; + let mut osticket = self.osticket_arc.lock().unwrap(); + let _ = osticket.ticket.select_id(id).await; } Action::SetMode(new_mode) => { // Clear TableType selection @@ -562,7 +569,7 @@ impl App { } _ => {} } - self.set_mode(new_mode).await?; + self.set_mode(new_mode)?; self.action_tx .send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?; self.action_tx.send(build_left_items(self))?; @@ -720,28 +727,6 @@ fn get_chunks(r: Rect) -> Vec { chunks } -fn build_disk_report(disk: &Disk) -> Vec { - let mut report = Vec::with_capacity(8); - report.push(format!( - "... {:<8} {:>11} {:<4} {:<4} {}", - "Disk ID", "Size", "Conn", "Type", "Model (Serial)" - )); - report.push(format!("... {}", disk.description)); - report.push(String::new()); - report.push(format!( - "... {:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - )); - report.append(&mut disk.parts_description.clone()); - disk.parts_description.iter().for_each(|desc| { - report.push(format!("... {desc}")); - }); - if disk.parts_description.is_empty() { - report.push(String::from("... -None-")); - }; - report -} - fn build_footer_string(cur_mode: Mode) -> String { match cur_mode { Mode::Home | Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { @@ -859,15 +844,15 @@ fn build_right_items(app: &App) -> Action { let mut labels: Vec> = Vec::new(); let mut start_index = 0; if app.connected - && let Ok(ticket) = app.ost_ticket.lock() - && let Some(name) = &ticket.name + && let Ok(osticket) = app.osticket_arc.lock() + && let Some(name) = &osticket.ticket.name { items.push(vec![DVLine { line_parts: vec![ - format!("osTicket: #{} ", ticket.id.unwrap()), + format!("osTicket: #{} ", osticket.ticket.id.unwrap()), name.clone(), String::from(" // "), - ticket.subject.clone().unwrap(), + osticket.ticket.subject.clone().unwrap(), ], line_colors: vec![Color::Reset, Color::Cyan, Color::Reset, Color::Reset], }]); diff --git a/deja_vu/src/main.rs b/deja_vu/src/main.rs index 35241a5..8048d12 100644 --- a/deja_vu/src/main.rs +++ b/deja_vu/src/main.rs @@ -37,7 +37,7 @@ async fn main() -> Result<()> { core::logging::init()?; let args = core::cli::Cli::parse(); - let mut app = App::new(args.tick_rate, args.frame_rate, &args.ost_server)?; + let mut app = App::new(args.tick_rate, args.frame_rate)?; app.run().await?; } Ok(())