fix(health): suppress 404 when health monitor disabled + fix audit test assertion

- Sidebar.svelte: defer healthStore.refresh() until feature flags confirm
  health_monitor is enabled; skip entirely when disabled
- health.js: remove throw error from catch block — log and return null
  instead, preventing 'Uncaught (in promise)' on expected 404
- test_report_audit_immutability.py: fix mock assertions —
  audit_service uses logger.reason/reflect/explore, not logger.info
- HealthStore and Sidebar now produce zero network noise when
  FEATURES__HEALTH_MONITOR=false
This commit is contained in:
2026-05-17 14:18:02 +03:00
parent 58ac89c21e
commit cd868df261
141 changed files with 9631 additions and 10165 deletions

View File

@@ -72,6 +72,7 @@
const failingCountState = fromStore(failingCount);
let features = $state({});
let cleanupInterval = null;
let categories = $derived(
buildSidebarCategories(
@@ -195,21 +196,29 @@
});
onMount(() => {
healthStore.refresh();
// Refresh every 5 minutes
const interval = setInterval(() => healthStore.refresh(), 5 * 60 * 1000);
// Fetch feature flags for sidebar filtering
// Fetch feature flags first, then conditionally refresh health
fetchApi("/settings/features")
.then((res) => {
features = res;
if (res.health_monitor) {
healthStore.refresh();
// Refresh every 5 minutes
const interval = setInterval(() => healthStore.refresh(), 5 * 60 * 1000);
cleanupInterval = interval;
}
})
.catch(() => {
// Graceful degradation: default to {} (all features visible)
features = {};
// On error, try health anyway
healthStore.refresh();
const interval = setInterval(() => healthStore.refresh(), 5 * 60 * 1000);
cleanupInterval = interval;
});
return () => clearInterval(interval);
return () => {
if (cleanupInterval) clearInterval(cleanupInterval);
};
});
</script>

View File

@@ -1,23 +1,15 @@
<!-- #region BulkCorrectionSidebar [C:3] [TYPE Component] [SEMANTICS translate, correction, bulk, sidebar, dictionary] -->
<!-- @BRIEF Component component: lib/components/translate/BulkCorrectionSidebar.svelte -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [TranslateApi] -->
<!-- @RELATION DEPENDS_ON -> [dictionaryApi] -->
<!-- @UX_STATE closed -> Sidebar hidden -->
<!-- @UX_STATE collecting -> Items being collected, user can edit corrections -->
<!-- @UX_STATE reviewing -> User reviews collected corrections before submit -->
<!-- @UX_STATE submitting -> API call in progress -->
<!-- @UX_STATE submitted -> Success feedback -->
<!-- @UX_FEEDBACK Toast on success/error; count badge on toggle button -->
<!-- @UX_RECOVERY Remove individual items; retry submission -->
<script>
/**
* @PURPOSE: Sidebar for collecting multiple incorrect terms across different rows,
* per-term correction inputs, submit atomically to dictionary.
* @LAYER: UI
* @RELATION: DEPENDS_ON -> [TranslateApi]
* @RELATION: DEPENDS_ON -> [dictionaryApi]
*
* @UX_STATE: closed -> Sidebar hidden
* @UX_STATE: collecting -> Items being collected, user can edit corrections
* @UX_STATE: reviewing -> User reviews collected corrections before submit
* @UX_STATE: submitting -> API call in progress
* @UX_STATE: submitted -> Success feedback
*
* @UX_FEEDBACK: Toast on success/error; count badge on toggle button
* @UX_RECOVERY: Remove individual items; retry submission
*/
import { t } from '$lib/i18n';
import { addToast } from '$lib/toasts.js';
import { submitBulkCorrections, dictionaryApi } from '$lib/api/translate.js';

View File

