tftsr-devops_investigation/src-tauri/src/commands/analysis.rs

901 lines
30 KiB
Rust
Raw Normal View History

use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
use sha2::{Digest, Sha256};
use std::io::Read as IoRead;
use std::io::Write;
use std::path::{Path, PathBuf};
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
use tauri::State;
use tracing::warn;
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
use crate::db::models::{AuditEntry, LogFile, LogFileSummary, PiiDetectionResult, PiiSpanRecord};
use crate::pii::{self, PiiDetector, RedactedLogFile};
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
use crate::state::AppState;
const MAX_LOG_FILE_BYTES: u64 = 50 * 1024 * 1024;
const SAFE_TEXT_EXTENSIONS: &[&str] = &[
"log",
"txt",
"out",
"err",
"syslog",
"journal",
"yaml",
"yml",
"json",
"toml",
"xml",
"ini",
"cfg",
"conf",
"config",
"env",
"properties",
"md",
"markdown",
"rst",
"csv",
"tsv",
"ndjson",
"jsonl",
"sql",
"sh",
"bash",
"zsh",
"py",
"js",
"ts",
"rb",
"go",
"rs",
"java",
"html",
"htm",
"css",
"diff",
"patch",
"rtf",
];
const SAFE_BINARY_EXTENSIONS: &[&str] =
&["pdf", "docx", "doc", "xlsx", "xls", "pcap", "pcapng", "cap"];
fn compress_text(text: &str) -> Result<Vec<u8>, String> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder
.write_all(text.as_bytes())
.map_err(|e| format!("Compression write error: {e}"))?;
encoder
.finish()
.map_err(|e| format!("Compression finish error: {e}"))
}
/// 100 MB cap — prevents decompression-bomb attacks on crafted DB entries.
const MAX_DECOMPRESSED_BYTES: u64 = 100 * 1024 * 1024;
fn decompress_text(bytes: &[u8]) -> Result<String, String> {
let decoder = GzDecoder::new(bytes);
let mut limited = decoder.take(MAX_DECOMPRESSED_BYTES + 1);
let mut s = String::new();
limited
.read_to_string(&mut s)
.map_err(|e| format!("Failed to decompress: {e}"))?;
if s.len() as u64 > MAX_DECOMPRESSED_BYTES {
return Err("Decompressed content exceeds 100 MB limit".to_string());
}
Ok(s)
}
pub fn is_safe_file(path: &Path) -> bool {
let ext = path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase());
match ext.as_deref() {
Some(e) => SAFE_TEXT_EXTENSIONS.contains(&e) || SAFE_BINARY_EXTENSIONS.contains(&e),
None => false,
}
}
pub fn extract_text_content(path: &Path) -> Result<String, String> {
let ext = path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase())
.unwrap_or_default();
match ext.as_str() {
"pdf" => extract_pdf_text(path),
"docx" | "doc" => extract_docx_text(path),
"pcap" | "pcapng" | "cap" => extract_pcap_text(path),
"xlsx" | "xls" => Err(format!(
"Spreadsheet format .{ext} is not yet supported for text extraction. \
Export the sheet as CSV and upload that instead."
)),
_ => std::fs::read_to_string(path).map_err(|e| format!("Failed to read file: {e}")),
}
}
fn extract_pdf_text(path: &Path) -> Result<String, String> {
let doc = lopdf::Document::load(path).map_err(|e| format!("Failed to parse PDF: {e}"))?;
let mut text = String::new();
let mut pages: Vec<u32> = doc.get_pages().keys().copied().collect();
pages.sort_unstable();
for page_num in pages {
if let Ok(content) = doc.extract_text(&[page_num]) {
text.push_str(&content);
text.push('\n');
}
}
if text.trim().is_empty() {
return Err("PDF contains no extractable text (may be a scanned image)".to_string());
}
Ok(text)
}
fn extract_docx_text(path: &Path) -> Result<String, String> {
use std::io::Read as _;
let file = std::fs::File::open(path).map_err(|e| format!("Failed to open file: {e}"))?;
let mut archive =
zip::ZipArchive::new(file).map_err(|e| format!("Failed to open as ZIP/DOCX: {e}"))?;
let mut xml_content = String::new();
{
// Safety: only one hardcoded entry is ever accessed; no arbitrary path extraction is
// performed, so zip-slip path traversal attacks cannot apply here.
let mut doc_xml = archive
.by_name("word/document.xml")
.map_err(|_| "Not a valid DOCX: missing word/document.xml".to_string())?;
doc_xml
.read_to_string(&mut xml_content)
.map_err(|e| format!("Failed to read document.xml: {e}"))?;
}
let mut text = String::new();
let mut reader = quick_xml::Reader::from_str(&xml_content);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(quick_xml::events::Event::Text(e)) => {
if let Ok(s) = e.unescape() {
let trimmed = s.trim().to_string();
if !trimmed.is_empty() {
text.push_str(&trimmed);
text.push(' ');
}
}
}
Ok(quick_xml::events::Event::Eof) => break,
Err(e) => return Err(format!("XML parse error: {e}")),
_ => {}
}
buf.clear();
}
if text.trim().is_empty() {
return Err("DOCX contains no extractable text".to_string());
}
Ok(text)
}
fn extract_pcap_text(path: &Path) -> Result<String, String> {
// Try to use tshark (Wireshark CLI) to extract packet data
// Limit to first 1000 packets and disable name resolution to prevent OOM and stalls
let output = std::process::Command::new("tshark")
.arg("-r")
.arg(path)
.arg("-n") // Disable name resolution
.arg("-c")
.arg("1000") // Limit to first 1000 packets
.arg("-V") // Verbose packet dissection
.output();
match output {
Ok(result) if result.status.success() => {
let text = String::from_utf8_lossy(&result.stdout).to_string();
if text.trim().is_empty() {
return Err("PCAP file contains no packets or is empty".to_string());
}
// Truncate to 1MB to prevent memory issues with verbose output
const MAX_PCAP_TEXT: usize = 1024 * 1024;
if text.len() > MAX_PCAP_TEXT {
Ok(format!(
"{}... (truncated from {} bytes)",
&text[..MAX_PCAP_TEXT],
text.len()
))
} else {
Ok(text)
}
}
Ok(result) => {
let stderr = String::from_utf8_lossy(&result.stderr);
Err(format!("tshark failed to process PCAP: {stderr}"))
}
Err(_) => {
// tshark not installed - try tcpdump as fallback
let tcpdump_output = std::process::Command::new("tcpdump")
.arg("-n") // Don't resolve addresses
.arg("-c")
.arg("1000") // Limit to first 1000 packets
.arg("-r")
.arg(path)
.arg("-A") // Print packet payload in ASCII
.output();
match tcpdump_output {
Ok(result) if result.status.success() => {
let text = String::from_utf8_lossy(&result.stdout).to_string();
if text.trim().is_empty() {
return Err("PCAP file contains no packets or is empty".to_string());
}
// Truncate to 1MB to prevent memory issues with verbose output
const MAX_PCAP_TEXT: usize = 1024 * 1024;
if text.len() > MAX_PCAP_TEXT {
Ok(format!(
"{}... (truncated from {} bytes)",
&text[..MAX_PCAP_TEXT],
text.len()
))
} else {
Ok(text)
}
}
Ok(result) => {
let stderr = String::from_utf8_lossy(&result.stderr);
Err(format!("tcpdump failed to process PCAP: {stderr}"))
}
Err(_) => Err(
"Neither tshark nor tcpdump is installed. Install Wireshark or tcpdump to analyze packet captures.".to_string()
),
}
}
}
}
fn validate_log_file_path(file_path: &str) -> Result<PathBuf, String> {
let path = Path::new(file_path);
let canonical = std::fs::canonicalize(path).map_err(|_| "Unable to access selected file")?;
let metadata = std::fs::metadata(&canonical).map_err(|_| "Unable to read file metadata")?;
if !metadata.is_file() {
return Err("Selected path is not a file".to_string());
}
if metadata.len() > MAX_LOG_FILE_BYTES {
return Err(format!(
"File exceeds maximum supported size ({} MB)",
MAX_LOG_FILE_BYTES / 1024 / 1024
));
}
Ok(canonical)
}
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
#[tauri::command]
pub async fn upload_log_file(
issue_id: String,
file_path: String,
state: State<'_, AppState>,
) -> Result<LogFile, String> {
let canonical_path = validate_log_file_path(&file_path)?;
if !is_safe_file(&canonical_path) {
let ext = canonical_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("(none)");
return Err(format!(
"File type '.{ext}' is not supported. Supported formats include .log, .txt, .json, .pdf, .docx, .md, and many more."
));
}
let file_name = canonical_path
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string();
let file_ext = canonical_path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase())
.unwrap_or_default();
let extracted_text = extract_text_content(&canonical_path)
.map_err(|e| format!("Failed to read file content: {e}"))?;
let content_bytes = extracted_text.as_bytes();
let content_hash = format!("{:x}", Sha256::digest(content_bytes));
let file_size = content_bytes.len() as i64;
let mime_type = match file_ext.as_str() {
"json" => "application/json",
"xml" => "application/xml",
"yaml" | "yml" => "application/yaml",
"pdf" => "application/pdf",
"docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"doc" => "application/msword",
"md" | "markdown" => "text/markdown",
"csv" | "tsv" => "text/csv",
"html" | "htm" => "text/html",
"pcap" | "cap" => "application/vnd.tcpdump.pcap",
"pcapng" => "application/x-pcapng",
_ => "text/plain",
};
let is_binary = SAFE_BINARY_EXTENSIONS.contains(&file_ext.as_str());
let stored_path = if is_binary {
let extracted_path = canonical_path.with_extension("extracted.txt");
std::fs::write(&extracted_path, &extracted_text)
.map_err(|e| format!("Failed to write extracted text: {e}"))?;
extracted_path.to_string_lossy().to_string()
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
} else {
canonical_path.to_string_lossy().to_string()
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
};
let log_file = LogFile::new(issue_id.clone(), file_name, stored_path, file_size);
let log_file = LogFile {
content_hash: content_hash.clone(),
mime_type: mime_type.to_string(),
..log_file
};
let compressed = compress_text(&extracted_text)
.map_err(|e| format!("Failed to compress log content: {e}"))?;
let db = state.db.lock().map_err(|e| e.to_string())?;
db.execute(
"INSERT INTO log_files (id, issue_id, file_name, file_path, file_size, mime_type, content_hash, uploaded_at, redacted, content_compressed) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
rusqlite::params![
log_file.id,
log_file.issue_id,
log_file.file_name,
log_file.file_path,
log_file.file_size,
log_file.mime_type,
log_file.content_hash,
log_file.uploaded_at,
log_file.redacted as i32,
compressed,
],
)
.map_err(|_| "Failed to store uploaded log metadata".to_string())?;
// Audit
let entry = AuditEntry::new(
"upload_log_file".to_string(),
"log_file".to_string(),
log_file.id.clone(),
serde_json::json!({ "issue_id": issue_id, "file_name": log_file.file_name }).to_string(),
);
if let Err(err) = crate::audit::log::write_audit_event(
&db,
&entry.action,
&entry.entity_type,
&entry.entity_id,
&entry.details,
) {
warn!(error = %err, "failed to write upload_log_file audit entry");
}
Ok(log_file)
}
#[tauri::command]
pub async fn upload_log_file_by_content(
issue_id: String,
file_name: String,
content: String,
state: State<'_, AppState>,
) -> Result<LogFile, String> {
let fake_path = Path::new(&file_name);
if !is_safe_file(fake_path) {
let ext = fake_path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("(none)");
return Err(format!("File type '.{ext}' is not supported."));
}
let content_bytes = content.as_bytes();
let content_hash = format!("{:x}", Sha256::digest(content_bytes));
let file_size = content_bytes.len() as i64;
let file_ext = fake_path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase())
.unwrap_or_default();
let mime_type = match file_ext.as_str() {
"json" => "application/json",
"xml" => "application/xml",
"yaml" | "yml" => "application/yaml",
"pdf" => "application/pdf",
"docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"doc" => "application/msword",
"md" | "markdown" => "text/markdown",
"csv" | "tsv" => "text/csv",
"html" | "htm" => "text/html",
"pcap" | "cap" => "application/vnd.tcpdump.pcap",
"pcapng" => "application/x-pcapng",
_ => "text/plain",
};
// Use the file_name as the file_path for DB storage
let log_file = LogFile::new(
issue_id.clone(),
file_name.clone(),
file_name.clone(),
file_size,
);
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
let log_file = LogFile {
content_hash: content_hash.clone(),
mime_type: mime_type.to_string(),
..log_file
};
let compressed =
compress_text(&content).map_err(|e| format!("Failed to compress log content: {e}"))?;
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
let db = state.db.lock().map_err(|e| e.to_string())?;
db.execute(
"INSERT INTO log_files (id, issue_id, file_name, file_path, file_size, mime_type, content_hash, uploaded_at, redacted, content_compressed) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
rusqlite::params![
log_file.id,
log_file.issue_id,
log_file.file_name,
log_file.file_path,
log_file.file_size,
log_file.mime_type,
log_file.content_hash,
log_file.uploaded_at,
log_file.redacted as i32,
compressed,
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
],
)
.map_err(|_| "Failed to store uploaded log metadata".to_string())?;
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
// Audit
let entry = AuditEntry::new(
"upload_log_file".to_string(),
"log_file".to_string(),
log_file.id.clone(),
serde_json::json!({ "issue_id": issue_id, "file_name": log_file.file_name }).to_string(),
);
if let Err(err) = crate::audit::log::write_audit_event(
&db,
&entry.action,
&entry.entity_type,
&entry.entity_id,
&entry.details,
) {
warn!(error = %err, "failed to write upload_log_file audit entry");
}
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
Ok(log_file)
}
#[tauri::command]
pub async fn detect_pii(
log_file_id: String,
state: State<'_, AppState>,
) -> Result<PiiDetectionResult, String> {
// Load file path from DB
let file_path: String = {
let db = state.db.lock().map_err(|e| e.to_string())?;
db.prepare("SELECT file_path FROM log_files WHERE id = ?1")
.and_then(|mut stmt| stmt.query_row([&log_file_id], |row| row.get(0)))
.map_err(|_| "Failed to load log file metadata".to_string())?
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
};
let content =
std::fs::read_to_string(&file_path).map_err(|_| "Failed to read log file content")?;
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
let detector = PiiDetector::new();
let spans = detector.detect(&content);
// Store PII spans in the database for later use
{
let db = state.db.lock().map_err(|e| e.to_string())?;
for span in &spans {
let record = PiiSpanRecord {
id: span.id.clone(),
log_file_id: log_file_id.clone(),
pii_type: span.pii_type.clone(),
start_offset: span.start as i64,
end_offset: span.end as i64,
original_value: String::new(),
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
replacement: span.replacement.clone(),
};
if let Err(err) = db.execute(
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
"INSERT OR REPLACE INTO pii_spans (id, log_file_id, pii_type, start_offset, end_offset, original_value, replacement) \
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
rusqlite::params![
record.id, record.log_file_id, record.pii_type,
record.start_offset, record.end_offset,
record.original_value, record.replacement
],
) {
warn!(error = %err, span_id = %span.id, "failed to persist pii span");
}
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
}
}
let total_pii_found = spans.len();
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
Ok(PiiDetectionResult {
log_file_id,
detections: spans,
total_pii_found,
})
}
/// Maximum text size accepted by scan_text_for_pii to prevent DoS on large payloads.
const MAX_TEXT_SCAN_BYTES: usize = 32 * 1024; // 32 KB
/// Scan arbitrary text for PII without creating any database records.
/// Used by the backend before sending typed chat messages to AI providers.
#[tauri::command]
pub async fn scan_text_for_pii(text: String) -> Result<PiiDetectionResult, String> {
if text.len() > MAX_TEXT_SCAN_BYTES {
return Err(format!(
"Text too large for inline PII scan ({} bytes; limit {MAX_TEXT_SCAN_BYTES})",
text.len()
));
}
let detector = PiiDetector::new();
let spans = detector.detect(&text);
let total_pii_found = spans.len();
Ok(PiiDetectionResult {
log_file_id: String::new(),
detections: spans,
total_pii_found,
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
})
}
#[tauri::command]
pub async fn apply_redactions(
log_file_id: String,
approved_span_ids: Vec<String>,
state: State<'_, AppState>,
) -> Result<RedactedLogFile, String> {
// Load file path
let file_path: String = {
let db = state.db.lock().map_err(|e| e.to_string())?;
db.prepare("SELECT file_path FROM log_files WHERE id = ?1")
.and_then(|mut stmt| stmt.query_row([&log_file_id], |row| row.get(0)))
.map_err(|_| "Failed to load log file metadata".to_string())?
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
};
let content =
std::fs::read_to_string(&file_path).map_err(|_| "Failed to read log file content")?;
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
// Load PII spans from DB, filtering to only approved ones
let spans: Vec<pii::PiiSpan> = {
type Row = (String, String, i64, i64, String, String);
let db = state.db.lock().map_err(|e| e.to_string())?;
let raw: Vec<Row> = db
.prepare(
"SELECT id, pii_type, start_offset, end_offset, original_value, replacement \
FROM pii_spans WHERE log_file_id = ?1 ORDER BY start_offset ASC",
)
.and_then(|mut stmt| {
stmt.query_map([&log_file_id], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, i64>(2)?,
row.get::<_, i64>(3)?,
row.get::<_, String>(4)?,
row.get::<_, String>(5)?,
))
})
.map(|rows| rows.filter_map(|r| r.ok()).collect::<Vec<_>>())
})
.unwrap_or_default();
drop(db);
raw.into_iter()
2026-03-15 17:43:46 +00:00
.map(
|(id, pii_type, start, end, original, replacement)| pii::PiiSpan {
id,
pii_type,
start: start as usize,
end: end as usize,
original,
replacement,
},
)
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
.filter(|span| approved_span_ids.contains(&span.id))
.collect()
};
// Apply redactions using the redactor module
let redacted_text = pii::apply_redactions(&content, &spans);
let data_hash = pii::hash_content(&redacted_text);
// Save redacted file alongside original
let redacted_path = format!("{file_path}.redacted");
std::fs::write(&redacted_path, &redacted_text)
.map_err(|_| "Failed to write redacted output file".to_string())?;
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
// Mark the log file as redacted in DB
{
let db = state.db.lock().map_err(|e| e.to_string())?;
db.execute(
"UPDATE log_files SET redacted = 1 WHERE id = ?1",
[&log_file_id],
)
.map_err(|_| "Failed to mark file as redacted".to_string())?;
db.execute(
"UPDATE pii_spans SET original_value = '' WHERE log_file_id = ?1",
[&log_file_id],
)
.map_err(|_| "Failed to finalize redaction metadata".to_string())?;
feat: initial implementation of TFTSR IT Triage & RCA application Implements Phases 1-8 of the TFTSR implementation plan. Rust backend (Tauri 2.x, src-tauri/): - Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama - PII detection engine: 11 regex patterns with overlap resolution - SQLCipher AES-256 encrypted database with 10 versioned migrations - 28 Tauri IPC commands for triage, analysis, document, and system ops - Ollama: hardware probe, model recommendations, pull/delete with events - RCA and blameless post-mortem Markdown document generators - PDF export via printpdf - Audit log: SHA-256 hash of every external data send - Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2) Frontend (React 18 + TypeScript + Vite, src/): - 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings - 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives - 3 Zustand stores: session, settings (persisted), history - Type-safe tauriCommands.ts matching Rust backend types exactly - 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs) DevOps: - .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push - .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload Verified: - cargo check: zero errors - tsc --noEmit: zero errors - vitest run: 13/13 unit tests passing Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 03:36:25 +00:00
}
Ok(RedactedLogFile {
log_file_id,
redacted_text,
data_hash,
})
}
#[tauri::command]
pub async fn get_log_file_content(
log_file_id: String,
state: State<'_, AppState>,
) -> Result<String, String> {
let row: (Option<Vec<u8>>, String) = {
let db = state.db.lock().map_err(|e| e.to_string())?;
db.prepare("SELECT content_compressed, file_path FROM log_files WHERE id = ?1")
.and_then(|mut stmt| {
stmt.query_row([&log_file_id], |row| Ok((row.get(0)?, row.get(1)?)))
})
.map_err(|_| "Log file not found".to_string())?
};
let (compressed, file_path) = row;
if let Some(bytes) = compressed {
return decompress_text(&bytes);
}
std::fs::read_to_string(&file_path).map_err(|e| format!("Log file not found on disk: {e}"))
}
#[tauri::command]
pub async fn list_all_log_files(
search: Option<String>,
issue_id: Option<String>,
state: State<'_, AppState>,
) -> Result<Vec<LogFileSummary>, String> {
let db = state.db.lock().map_err(|e| e.to_string())?;
let mut query = "SELECT id, issue_id, file_name, file_path, file_size, mime_type, content_hash, uploaded_at, redacted, issue_title \
FROM v_log_files_with_issue WHERE 1=1"
.to_string();
let mut params: Vec<String> = Vec::new();
if let Some(ref q) = search {
query.push_str(" AND file_name LIKE ?");
params.push(format!("%{q}%"));
}
if let Some(ref id) = issue_id {
query.push_str(" AND issue_id = ?");
params.push(id.clone());
}
query.push_str(" ORDER BY uploaded_at DESC");
let mut stmt = db.prepare(&query).map_err(|e| e.to_string())?;
let results = stmt
.query_map(rusqlite::params_from_iter(params.iter()), |row| {
Ok(LogFileSummary {
id: row.get(0)?,
issue_id: row.get(1)?,
file_name: row.get(2)?,
file_path: row.get(3)?,
file_size: row.get(4)?,
mime_type: row.get(5)?,
content_hash: row.get(6)?,
uploaded_at: row.get(7)?,
redacted: row.get::<_, i32>(8)? != 0,
issue_title: row.get(9)?,
})
})
.map_err(|e| e.to_string())?
.filter_map(|r| r.ok())
.collect();
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_log_file_path_rejects_non_file() {
let dir = std::env::temp_dir();
let result = validate_log_file_path(dir.to_string_lossy().as_ref());
assert!(result.is_err());
}
#[test]
fn test_validate_log_file_path_accepts_small_file() {
let file_path =
std::env::temp_dir().join(format!("trcaa-analysis-test-{}.log", uuid::Uuid::now_v7()));
std::fs::write(&file_path, "hello").unwrap();
let result = validate_log_file_path(file_path.to_string_lossy().as_ref());
assert!(result.is_ok());
let _ = std::fs::remove_file(file_path);
}
#[test]
fn test_is_safe_file_allows_txt() {
assert!(is_safe_file(Path::new("file.txt")));
}
#[test]
fn test_is_safe_file_allows_md() {
assert!(is_safe_file(Path::new("readme.md")));
}
#[test]
fn test_is_safe_file_allows_pdf() {
assert!(is_safe_file(Path::new("report.pdf")));
}
#[test]
fn test_is_safe_file_allows_docx() {
assert!(is_safe_file(Path::new("doc.docx")));
}
#[test]
fn test_is_safe_file_rejects_exe() {
assert!(!is_safe_file(Path::new("malware.exe")));
}
#[test]
fn test_is_safe_file_rejects_dll() {
assert!(!is_safe_file(Path::new("library.dll")));
}
#[test]
fn test_is_safe_file_rejects_zip_directly() {
assert!(!is_safe_file(Path::new("archive.zip")));
}
#[test]
fn test_is_safe_file_case_insensitive() {
assert!(is_safe_file(Path::new("file.TXT")));
assert!(is_safe_file(Path::new("file.Log")));
}
#[test]
fn test_is_safe_file_no_extension_rejected() {
assert!(!is_safe_file(Path::new("Makefile")));
}
#[test]
fn test_extract_text_plain_file() {
let dir = std::env::temp_dir();
let path = dir.join(format!("trcaa-test-extract-{}.txt", uuid::Uuid::now_v7()));
std::fs::write(&path, "hello world").unwrap();
let result = extract_text_content(&path);
assert!(result.is_ok());
assert_eq!(result.unwrap().trim(), "hello world");
let _ = std::fs::remove_file(path);
}
#[test]
fn test_extract_text_unsupported_binary_returns_error() {
let result = extract_text_content(Path::new("data.xlsx"));
assert!(result.is_err());
assert!(result.unwrap_err().contains("not yet supported"));
}
#[test]
fn test_compress_decompress_roundtrip() {
let original = "Hello, World! This is a log line with some content.";
let compressed = compress_text(original).unwrap();
assert!(!compressed.is_empty());
assert!(compressed.len() < original.len() * 3);
let decompressed = decompress_text(&compressed).unwrap();
assert_eq!(decompressed, original);
}
#[test]
fn test_compress_returns_error_not_empty_on_failure() {
// compress_text returns Result — callers must propagate, not silently discard.
// For in-memory gzip this essentially never fails, but the API now allows
// callers to surface the error rather than storing empty bytes.
let result = compress_text("normal log line");
assert!(
result.is_ok(),
"compress_text should succeed for normal input"
);
assert!(!result.unwrap().is_empty());
}
#[test]
fn test_compress_large_text_is_smaller() {
let original = "INFO server started\n".repeat(1000);
let compressed = compress_text(&original).unwrap();
assert!(
compressed.len() < original.len(),
"gzip should compress repetitive text"
);
}
#[test]
fn test_decompress_invalid_bytes_returns_error() {
let result = decompress_text(b"not gzip data");
assert!(result.is_err());
}
#[test]
fn test_decompress_size_limit_enforced() {
assert_eq!(
MAX_DECOMPRESSED_BYTES,
100 * 1024 * 1024,
"Decompression bomb guard must be 100 MB"
);
// A valid small payload must still decompress fine after the guard is in place.
let text = "hello world decompression guard test\n".repeat(100);
let compressed = compress_text(&text).unwrap();
let result = decompress_text(&compressed);
assert!(result.is_ok());
assert_eq!(result.unwrap(), text);
}
#[test]
fn test_pcap_files_recognized_as_safe_binary() {
use std::path::Path;
assert!(is_safe_file(Path::new("capture.pcap")));
assert!(is_safe_file(Path::new("network.pcapng")));
assert!(is_safe_file(Path::new("traffic.cap")));
assert!(!is_safe_file(Path::new("malicious.exe")));
}
#[test]
fn test_pcap_mime_types() {
use std::path::Path;
// This test verifies that pcap files get proper MIME types in upload_log_file
// The actual MIME type mapping is in the upload_log_file function
// We're testing that the extension detection works correctly
let pcap_path = Path::new("test.pcap");
let pcapng_path = Path::new("test.pcapng");
let cap_path = Path::new("test.cap");
assert_eq!(pcap_path.extension().and_then(|e| e.to_str()), Some("pcap"));
assert_eq!(
pcapng_path.extension().and_then(|e| e.to_str()),
Some("pcapng")
);
assert_eq!(cap_path.extension().and_then(|e| e.to_str()), Some("cap"));
}
#[test]
fn test_extract_pcap_requires_external_tool() {
use std::path::Path;
// extract_pcap_text requires tshark or tcpdump to be installed
// This test verifies that the function returns appropriate error when neither is available
// Note: We can't test actual extraction without mock/fixture files and installed tools
// Verify that trying to extract from a non-existent file returns an error
let fake_path = Path::new("/tmp/nonexistent_test_file.pcap");
let result = extract_pcap_text(fake_path);
// Should fail because file doesn't exist (and possibly no tshark/tcpdump)
assert!(result.is_err());
// Error message should mention either tshark failure or missing tools
let error_msg = result.unwrap_err();
assert!(
error_msg.contains("tshark")
|| error_msg.contains("tcpdump")
|| error_msg.contains("Neither")
|| error_msg.contains("No such file"),
"Expected error about missing tools or file, got: {}",
error_msg
);
}
}