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 crate::db::models::{
|
2026-03-31 17:50:39 +00:00
|
|
|
AiConversation, AiMessage, Issue, IssueDetail, IssueFilter, IssueSummary, IssueUpdate, LogFile,
|
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
|
|
|
ResolutionStep,
|
|
|
|
|
};
|
|
|
|
|
use crate::state::AppState;
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn create_issue(
|
|
|
|
|
title: String,
|
|
|
|
|
description: String,
|
|
|
|
|
severity: String,
|
|
|
|
|
category: String,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<Issue, String> {
|
|
|
|
|
let issue = Issue::new(title, description, severity, category);
|
|
|
|
|
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
db.execute(
|
|
|
|
|
"INSERT INTO issues (id, title, description, severity, status, category, source, created_at, updated_at, resolved_at, assigned_to, tags) \
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
|
|
|
|
|
rusqlite::params![
|
|
|
|
|
issue.id,
|
|
|
|
|
issue.title,
|
|
|
|
|
issue.description,
|
|
|
|
|
issue.severity,
|
|
|
|
|
issue.status,
|
|
|
|
|
issue.category,
|
|
|
|
|
issue.source,
|
|
|
|
|
issue.created_at,
|
|
|
|
|
issue.updated_at,
|
|
|
|
|
issue.resolved_at,
|
|
|
|
|
issue.assigned_to,
|
|
|
|
|
issue.tags,
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
Ok(issue)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn get_issue(
|
|
|
|
|
issue_id: String,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<IssueDetail, String> {
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
// Load issue
|
|
|
|
|
let mut stmt = db
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT id, title, description, severity, status, category, source, \
|
|
|
|
|
created_at, updated_at, resolved_at, assigned_to, tags \
|
|
|
|
|
FROM issues WHERE id = ?1",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
let issue = stmt
|
|
|
|
|
.query_row([&issue_id], |row| {
|
|
|
|
|
Ok(Issue {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
title: row.get(1)?,
|
|
|
|
|
description: row.get(2)?,
|
|
|
|
|
severity: row.get(3)?,
|
|
|
|
|
status: row.get(4)?,
|
|
|
|
|
category: row.get(5)?,
|
|
|
|
|
source: row.get(6)?,
|
|
|
|
|
created_at: row.get(7)?,
|
|
|
|
|
updated_at: row.get(8)?,
|
|
|
|
|
resolved_at: row.get(9)?,
|
|
|
|
|
assigned_to: row.get(10)?,
|
|
|
|
|
tags: row.get(11)?,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
// Load log files
|
|
|
|
|
let mut lf_stmt = db
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT id, issue_id, file_name, file_path, file_size, mime_type, content_hash, uploaded_at, redacted \
|
|
|
|
|
FROM log_files WHERE issue_id = ?1 ORDER BY uploaded_at ASC",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
let log_files: Vec<LogFile> = lf_stmt
|
|
|
|
|
.query_map([&issue_id], |row| {
|
|
|
|
|
Ok(LogFile {
|
|
|
|
|
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,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())?
|
|
|
|
|
.filter_map(|r| r.ok())
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
// Load resolution steps (5-whys)
|
|
|
|
|
let mut rs_stmt = db
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT id, issue_id, step_order, why_question, answer, evidence, created_at \
|
|
|
|
|
FROM resolution_steps WHERE issue_id = ?1 ORDER BY step_order ASC",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
let resolution_steps: Vec<ResolutionStep> = rs_stmt
|
|
|
|
|
.query_map([&issue_id], |row| {
|
|
|
|
|
Ok(ResolutionStep {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
issue_id: row.get(1)?,
|
|
|
|
|
step_order: row.get(2)?,
|
|
|
|
|
why_question: row.get(3)?,
|
|
|
|
|
answer: row.get(4)?,
|
|
|
|
|
evidence: row.get(5)?,
|
|
|
|
|
created_at: row.get(6)?,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())?
|
|
|
|
|
.filter_map(|r| r.ok())
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
// Load conversations
|
|
|
|
|
let mut conv_stmt = db
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT id, issue_id, provider, model, created_at, title \
|
|
|
|
|
FROM ai_conversations WHERE issue_id = ?1 ORDER BY created_at ASC",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
let conversations: Vec<AiConversation> = conv_stmt
|
|
|
|
|
.query_map([&issue_id], |row| {
|
|
|
|
|
Ok(AiConversation {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
issue_id: row.get(1)?,
|
|
|
|
|
provider: row.get(2)?,
|
|
|
|
|
model: row.get(3)?,
|
|
|
|
|
created_at: row.get(4)?,
|
|
|
|
|
title: row.get(5)?,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())?
|
|
|
|
|
.filter_map(|r| r.ok())
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
Ok(IssueDetail {
|
|
|
|
|
issue,
|
|
|
|
|
log_files,
|
|
|
|
|
resolution_steps,
|
|
|
|
|
conversations,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn update_issue(
|
|
|
|
|
issue_id: String,
|
|
|
|
|
updates: IssueUpdate,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<Issue, String> {
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
|
|
|
|
|
|
|
|
if let Some(ref title) = updates.title {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET title = ?1, updated_at = ?2 WHERE id = ?3",
|
|
|
|
|
rusqlite::params![title, now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref description) = updates.description {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET description = ?1, updated_at = ?2 WHERE id = ?3",
|
|
|
|
|
rusqlite::params![description, now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref severity) = updates.severity {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET severity = ?1, updated_at = ?2 WHERE id = ?3",
|
|
|
|
|
rusqlite::params![severity, now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref status) = updates.status {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET status = ?1, updated_at = ?2 WHERE id = ?3",
|
|
|
|
|
rusqlite::params![status, now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
if status == "resolved" {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET resolved_at = ?1 WHERE id = ?2",
|
|
|
|
|
rusqlite::params![now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref category) = updates.category {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET category = ?1, updated_at = ?2 WHERE id = ?3",
|
|
|
|
|
rusqlite::params![category, now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref assigned_to) = updates.assigned_to {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET assigned_to = ?1, updated_at = ?2 WHERE id = ?3",
|
|
|
|
|
rusqlite::params![assigned_to, now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref tags) = updates.tags {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET tags = ?1, updated_at = ?2 WHERE id = ?3",
|
|
|
|
|
rusqlite::params![tags, now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch and return updated issue
|
|
|
|
|
let mut stmt = db
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT id, title, description, severity, status, category, source, \
|
|
|
|
|
created_at, updated_at, resolved_at, assigned_to, tags \
|
|
|
|
|
FROM issues WHERE id = ?1",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
stmt.query_row([&issue_id], |row| {
|
|
|
|
|
Ok(Issue {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
title: row.get(1)?,
|
|
|
|
|
description: row.get(2)?,
|
|
|
|
|
severity: row.get(3)?,
|
|
|
|
|
status: row.get(4)?,
|
|
|
|
|
category: row.get(5)?,
|
|
|
|
|
source: row.get(6)?,
|
|
|
|
|
created_at: row.get(7)?,
|
|
|
|
|
updated_at: row.get(8)?,
|
|
|
|
|
resolved_at: row.get(9)?,
|
|
|
|
|
assigned_to: row.get(10)?,
|
|
|
|
|
tags: row.get(11)?,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
2026-03-15 17:43:46 +00:00
|
|
|
pub async fn delete_issue(issue_id: String, state: State<'_, AppState>) -> Result<(), 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 db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
// Delete related records (CASCADE should handle this, but be explicit)
|
|
|
|
|
db.execute("DELETE FROM ai_messages WHERE conversation_id IN (SELECT id FROM ai_conversations WHERE issue_id = ?1)", [&issue_id])
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
2026-03-15 17:43:46 +00:00
|
|
|
db.execute(
|
|
|
|
|
"DELETE FROM ai_conversations WHERE issue_id = ?1",
|
|
|
|
|
[&issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
db.execute(
|
|
|
|
|
"DELETE FROM pii_spans WHERE log_file_id IN (SELECT id FROM log_files WHERE issue_id = ?1)",
|
|
|
|
|
[&issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.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
|
|
|
db.execute("DELETE FROM log_files WHERE issue_id = ?1", [&issue_id])
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
2026-03-15 17:43:46 +00:00
|
|
|
db.execute(
|
|
|
|
|
"DELETE FROM resolution_steps WHERE issue_id = ?1",
|
|
|
|
|
[&issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.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
|
|
|
db.execute("DELETE FROM issues WHERE id = ?1", [&issue_id])
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn list_issues(
|
|
|
|
|
filter: IssueFilter,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<Vec<IssueSummary>, String> {
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
let limit = filter.limit.unwrap_or(50);
|
|
|
|
|
let offset = filter.offset.unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
let mut sql = String::from(
|
|
|
|
|
"SELECT i.id, i.title, i.severity, i.status, i.category, i.created_at, i.updated_at, \
|
|
|
|
|
(SELECT COUNT(*) FROM log_files lf WHERE lf.issue_id = i.id) as log_count, \
|
|
|
|
|
(SELECT COUNT(*) FROM resolution_steps rs WHERE rs.issue_id = i.id) as step_count \
|
|
|
|
|
FROM issues i WHERE 1=1",
|
|
|
|
|
);
|
|
|
|
|
let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = vec![];
|
|
|
|
|
|
|
|
|
|
if let Some(ref status) = filter.status {
|
|
|
|
|
sql.push_str(&format!(" AND i.status = ?{}", params.len() + 1));
|
|
|
|
|
params.push(Box::new(status.clone()));
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref severity) = filter.severity {
|
|
|
|
|
sql.push_str(&format!(" AND i.severity = ?{}", params.len() + 1));
|
|
|
|
|
params.push(Box::new(severity.clone()));
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref category) = filter.category {
|
|
|
|
|
sql.push_str(&format!(" AND i.category = ?{}", params.len() + 1));
|
|
|
|
|
params.push(Box::new(category.clone()));
|
|
|
|
|
}
|
|
|
|
|
if let Some(ref search) = filter.search {
|
2026-03-15 18:28:59 +00:00
|
|
|
let pattern = format!("%{search}%");
|
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
|
|
|
sql.push_str(&format!(
|
|
|
|
|
" AND (i.title LIKE ?{0} OR i.description LIKE ?{0} OR i.category LIKE ?{0})",
|
|
|
|
|
params.len() + 1
|
|
|
|
|
));
|
|
|
|
|
params.push(Box::new(pattern));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sql.push_str(" ORDER BY i.updated_at DESC");
|
|
|
|
|
sql.push_str(&format!(
|
|
|
|
|
" LIMIT ?{} OFFSET ?{}",
|
|
|
|
|
params.len() + 1,
|
|
|
|
|
params.len() + 2
|
|
|
|
|
));
|
|
|
|
|
params.push(Box::new(limit));
|
|
|
|
|
params.push(Box::new(offset));
|
|
|
|
|
|
|
|
|
|
let param_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
|
|
|
|
|
|
|
|
|
|
let mut stmt = db.prepare(&sql).map_err(|e| e.to_string())?;
|
|
|
|
|
let issues = stmt
|
|
|
|
|
.query_map(param_refs.as_slice(), |row| {
|
|
|
|
|
Ok(IssueSummary {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
title: row.get(1)?,
|
|
|
|
|
severity: row.get(2)?,
|
|
|
|
|
status: row.get(3)?,
|
|
|
|
|
category: row.get(4)?,
|
|
|
|
|
created_at: row.get(5)?,
|
|
|
|
|
updated_at: row.get(6)?,
|
|
|
|
|
log_count: row.get(7)?,
|
|
|
|
|
step_count: row.get(8)?,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())?
|
|
|
|
|
.filter_map(|r| r.ok())
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
Ok(issues)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn search_issues(
|
|
|
|
|
query: String,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<Vec<IssueSummary>, String> {
|
|
|
|
|
let filter = IssueFilter {
|
|
|
|
|
search: Some(query),
|
|
|
|
|
limit: Some(50),
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
list_issues(filter, state).await
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 17:50:39 +00:00
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn get_issue_messages(
|
|
|
|
|
issue_id: String,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<Vec<AiMessage>, String> {
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
let mut stmt = db
|
|
|
|
|
.prepare(
|
|
|
|
|
"SELECT am.id, am.conversation_id, am.role, am.content, am.token_count, am.created_at \
|
|
|
|
|
FROM ai_messages am \
|
|
|
|
|
JOIN ai_conversations ac ON ac.id = am.conversation_id \
|
|
|
|
|
WHERE ac.issue_id = ?1 \
|
|
|
|
|
ORDER BY am.created_at ASC",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
let messages = stmt
|
|
|
|
|
.query_map([&issue_id], |row| {
|
|
|
|
|
Ok(AiMessage {
|
|
|
|
|
id: row.get(0)?,
|
|
|
|
|
conversation_id: row.get(1)?,
|
|
|
|
|
role: row.get(2)?,
|
|
|
|
|
content: row.get(3)?,
|
|
|
|
|
token_count: row.get(4)?,
|
|
|
|
|
created_at: row.get(5)?,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map_err(|e| e.to_string())?
|
|
|
|
|
.filter_map(|r| r.ok())
|
|
|
|
|
.collect();
|
|
|
|
|
Ok(messages)
|
|
|
|
|
}
|
|
|
|
|
|
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 add_five_why(
|
|
|
|
|
issue_id: String,
|
|
|
|
|
step_order: i64,
|
|
|
|
|
why_question: String,
|
|
|
|
|
answer: String,
|
|
|
|
|
evidence: String,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<ResolutionStep, String> {
|
2026-03-15 17:43:46 +00:00
|
|
|
let step = ResolutionStep::new(issue_id.clone(), step_order, why_question, answer, evidence);
|
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 resolution_steps (id, issue_id, step_order, why_question, answer, evidence, created_at) \
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
|
|
|
|
rusqlite::params![
|
|
|
|
|
step.id,
|
|
|
|
|
step.issue_id,
|
|
|
|
|
step.step_order,
|
|
|
|
|
step.why_question,
|
|
|
|
|
step.answer,
|
|
|
|
|
step.evidence,
|
|
|
|
|
step.created_at,
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
// Update issue timestamp
|
|
|
|
|
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET updated_at = ?1 WHERE id = ?2",
|
|
|
|
|
rusqlite::params![now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
Ok(step)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn update_five_why(
|
|
|
|
|
step_id: String,
|
|
|
|
|
answer: String,
|
|
|
|
|
evidence: Option<String>,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<(), String> {
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
if let Some(ref ev) = evidence {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE resolution_steps SET answer = ?1, evidence = ?2 WHERE id = ?3",
|
|
|
|
|
rusqlite::params![answer, ev, step_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
} else {
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE resolution_steps SET answer = ?1 WHERE id = ?2",
|
|
|
|
|
rusqlite::params![answer, step_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn add_timeline_event(
|
|
|
|
|
issue_id: String,
|
|
|
|
|
event_type: String,
|
|
|
|
|
description: String,
|
|
|
|
|
state: State<'_, AppState>,
|
|
|
|
|
) -> Result<(), String> {
|
|
|
|
|
// Use audit_log for timeline tracking
|
|
|
|
|
let db = state.db.lock().map_err(|e| e.to_string())?;
|
|
|
|
|
let entry = crate::db::models::AuditEntry::new(
|
|
|
|
|
event_type,
|
|
|
|
|
"issue".to_string(),
|
|
|
|
|
issue_id.clone(),
|
|
|
|
|
serde_json::json!({ "description": description }).to_string(),
|
|
|
|
|
);
|
|
|
|
|
db.execute(
|
|
|
|
|
"INSERT INTO audit_log (id, timestamp, action, entity_type, entity_id, user_id, details) \
|
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
|
|
|
|
rusqlite::params![
|
2026-03-15 17:43:46 +00:00
|
|
|
entry.id,
|
|
|
|
|
entry.timestamp,
|
|
|
|
|
entry.action,
|
|
|
|
|
entry.entity_type,
|
|
|
|
|
entry.entity_id,
|
|
|
|
|
entry.user_id,
|
|
|
|
|
entry.details
|
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(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
// Update issue timestamp
|
|
|
|
|
let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
|
|
|
db.execute(
|
|
|
|
|
"UPDATE issues SET updated_at = ?1 WHERE id = ?2",
|
|
|
|
|
rusqlite::params![now, issue_id],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|