tftsr-devops_investigation/src/pages/LogUpload/index.tsx

471 lines
15 KiB
TypeScript
Raw Normal View History

import React, { useState, useCallback, useRef, useEffect } from "react";
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
import { useNavigate, useParams } from "react-router-dom";
import { Upload, File, Trash2, ShieldCheck, AlertTriangle, Image as ImageIcon } from "lucide-react";
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
import { Button, Card, CardHeader, CardTitle, CardContent, Badge } from "@/components/ui";
import { PiiDiffViewer } from "@/components/PiiDiffViewer";
import { useSessionStore } from "@/stores/sessionStore";
import {
uploadLogFileCmd,
detectPiiCmd,
uploadImageAttachmentCmd,
uploadPasteImageCmd,
uploadFileToDatastoreCmd,
listImageAttachmentsCmd,
deleteImageAttachmentCmd,
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
type LogFile,
type PiiSpan,
type PiiDetectionResult,
type ImageAttachment,
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
} from "@/lib/tauriCommands";
import ImageGallery from "@/components/ImageGallery";
import { open } from "@tauri-apps/plugin-dialog";
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
export default function LogUpload() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { piiSpans, approvedRedactions, setPiiSpans, setApprovedRedactions } = useSessionStore();
const [files, setFiles] = useState<{ file: File; uploaded?: LogFile }[]>([]);
const [images, setImages] = useState<ImageAttachment[]>([]);
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
const [piiResult, setPiiResult] = useState<PiiDetectionResult | null>(null);
const [isUploading, setIsUploading] = useState(false);
const [isDetecting, setIsDetecting] = useState(false);
const [error, setError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
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
const handleDrop = useCallback(
(e: React.DragEvent) => {
e.preventDefault();
const droppedFiles = Array.from(e.dataTransfer.files);
setFiles((prev) => [...prev, ...droppedFiles.map((f) => ({ file: f }))]);
},
[]
);
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
const selected = Array.from(e.target.files);
setFiles((prev) => [...prev, ...selected.map((f) => ({ file: f }))]);
}
};
const removeFile = (index: number) => {
setFiles((prev) => prev.filter((_, i) => i !== index));
};
const handleUpload = async () => {
if (!id || files.length === 0) return;
setIsUploading(true);
setError(null);
try {
const uploaded = await Promise.all(
files.map(async (entry) => {
if (entry.uploaded) return entry;
const content = await entry.file.text();
const logFile = await uploadLogFileCmd(id, entry.file.name);
return { ...entry, uploaded: logFile };
})
);
setFiles(uploaded);
} catch (err) {
setError(String(err));
} finally {
setIsUploading(false);
}
};
const handleDetectPii = async () => {
const allContent = files
.map((f) => f.uploaded?.file_name)
.filter(Boolean)
.join("\n---\n");
if (!allContent) return;
setIsDetecting(true);
setError(null);
try {
const result = files[0]?.uploaded ? await detectPiiCmd(files[0].uploaded.id) : null;
setPiiResult(result);
if (result) { setPiiSpans(result.detections);
setApprovedRedactions(result.detections); };
} catch (err) {
setError(String(err));
} finally {
setIsDetecting(false);
}
};
const handleToggleSpan = (span: PiiSpan, approved: boolean) => {
if (approved) {
setApprovedRedactions([...approvedRedactions, span]);
} else {
setApprovedRedactions(
approvedRedactions.filter(
(s) => !(s.start === span.start && s.end === span.end)
)
);
}
};
const handleImageDrop = useCallback(
async (e: React.DragEvent) => {
e.preventDefault();
// Use file dialog to get actual paths (drag-and-drop doesn't give paths in Tauri v2)
try {
const selected = await open({
multiple: true,
filters: [{ name: "Images", extensions: ["png", "jpg", "jpeg", "gif", "bmp", "webp"] }],
});
if (!selected) return;
const paths = Array.isArray(selected) ? selected : [selected];
if (paths.length > 0) {
handleImagesUpload(paths as unknown as File[]);
}
} catch (err) {
setError(`Attachment failed: ${String(err)}`);
}
},
[id]
);
const handleImageFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
try {
const selected = await open({
multiple: true,
filters: [{ name: "Images", extensions: ["png", "jpg", "jpeg", "gif", "bmp", "webp"] }],
});
if (selected) {
const paths = Array.isArray(selected) ? selected : [selected];
if (paths.length > 0) {
handleImagesUpload(paths as unknown as File[]);
}
}
} catch (err) {
setError(`Attachment failed: ${String(err)}`);
}
}
};
const handlePaste = useCallback(
async (e: React.ClipboardEvent) => {
const items = e.clipboardData?.items;
const imageItems = items ? Array.from(items).filter((item: DataTransferItem) => item.type.startsWith("image/")) : [];
for (const item of imageItems) {
const file = item.getAsFile();
if (file) {
const reader = new FileReader();
reader.onload = async () => {
const base64Data = reader.result as string;
try {
const result = await uploadPasteImageCmd(id || "", base64Data, file.type);
setImages((prev) => [...prev, result]);
} catch (err) {
setError(String(err));
}
};
reader.readAsDataURL(file);
}
}
},
[id]
);
const handleImagesUpload = async (imageFiles: File[]) => {
if (!id || imageFiles.length === 0) return;
setIsUploading(true);
setError(null);
try {
const uploaded: ImageAttachment[] = await Promise.all(
imageFiles.map(async (file) => {
// Extract the path from the file name (which contains the full path from file dialog)
const filePath = file.name;
// Get just the filename for display
const fileName = filePath.split(/[\/\\]/).pop() || "unknown";
// Use uploadLogFileCmd which properly handles file paths
const result = await uploadLogFileCmd(id, filePath);
// Convert LogFile to ImageAttachment for the state
return {
id: result.id,
issue_id: result.issue_id,
file_name: result.file_name,
file_path: result.file_path,
file_size: result.file_size,
mime_type: result.mime_type,
upload_hash: result.content_hash,
uploaded_at: result.uploaded_at,
pii_warning_acknowledged: true,
is_paste: false,
};
})
);
setImages((prev) => [...prev, ...uploaded]);
} catch (err) {
setError(String(err));
} finally {
setIsUploading(false);
}
};
const handleDeleteImage = async (image: ImageAttachment) => {
try {
await deleteImageAttachmentCmd(image.id);
setImages((prev) => prev.filter((img) => img.id !== image.id));
} catch (err) {
setError(String(err));
}
};
const fileToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = (err) => reject(err);
reader.readAsDataURL(file);
});
};
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
const allUploaded = files.length > 0 && files.every((f) => f.uploaded);
const piiReviewed = piiResult != null;
useEffect(() => {
const handleGlobalPaste = (e: ClipboardEvent) => {
if (document.activeElement?.tagName === "INPUT" ||
document.activeElement?.tagName === "TEXTAREA" ||
(document.activeElement as HTMLElement)?.isContentEditable || false) {
return;
}
const items = e.clipboardData?.items;
const imageItems = items ? Array.from(items).filter((item: DataTransferItem) => item.type.startsWith("image/")) : [];
for (const item of imageItems) {
const file = item.getAsFile();
if (file) {
e.preventDefault();
const reader = new FileReader();
reader.onload = async () => {
const base64Data = reader.result as string;
try {
const result = await uploadPasteImageCmd(id || "", base64Data, file.type);
setImages((prev) => [...prev, result]);
} catch (err) {
setError(String(err));
}
};
reader.readAsDataURL(file);
break;
}
}
};
window.addEventListener("paste", handleGlobalPaste);
return () => window.removeEventListener("paste", handleGlobalPaste);
}, [id]);
useEffect(() => {
if (id) {
listImageAttachmentsCmd(id).then(setImages).catch(setError);
}
}, [id]);
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
return (
<div className="p-6 space-y-6">
<div>
<h1 className="text-3xl font-bold">Upload Logs</h1>
<p className="text-muted-foreground mt-1">
Upload log files for PII detection and redaction before triage.
</p>
</div>
{/* Drop zone */}
<div
onDragOver={(e) => e.preventDefault()}
onDrop={handleDrop}
className="border-2 border-dashed rounded-lg p-8 text-center hover:border-primary transition-colors cursor-pointer"
onClick={() => document.getElementById("file-input")?.click()}
>
<Upload className="w-8 h-8 mx-auto text-muted-foreground mb-2" />
<p className="text-sm text-muted-foreground">
Drag and drop log files here, or click to browse
</p>
<input
id="file-input"
type="file"
multiple
className="hidden"
onChange={handleFileSelect}
accept=".log,.txt,.json,.csv,.xml,.yaml,.yml"
/>
</div>
{/* File list */}
{files.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="text-lg">Files ({files.length})</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{files.map((entry, idx) => (
<div key={idx} className="flex items-center justify-between rounded-md border p-2">
<div className="flex items-center gap-2">
<File className="w-4 h-4 text-muted-foreground" />
<span className="text-sm">{entry.file.name}</span>
<span className="text-xs text-muted-foreground">
({(entry.file.size / 1024).toFixed(1)} KB)
</span>
{entry.uploaded && (
<Badge variant="outline">Uploaded</Badge>
)}
</div>
<button
onClick={() => removeFile(idx)}
className="text-muted-foreground hover:text-destructive"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
))}
{!allUploaded && (
<Button onClick={handleUpload} disabled={isUploading} className="mt-2">
{isUploading ? "Uploading..." : "Upload Files"}
</Button>
)}
</CardContent>
</Card>
)}
{/* Image Upload */}
{id && (
<>
<div>
<h2 className="text-2xl font-semibold flex items-center gap-2">
<ImageIcon className="w-6 h-6" />
Image Attachments
</h2>
<p className="text-muted-foreground mt-1">
Upload or paste screenshots and images.
</p>
</div>
{/* Image drop zone */}
<div
onDragOver={(e) => e.preventDefault()}
onDrop={handleImageDrop}
className="border-2 border-dashed border-primary/30 rounded-lg p-8 text-center hover:border-primary transition-colors cursor-pointer bg-primary/5"
onClick={() => document.getElementById("image-input")?.click()}
>
<Upload className="w-8 h-8 mx-auto text-primary mb-2" />
<p className="text-sm text-muted-foreground">
Drag and drop images here, or click to browse
</p>
<p className="text-xs text-muted-foreground mt-2">
Supported: PNG, JPEG, GIF, WebP, SVG
</p>
<input
id="image-input"
type="file"
accept="image/*"
className="hidden"
onChange={handleImageFileSelect}
/>
</div>
{/* Paste button */}
<div className="flex items-center gap-2">
<Button
onClick={async (e) => {
e.preventDefault();
document.execCommand("paste");
}}
variant="secondary"
>
Paste from Clipboard
</Button>
<span className="text-xs text-muted-foreground">
Use Ctrl+V / Cmd+V or the button above to paste images
</span>
</div>
{/* PII warning for images */}
<div className="bg-amber-50 border border-amber-200 rounded-md p-3">
<AlertTriangle className="w-5 h-5 text-amber-600 inline mr-2" />
<span className="text-sm text-amber-800">
PII cannot be automatically redacted from images. Use at your own risk.
</span>
</div>
{/* Image Gallery */}
{images.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
<ImageIcon className="w-5 h-5" />
Attached Images ({images.length})
</CardTitle>
</CardHeader>
<CardContent>
<ImageGallery
images={images}
onDelete={handleDeleteImage}
showWarning={false}
/>
</CardContent>
</Card>
)}
</>
)}
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
{/* PII Detection */}
{allUploaded && (
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
<ShieldCheck className="w-5 h-5" />
PII Detection
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{!piiResult && (
<Button onClick={handleDetectPii} disabled={isDetecting}>
{isDetecting ? "Detecting PII..." : "Detect PII"}
</Button>
)}
{piiResult && (
<PiiDiffViewer
originalText="[original log content]"
redactedText="[redacted log content]"
spans={piiSpans}
approvedSpans={approvedRedactions}
onToggleSpan={handleToggleSpan}
/>
)}
</CardContent>
</Card>
)}
{/* Error */}
{error && (
<div className="text-sm text-destructive bg-destructive/10 rounded-md p-3">
{error}
</div>
)}
{/* Continue */}
<Button
onClick={() => navigate(`/issue/${id}/triage`)}
disabled={!piiReviewed}
className="w-full"
size="lg"
>
Continue to Triage
</Button>
</div>
);
}