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>
113 lines
2.5 KiB
JavaScript
113 lines
2.5 KiB
JavaScript
'use strict'
|
|
|
|
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors')
|
|
const DecoratorHandler = require('../handler/decorator-handler')
|
|
|
|
class DumpHandler extends DecoratorHandler {
|
|
#maxSize = 1024 * 1024
|
|
#dumped = false
|
|
#size = 0
|
|
#controller = null
|
|
aborted = false
|
|
reason = false
|
|
|
|
constructor ({ maxSize, signal }, handler) {
|
|
if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) {
|
|
throw new InvalidArgumentError('maxSize must be a number greater than 0')
|
|
}
|
|
|
|
super(handler)
|
|
|
|
this.#maxSize = maxSize ?? this.#maxSize
|
|
// this.#handler = handler
|
|
}
|
|
|
|
#abort (reason) {
|
|
this.aborted = true
|
|
this.reason = reason
|
|
}
|
|
|
|
onRequestStart (controller, context) {
|
|
controller.abort = this.#abort.bind(this)
|
|
this.#controller = controller
|
|
|
|
return super.onRequestStart(controller, context)
|
|
}
|
|
|
|
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
const contentLength = headers['content-length']
|
|
|
|
if (contentLength != null && contentLength > this.#maxSize) {
|
|
throw new RequestAbortedError(
|
|
`Response size (${contentLength}) larger than maxSize (${
|
|
this.#maxSize
|
|
})`
|
|
)
|
|
}
|
|
|
|
if (this.aborted === true) {
|
|
return true
|
|
}
|
|
|
|
return super.onResponseStart(controller, statusCode, headers, statusMessage)
|
|
}
|
|
|
|
onResponseError (controller, err) {
|
|
if (this.#dumped) {
|
|
return
|
|
}
|
|
|
|
// On network errors before connect, controller will be null
|
|
err = this.#controller?.reason ?? err
|
|
|
|
super.onResponseError(controller, err)
|
|
}
|
|
|
|
onResponseData (controller, chunk) {
|
|
this.#size = this.#size + chunk.length
|
|
|
|
if (this.#size >= this.#maxSize) {
|
|
this.#dumped = true
|
|
|
|
if (this.aborted === true) {
|
|
super.onResponseError(controller, this.reason)
|
|
} else {
|
|
super.onResponseEnd(controller, {})
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
onResponseEnd (controller, trailers) {
|
|
if (this.#dumped) {
|
|
return
|
|
}
|
|
|
|
if (this.#controller.aborted === true) {
|
|
super.onResponseError(controller, this.reason)
|
|
return
|
|
}
|
|
|
|
super.onResponseEnd(controller, trailers)
|
|
}
|
|
}
|
|
|
|
function createDumpInterceptor (
|
|
{ maxSize: defaultMaxSize } = {
|
|
maxSize: 1024 * 1024
|
|
}
|
|
) {
|
|
return dispatch => {
|
|
return function Intercept (opts, handler) {
|
|
const { dumpMaxSize = defaultMaxSize } = opts
|
|
|
|
const dumpHandler = new DumpHandler({ maxSize: dumpMaxSize, signal: opts.signal }, handler)
|
|
|
|
return dispatch(opts, dumpHandler)
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = createDumpInterceptor
|