@@ -1,21 +1,14 @@
<!-- #region ScheduleConfig [C:3] [TYPE Component] [SEMANTICS translate, schedule, cron, trigger, config] -->
<!-- @BRIEF Component component: lib/components/translate/ScheduleConfig.svelte -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [TranslateApi] -->
<!-- @UX_STATE idle -> No schedule configured -->
<!-- @UX_STATE editing -> Configuring schedule -->
<!-- @UX_STATE enabled -> Schedule active -->
<!-- @UX_STATE disabled -> Schedule inactive -->
<!-- @UX_STATE no_prior_run_warning -> No prior manual run, show warning -->
<!-- @UX_FEEDBACK Next-3-executions preview; toast on save/error -->
<!-- @UX_RECOVERY Enable/disable toggle; delete schedule -->
<script>
/**
* @PURPOSE: Schedule configuration for a translation job — cron/interval/once triggers, timezone, enable/disable.
* @LAYER: UI
* @RELATION: DEPENDS_ON -> [TranslateApi]
*
* @UX_STATE: idle -> No schedule configured
* @UX_STATE: editing -> Configuring schedule
* @UX_STATE: enabled -> Schedule active
* @UX_STATE: disabled -> Schedule inactive
* @UX_STATE: no_prior_run_warning -> No prior manual run, show warning
*
* @UX_FEEDBACK: Next-3-executions preview; toast on save/error
* @UX_RECOVERY: Enable/disable toggle; delete schedule
*/
import { t } from '$lib/i18n';
import { addToast } from '$lib/toasts.js';
import {

View File

@@ -1,23 +1,16 @@
<!-- #region TermCorrectionPopup [C:3] [TYPE Component] [SEMANTICS translate, correction, popup, dictionary, term] -->
<!-- @BRIEF Component component: lib/components/translate/TermCorrectionPopup.svelte -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [TranslateApi] -->
<!-- @RELATION BINDS_TO -> [dictionaryApi] -->
<!-- @UX_STATE closed -> Hidden -->
<!-- @UX_STATE selecting -> User selects target dictionary -->
<!-- @UX_STATE editing -> User enters corrected term -->
<!-- @UX_STATE submitting -> API call in progress -->
<!-- @UX_STATE conflict_detected -> Existing entry found, user chooses overwrite/keep -->
<!-- @UX_STATE submitted -> Success feedback -->
<!-- @UX_FEEDBACK Toast on success/error; conflict dialog with action buttons -->
<!-- @UX_RECOVERY Cancel button returns to previous state -->
<script>
/**
* @PURPOSE: Popup for submitting a term correction from a run result row.
* @LAYER: UI
* @RELATION: DEPENDS_ON -> [TranslateApi]
* @RELATION: BINDS_TO -> [dictionaryApi]
*
* @UX_STATE: closed -> Hidden
* @UX_STATE: selecting -> User selects target dictionary
* @UX_STATE: editing -> User enters corrected term
* @UX_STATE: submitting -> API call in progress
* @UX_STATE: conflict_detected -> Existing entry found, user chooses overwrite/keep
* @UX_STATE: submitted -> Success feedback
*
* @UX_FEEDBACK: Toast on success/error; conflict dialog with action buttons
* @UX_RECOVERY: Cancel button returns to previous state
*/
import { addToast } from '$lib/toasts.js';
import { submitCorrection, dictionaryApi } from '$lib/api/translate.js';
import { t, _ } from '$lib/i18n';

View File

@@ -1,21 +1,13 @@
<!-- #region TranslationMetricsDashboard [C:3] [TYPE Component] [SEMANTICS translate, metrics, dashboard, stats, cost] -->
<!-- @BRIEF Component component: lib/components/translate/TranslationMetricsDashboard.svelte -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [TranslateApi] -->
<!-- @UX_STATE loading -> Initial load -->
<!-- @UX_STATE loaded -> Metrics data displayed -->
<!-- @UX_STATE error -> Failed to load metrics -->
<!-- @UX_STATE empty -> No metrics data available -->
<!-- @UX_FEEDBACK Spinner during load; card grid for totals; table for per-job breakdown -->
<!-- @UX_RECOVERY Retry button on error -->
<script>
/**
* @PURPOSE: Admin metrics dashboard for translation — per-job run counts, success/failure ratio,
* cumulative tokens, cumulative cost, average latency.
* @LAYER: UI
* @RELATION: DEPENDS_ON -> [TranslateApi]
*
* @UX_STATE: loading -> Initial load
* @UX_STATE: loaded -> Metrics data displayed
* @UX_STATE: error -> Failed to load metrics
* @UX_STATE: empty -> No metrics data available
*
* @UX_FEEDBACK: Spinner during load; card grid for totals; table for per-job breakdown
* @UX_RECOVERY: Retry button on error
*/
import { t } from '$lib/i18n';
import { addToast } from '$lib/toasts.js';
import { fetchAllMetrics } from '$lib/api/translate.js';

View File

@@ -1,26 +1,16 @@
<!-- #region TranslationPreview [C:3] [TYPE Component] [SEMANTICS translate, preview, multi-language, approve, cost] -->
<!-- @BRIEF Component component: lib/components/translate/TranslationPreview.svelte -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [TranslateApi] -->
<!-- @RELATION DEPENDS_ON -> [api_module] -->
<!-- @UX_STATE idle -> Initial state, no preview loaded -->
<!-- @UX_STATE loading -> Preview API call in progress -->
<!-- @UX_STATE preview_loaded -> Preview data displayed with dynamic language columns -->
<!-- @UX_STATE preview_error -> Preview API call failed -->
<!-- @UX_STATE accepted -> Preview session accepted -->
<!-- @UX_STATE stale_config -> Configuration changed since preview was generated -->
<!-- @UX_FEEDBACK spinner during preview; visual distinction for LLM vs edited translations; cost warning for large previews. -->
<!-- @UX_RECOVERY retry preview; re-fetch with updated config. -->
<script>
/**
* @PURPOSE: Multi-language translation preview with dynamic per-language columns, approve/edit/reject, cost estimate, and accept gate.
* @LAYER: UI
* @RELATION: DEPENDS_ON -> [TranslateApi]
* @RELATION: DEPENDS_ON -> [api_module]
* @PRE: jobId is a valid translation job ID.
* @POST: User can preview, approve/edit/reject per-language rows, and accept the session to gate full execution.
*
* @UX_STATE: idle -> Initial state, no preview loaded
* @UX_STATE: loading -> Preview API call in progress
* @UX_STATE: preview_loaded -> Preview data displayed with dynamic language columns
* @UX_STATE: preview_error -> Preview API call failed
* @UX_STATE: accepted -> Preview session accepted
* @UX_STATE: stale_config -> Configuration changed since preview was generated
*
* @UX_FEEDBACK: spinner during preview; visual distinction for LLM vs edited translations; cost warning for large previews.
* @UX_RECOVERY: retry preview; re-fetch with updated config.
*/
import { t } from '$lib/i18n';
import { addToast } from '$lib/toasts.js';
import {

View File

@@ -1,6 +1,5 @@
<!-- #region TranslationRunGlobalIndicator [C:3] [TYPE Component] [SEMANTICS translate, progress, global, indicator] -->
<!-- @BRIEF Persistent rich progress panel for active translation runs, rendered in root layout. -->
<!-- @LAYER UI -->
<!-- @RELATION BINDS_TO -> [translationRunStore] -->
<!-- @UX_STATE hidden -> No active or recently completed run -->
<!-- @UX_STATE running -> Rich panel with stats grid + progress bar + cancel button -->

View File

@@ -1,28 +1,18 @@
<!-- #region TranslationRunProgress [C:3] [TYPE Component] [SEMANTICS translate, progress, websocket, batch, status] -->
<!-- @BRIEF Component component: lib/components/translate/TranslationRunProgress.svelte -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [TranslateApi] -->
<!-- @RELATION DEPENDS_ON -> [api_module] -->
<!-- @UX_STATE idle -> Initial state, no run loaded -->
<!-- @UX_STATE running -> Translation phase in progress -->
<!-- @UX_STATE inserting -> Insert phase in progress -->
<!-- @UX_STATE completed -> All phases completed successfully -->
<!-- @UX_STATE partial -> Translation completed with some failures -->
<!-- @UX_STATE failed -> Translation phase failed -->
<!-- @UX_STATE insert_failed -> Insert phase failed -->
<!-- @UX_STATE cancelled -> Run was cancelled -->
<!-- @UX_FEEDBACK progress bar with percentage; batch counter; per-phase counts; cancel button during running/inserting. -->
<!-- @UX_RECOVERY retry on failure states. -->
<script>
/**
* @PURPOSE: WebSocket-driven progress bar with batch counter, success/failure/skip counts, two-phase display, and cancel button.
* @LAYER: UI
* @RELATION: DEPENDS_ON -> [TranslateApi]
* @RELATION: DEPENDS_ON -> [api_module]
* @PRE: runId is a valid translation run ID.
* @POST: Real-time progress display with two-phase (translation + insert) and state transitions.
*
* @UX_STATE: idle -> Initial state, no run loaded
* @UX_STATE: running -> Translation phase in progress
* @UX_STATE: inserting -> Insert phase in progress
* @UX_STATE: completed -> All phases completed successfully
* @UX_STATE: partial -> Translation completed with some failures
* @UX_STATE: failed -> Translation phase failed
* @UX_STATE: insert_failed -> Insert phase failed
* @UX_STATE: cancelled -> Run was cancelled
*
* @UX_FEEDBACK: progress bar with percentage; batch counter; per-phase counts; cancel button during running/inserting.
* @UX_RECOVERY: retry on failure states.
*/
import { onMount } from 'svelte';
import { t } from '$lib/i18n';
import { addToast } from '$lib/toasts.js';

View File

@@ -1,38 +1,14 @@
<!-- #region TranslationRunResult [C:3] [TYPE Component] [SEMANTICS translate, result, stats, retry, sql-audit] -->
<!-- @BRIEF Component component: lib/components/translate/TranslationRunResult.svelte -->
<!-- @LAYER UI -->
<!-- @PURPOSE Run result display with statistics, insert status badge, Superset query reference, SQL audit block, and retry buttons. -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [TranslateApi] -->
<!-- @RELATION DEPENDS_ON -> [api_module] -->
<!-- @RELATION DEPENDS_ON -> [api_module] -->
<!-- @PRE runId is a valid completed/failed/pending run. -->
<!-- @POST Results displayed with actions for retry, SQL audit, and Superset reference. -->
<!-- @UX_STATE insert_failed -> Insert phase failed, retry-insert available -->
<!-- @UX_STATE insert_failed -> Insert phase failed, retry-insert available -->
<!-- @UX_STATE insert_failed -> Insert phase failed, retry-insert available -->
<!-- @UX_STATE completed -> All phases completed, full stats displayed -->
<!-- @UX_STATE partial -> Translation completed with failures, retry available -->
<!-- @UX_STATE failed -> Translation failed, retry available -->
<!-- @UX_STATE insert_failed -> Insert phase failed, retry-insert available -->
<!-- @UX_FEEDBACK Tabular statistics; click to copy Superset query ID; collapsible SQL block. -->
<!-- @UX_RECOVERY Retry failed batches; retry insert. -->
<!-- @complexity 4 -->
<!-- @task connect_orphaned_components -->
<script>
/**
* @PURPOSE: Run result display with statistics, insert status badge, Superset query reference, SQL audit block, and retry buttons.
* @LAYER: UI
* @RELATION: DEPENDS_ON -> [TranslateApi]
* @RELATION: DEPENDS_ON -> [api_module]
* @PRE: runId is a valid completed/failed/pending run.
* @POST: Results displayed with actions for retry, SQL audit, and Superset reference.
*
* @UX_STATE: completed -> All phases completed, full stats displayed
* @UX_STATE: partial -> Translation completed with failures, retry available
* @UX_STATE: failed -> Translation failed, retry available
* @UX_STATE: insert_failed -> Insert phase failed, retry-insert available
*
* @UX_FEEDBACK: Tabular statistics; click to copy Superset query ID; collapsible SQL block.
* @UX_RECOVERY: Retry failed batches; retry insert.
*/
import { t } from '$lib/i18n';
import { addToast } from '$lib/toasts.js';
import {