## Backend (7 production files + 6 test files) ### P0-2: LLM output truncation cascade fix - _token_budget.py: OUTPUT_PER_ROW_PER_LANG 120→200, OUTPUT_SAFETY_FACTOR 0.70→0.55 - Prevents finish_reason=length → split → retry cascade (3 calls → 1 call per batch) - P2-8: added qwen-flash/qwen-plus/qwen-max/qwen-coder to PROVIDER_DEFAULTS ### P1-4/P1-5: EncryptionManager singleton - encryption.py: get_encryption_manager() process-wide singleton - llm_provider.py: use singleton instead of new EncryptionManager() per batch - Eliminates ~90 redundant Fernet key validations per translation run ### P1-6: Cache-hit log aggregation - _batch_proc.py: one log per batch (batch_rows + cache_hits) instead of per-row - 1076 log lines → ~30 per run ### P1-7: Timezone-aware datetime fix - scheduler.py: _ensure_aware() helper for naive DB datetime → UTC-aware - Fixes TypeError in scheduled translation concurrency check ### P2-9: Connection test timeout - connection_service.py: asyncio.wait_for(15s) on all dialect tests - Prevents 2-minute UI hangs from DNS/TCP stalls ### Trace ID propagation - middleware/trace.py: inject x-trace-id response header via ASGI send wrapper ### Test fixes & integration tests - test_scheduler.py: AsyncMock for execute_run, mock get_async_job_runner - test_sql_insert_service.py: AsyncMock for execute_sql - test_token_budget.py: batch_size 50→45 for new OUTPUT_PER_ROW_PER_LANG=200 - test_encryption.py: +2 singleton tests - test_scheduler_ensure_aware.py: +4 (naive→aware, passthrough, None, subtraction) - test_batch_classify_persist.py: +2 cache-hit aggregation tests - test_connection_service_edge.py: +2 timeout tests - test_trace_middleware.py: +4 x-trace-id header tests - test_token_budget.py: +4 qwen-flash/O200 tests ## Frontend (7 production files + 5 test files) ### Trace ID propagation - api.ts: _captureTraceId() reads x-trace-id → setTraceId() in fetchApi/requestApi/postApi/deleteApi ### Duplicate datasource columns fetch - ConfigTabForm.svelte: guard availableColumns.length === 0 before fetch ### Admin pages Svelte 5 runes fix - admin/users/+page.svelte: plain let → () for all template-bound vars - admin/roles/+page.svelte: same fix - Both pages were stuck on «Загрузка...» due to mixed reactivity models ### Validation popover positioning - +page.svelte: pass trigger HTMLElement instead of event - DashboardHubModel.svelte.ts: toggleValidationPopover(HTMLElement), closeValidationPopover() - Added X close button + click-outside overlay + i18n ### Test fixes & integration tests - api.test.ts: mock setTraceId/getTraceId, +3 _captureTraceId tests - provider_config.integration.test.ts: handleDelete→promptDeleteProvider - DatasetPreview.test.ts: dashboards/ → ROUTES.dashboards - test_config_tab_form.svelte.js: +2 columns fetch guard tests (NEW) - admin-users.test.ts: +3 loading→table tests (NEW) - admin-roles.test.ts: +2 loading→table tests (NEW) ## Semantic curation - Removed @COMPLEXITY N from 6 route files + metrics.py (duplicate of [C:N]) - Added [C:N] to 2 orphan child contracts in metrics.py - Added [C:N] + @BRIEF to 4 frontend anchors - Fixed #region → # #region consistency in validation_tasks.py ## Verification - Backend: 608 pytest passed (0 failures) - Frontend: 2472 vitest passed (128 files, 0 failures) - Frontend build: ✓ built in 18s - Browser: dashboards, admin/users, admin/roles, validation popover — all green
1129 lines
60 KiB
TypeScript
1129 lines
60 KiB
TypeScript
// #region ApiModule [C:5] [TYPE Module] [SEMANTICS api, client, fetch, auth, error-handling]
|
|
// @BRIEF Core API communication layer — typed fetch wrappers with auth injection, error normalization, toast feedback, and endpoint registry.
|
|
// @LAYER Infrastructure
|
|
// @RELATION DEPENDS_ON -> [ToastsModule]
|
|
// @RELATION CALLED_BY -> [ReportsApi]
|
|
// @RELATION CALLED_BY -> [AssistantApi]
|
|
// @RELATION CALLED_BY -> [TranslateRunsApi]
|
|
// @RELATION CALLED_BY -> [DatasetReviewApi]
|
|
// @RELATION CALLED_BY -> [MaintenanceApi]
|
|
// @RELATION CALLED_BY -> [ValidationRunDetailPageLoad]
|
|
// @PRE Auth token is available in localStorage under 'auth_token' after login.
|
|
// @POST Every API call returns typed JSON response or throws typed ApiError with status/detail/error_code.
|
|
// @SIDE_EFFECT Reads localStorage for auth token on every request. Dispatches error toasts on non-suppressed failures.
|
|
// @INVARIANT Every fetch MUST go through fetchApi/requestApi/postApi/deleteApi — never native fetch().
|
|
// @INVARIANT Every response.json() is wrapped in try/catch that converts HTTP errors to ApiError.
|
|
// @INVARIANT FetchOptions.signal is passed to native fetch() for cancellation/timeout support.
|
|
// @DATA_CONTRACT Input: (endpoint, body?, options?) → Output: Promise<T> | Promise<Blob> | Error(ApiError)
|
|
// @RATIONALE FetchOptions.signal added per semantics-core anti-loop protocol — prevents infinite loading
|
|
// state when backend is unreachable. Components can now pass AbortSignal.timeout() or
|
|
// onDestroy-aborted signals to cancel in-flight requests.
|
|
// @RATIONALE fetch wrappers exist to enforce auth token injection, centralized error handling,
|
|
// toast feedback, and trace_id propagation — without wrapping every component in try/catch boilerplate.
|
|
// @REJECTED Native fetch() rejected — would bypass auth header injection, error normalization,
|
|
// and toast feedback. Every component would need its own error handling.
|
|
// @REJECTED Axios rejected — unnecessary dependency for a single-domain API client; native fetch + wrappers
|
|
// is simpler, tree-shakeable, and has zero bundle cost.
|
|
|
|
import { log, setTraceId } from '$lib/cot-logger';
|
|
import { addToast } from './toasts.svelte.js';
|
|
import type { FetchOptions, DashboardListParams } from '../types/api';
|
|
|
|
const API_BASE_URL = '/api';
|
|
|
|
/** Default timeout for API requests in milliseconds (30s). Consumer may override via AbortSignal.timeout(). */
|
|
export const API_REQUEST_TIMEOUT = 30_000;
|
|
|
|
// #region ApiTypes [C:1] [TYPE Block] [SEMANTICS api, types, interfaces]
|
|
// @BRIEF Internal type definitions for error handling and API configuration.
|
|
// @DATA_CONTRACT ApiError: { message, status, detail, error_code }
|
|
|
|
interface ApiError extends Error {
|
|
status?: number;
|
|
detail?: unknown;
|
|
error_code?: string;
|
|
}
|
|
|
|
interface BuildApiErrorResponse {
|
|
detail?: string | { message?: string; error_code?: string; [k: string]: unknown };
|
|
error_code?: string;
|
|
[k: string]: unknown;
|
|
}
|
|
// #endregion ApiTypes
|
|
|
|
// #region buildApiError [C:2] [TYPE Function] [SEMANTICS api, error, parsing]
|
|
// @BRIEF Parse an HTTP Response into a structured ApiError with status, detail, and error_code.
|
|
// @PRE response is a failed HTTP Response object (ok === false).
|
|
// @POST Returns ApiError with message, status, detail, and optional error_code extracted from response body.
|
|
// @SIDE_EFFECT Reads response body via .json() (consumes the response stream).
|
|
// @RELATION CALLED_BY -> [fetchApi]
|
|
// @RELATION CALLED_BY -> [postApi]
|
|
// @RELATION CALLED_BY -> [deleteApi]
|
|
// @RELATION CALLED_BY -> [requestApi]
|
|
// @RATIONALE JSON body parsing is wrapped in .catch() because some error responses (e.g. 502 proxy errors)
|
|
// return non-JSON bodies. Without this, the error itself would throw and mask the original status code.
|
|
async function buildApiError(response: Response): Promise<ApiError> {
|
|
const errorData: BuildApiErrorResponse = await response.json().catch(() => ({}));
|
|
const detail = errorData?.detail;
|
|
const message = detail
|
|
? (typeof detail === 'string' ? detail : (typeof detail?.message === 'string' ? detail.message : JSON.stringify(detail)))
|
|
: `API request failed with status ${response.status}`;
|
|
const error: ApiError = new Error(message) as ApiError;
|
|
error.status = response.status;
|
|
error.detail = detail;
|
|
if (detail && typeof detail === 'object' && (detail as Record<string, unknown>).error_code) {
|
|
error.error_code = String((detail as Record<string, unknown>).error_code);
|
|
}
|
|
return error;
|
|
}
|
|
// #endregion buildApiError
|
|
|
|
// #region notifyApiError [C:2] [TYPE Function] [SEMANTICS api, error, toast, feedback]
|
|
// @BRIEF Dispatch an error toast with severity-based messaging.
|
|
// @PRE error is a structured ApiError (may have status).
|
|
// @POST Toast is dispatched with appropriate message and error severity.
|
|
// @SIDE_EFFECT Calls addToast() which mutates the global toast store.
|
|
// @RELATION DEPENDS_ON -> [addToast:Function]
|
|
// @RELATION CALLED_BY -> [fetchApi]
|
|
// @RELATION CALLED_BY -> [postApi]
|
|
// @RELATION CALLED_BY -> [deleteApi]
|
|
// @RELATION CALLED_BY -> [requestApi]
|
|
// @UX_FEEDBACK 401 → "401 Unauthorized" toast. 500+ → "Server error (N)" toast. Others → error.message toast.
|
|
function notifyApiError(error: ApiError): void {
|
|
if (error?.status === 401) { addToast(`401 Unauthorized: ${error.message}`, 'error'); return; }
|
|
if (error?.status >= 500) { addToast(`Server error (${error.status}): ${error.message}`, 'error'); return; }
|
|
addToast(error.message, 'error');
|
|
}
|
|
// #endregion notifyApiError
|
|
|
|
// #region shouldSuppressApiErrorToast [C:2] [TYPE Function] [SEMANTICS api, error, suppression, heuristics]
|
|
// @BRIEF Determine whether an API error should be silently suppressed (no toast) based on endpoint + error heuristics.
|
|
// @PRE endpoint is a string path. error is a structured ApiError.
|
|
// @POST Returns boolean — true if the error is an expected "non-error" for the given endpoint.
|
|
// @RATIONALE Several endpoints legitimately return 4xx for "empty" states (no git repo, no clarification session).
|
|
// Suppressing these toasts prevents noise pollution while still throwing for programmatic handling.
|
|
// @REJECTED A global "ignore 4xx" flag rejected — too broad. Each heuristic is endpoint-specific.
|
|
// @RELATION CALLED_BY -> [requestApi]
|
|
function shouldSuppressApiErrorToast(endpoint: string, error: ApiError): boolean {
|
|
const isGitStatusEndpoint = typeof endpoint === 'string' && endpoint.startsWith('/git/repositories/') && endpoint.endsWith('/status');
|
|
const isNoRepoError = (error?.status === 400 || error?.status === 404) && /Repository for dashboard .* not found/i.test(String(error?.message || ''));
|
|
const isGitPullEndpoint = typeof endpoint === 'string' && endpoint.startsWith('/git/repositories/') && endpoint.endsWith('/pull');
|
|
const isUnfinishedMergeError = error?.status === 409 && (String(error?.error_code || '') === 'GIT_UNFINISHED_MERGE' || String((error?.detail as Record<string, unknown>)?.error_code || '') === 'GIT_UNFINISHED_MERGE');
|
|
const isDatasetClarificationEndpoint = typeof endpoint === 'string' && /\/dataset-orchestration\/sessions\/[^/]+\/clarification$/.test(endpoint);
|
|
const isMissingClarificationSession = error?.status === 404 && /Clarification session not found/i.test(String(error?.message || ''));
|
|
const isGitRepoEndpoint = typeof endpoint === 'string' && endpoint.startsWith('/git/repositories/');
|
|
const isMissingEnvIdError = error?.status === 400 && /env_id is required/i.test(String(error?.message || ''));
|
|
const isGitConfigReposEndpoint = typeof endpoint === 'string' && /^\/git\/config\/[^/]+\/repositories$/.test(endpoint);
|
|
const isRepoAlreadyExistsError = error?.status === 409 && /already exists/i.test(String(error?.message || ''));
|
|
return (isGitStatusEndpoint && isNoRepoError) || (isGitPullEndpoint && isUnfinishedMergeError) || (isDatasetClarificationEndpoint && isMissingClarificationSession) || (isGitRepoEndpoint && isMissingEnvIdError) || (isGitConfigReposEndpoint && isRepoAlreadyExistsError);
|
|
}
|
|
// #endregion shouldSuppressApiErrorToast
|
|
|
|
// #region wsUrlHelpers [C:2] [TYPE Block] [SEMANTICS websocket, url, task-logs, maintenance, translate]
|
|
// @BRIEF WebSocket URL builders — each constructs an authenticated WS endpoint for a specific channel.
|
|
// @PRE taskId / runId are non-empty strings where applicable.
|
|
// @POST Returns fully-qualified ws:// or wss:// URL with auth token as query parameter.
|
|
// @SIDE_EFFECT Reads localStorage for auth_token on each call.
|
|
// @RATIONALE WebSocket API does not support custom headers in browser, so auth token is appended as query param.
|
|
|
|
/**
|
|
* Build a WebSocket URL for a task's log stream, including auth token.
|
|
*/
|
|
export const getWsUrl = (taskId: string): string => {
|
|
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const host = typeof window !== 'undefined' ? window.location.host : 'localhost:8000';
|
|
let url = `${protocol}//${host}/ws/logs/${taskId}`;
|
|
if (typeof window !== 'undefined') {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (token) {
|
|
url += `?token=${encodeURIComponent(token)}`;
|
|
}
|
|
}
|
|
return url;
|
|
};
|
|
|
|
/**
|
|
* Build a WebSocket URL for global task events, including auth token.
|
|
*/
|
|
export const getTaskEventsWsUrl = (): string => {
|
|
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const host = typeof window !== 'undefined' ? window.location.host : 'localhost:8000';
|
|
let url = `${protocol}//${host}/ws/task-events`;
|
|
if (typeof window !== 'undefined') {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (token) {
|
|
url += `?token=${encodeURIComponent(token)}`;
|
|
}
|
|
}
|
|
return url;
|
|
};
|
|
|
|
/**
|
|
* Build a WebSocket URL for maintenance events, including auth token.
|
|
*/
|
|
// #region getMaintenanceEventsWsUrl [C:1] [TYPE Function] [SEMANTICS api, ws, maintenance, url]
|
|
export const getMaintenanceEventsWsUrl = (): string => {
|
|
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const host = typeof window !== 'undefined' ? window.location.host : 'localhost:8000';
|
|
let url = `${protocol}//${host}/ws/maintenance/events`;
|
|
if (typeof window !== 'undefined') {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (token) {
|
|
url += `?token=${encodeURIComponent(token)}`;
|
|
}
|
|
}
|
|
return url;
|
|
};
|
|
// #endregion getMaintenanceEventsWsUrl
|
|
|
|
/**
|
|
* Build a WebSocket URL for translation run progress streaming.
|
|
*/
|
|
// #region getTranslateRunWsUrl [C:1] [TYPE Function] [SEMANTICS api, ws, translate, run, url]
|
|
export const getTranslateRunWsUrl = (runId: string): string => {
|
|
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const host = typeof window !== 'undefined' ? window.location.host : 'localhost:8000';
|
|
let url = `${protocol}//${host}/ws/translate/run/${runId}`;
|
|
if (typeof window !== 'undefined') {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (token) {
|
|
url += `?token=${encodeURIComponent(token)}`;
|
|
}
|
|
}
|
|
return url;
|
|
};
|
|
// #endregion getTranslateRunWsUrl
|
|
// #endregion wsUrlHelpers
|
|
|
|
// #region getAuthHeaders [C:2] [TYPE Function] [SEMANTICS auth, headers, token, localStorage]
|
|
// @BRIEF Build request headers with Content-Type and optional Bearer token from localStorage.
|
|
// @PRE extraHeaders is an optional map of header key → value.
|
|
// @POST Returns headers object with Content-Type: application/json and Authorization: Bearer <token> if logged in.
|
|
// @SIDE_EFFECT Reads localStorage for auth_token on every call.
|
|
// @RELATION CALLED_BY -> [fetchApi]
|
|
// @RELATION CALLED_BY -> [fetchApiBlob]
|
|
// @RELATION CALLED_BY -> [postApi]
|
|
// @RELATION CALLED_BY -> [deleteApi]
|
|
// @RELATION CALLED_BY -> [requestApi]
|
|
function getAuthHeaders(extraHeaders: Record<string, string> = {}): Record<string, string> {
|
|
const headers: Record<string, string> = { 'Content-Type': 'application/json', ...extraHeaders };
|
|
if (typeof window !== 'undefined') {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
return headers;
|
|
}
|
|
// #endregion getAuthHeaders
|
|
|
|
// #region fetchApi [C:4] [TYPE Function] [SEMANTICS api, fetch, get, json]
|
|
// @BRIEF Perform an authenticated GET request and return typed JSON response.
|
|
// @PRE endpoint is a non-empty string path (without /api prefix).
|
|
// @PRE options.headers may override default Content-Type.
|
|
// @POST Returns Promise<T> with parsed JSON. Returns null for 204 No Content.
|
|
// Throws ApiError on non-2xx response.
|
|
// @SIDE_EFFECT Sends HTTP GET request. On failure, dispatches error toast (unless options.suppressToast).
|
|
// Writes CoT log line via log() on entry and failure.
|
|
// @RELATION DEPENDS_ON -> [buildApiError]
|
|
// @RELATION DEPENDS_ON -> [notifyApiError]
|
|
// @RELATION DEPENDS_ON -> [getAuthHeaders]
|
|
/** Extract X-Trace-Id from response headers and seed the CoT logger's trace_id. */
|
|
function _captureTraceId(response: Response): void {
|
|
try {
|
|
const traceId = response.headers?.get?.('x-trace-id');
|
|
if (traceId) setTraceId(traceId);
|
|
} catch {
|
|
// headers unavailable (e.g. test mock without full Headers API)
|
|
}
|
|
}
|
|
|
|
async function fetchApi<T = unknown>(endpoint: string, options: FetchOptions = {}): Promise<T> {
|
|
try {
|
|
log('api', 'REASON', 'fetchApi', { endpoint });
|
|
const fetchInit: RequestInit = { headers: getAuthHeaders(options.headers || {}) };
|
|
if (options.signal) fetchInit.signal = options.signal;
|
|
const response = await fetch(`${API_BASE_URL}${endpoint}`, fetchInit);
|
|
if (!response.ok) throw await buildApiError(response);
|
|
if (response.status === 204) return null as T;
|
|
_captureTraceId(response);
|
|
return await response.json() as T;
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
log('api', 'EXPLORE', 'fetchApi failed', { endpoint }, apiError?.message || 'unknown');
|
|
if (!options.suppressToast) notifyApiError(apiError);
|
|
throw error;
|
|
}
|
|
}
|
|
// #endregion fetchApi
|
|
|
|
// #region fetchApiBlob [C:4] [TYPE Function] [SEMANTICS api, fetch, blob, thumbnail, file]
|
|
// @BRIEF Perform an authenticated GET request and return a Blob (for thumbnails, file downloads).
|
|
// @PRE endpoint is a non-empty string path.
|
|
// @POST Returns Promise<Blob> with binary data. Throws ApiError on failure or 202 "in progress".
|
|
// @SIDE_EFFECT Sends HTTP GET request. On failure (unless notifyError=false), dispatches error toast.
|
|
// @RELATION DEPENDS_ON -> [buildApiError]
|
|
// @RELATION DEPENDS_ON -> [notifyApiError]
|
|
// @RELATION DEPENDS_ON -> [getAuthHeaders]
|
|
// @RATIONALE 202 status is handled as a special case — the thumbnail generation may still be in progress.
|
|
// The caller can retry after a delay. This is NOT treated as a server error.
|
|
async function fetchApiBlob(endpoint: string, options: FetchOptions = {}): Promise<Blob> {
|
|
const notifyError = options.notifyError !== false;
|
|
try {
|
|
const fetchInit: RequestInit = { headers: getAuthHeaders(options.headers || {}) };
|
|
if (options.signal) fetchInit.signal = options.signal;
|
|
const response = await fetch(`${API_BASE_URL}${endpoint}`, fetchInit);
|
|
if (response.status === 202) {
|
|
const payload: Record<string, unknown> = await response.json().catch(() => ({ message: "Resource is being prepared" }));
|
|
const error: ApiError = new Error((payload?.message as string) || "Resource is being prepared") as ApiError;
|
|
error.status = 202;
|
|
throw error;
|
|
}
|
|
if (!response.ok) throw await buildApiError(response);
|
|
return await response.blob();
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
if (notifyError) notifyApiError(apiError);
|
|
throw error;
|
|
}
|
|
}
|
|
// #endregion fetchApiBlob
|
|
|
|
// #region postApi [C:4] [TYPE Function] [SEMANTICS api, post, create, json]
|
|
// @BRIEF Perform an authenticated POST request with JSON body and return typed response.
|
|
// @PRE endpoint is a non-empty string path.
|
|
// @PRE body is JSON-serializable (will be passed through JSON.stringify).
|
|
// @POST Returns Promise<T> with parsed JSON. Returns null for 204 No Content.
|
|
// Throws ApiError on non-2xx response.
|
|
// @SIDE_EFFECT Sends HTTP POST request. On failure, dispatches error toast (unless options.suppressToast).
|
|
// Writes CoT log line on entry and failure.
|
|
// @RELATION DEPENDS_ON -> [buildApiError]
|
|
// @RELATION DEPENDS_ON -> [notifyApiError]
|
|
// @RELATION DEPENDS_ON -> [getAuthHeaders]
|
|
async function postApi<T = unknown>(endpoint: string, body: unknown, options: FetchOptions = {}): Promise<T> {
|
|
try {
|
|
const fetchInit: RequestInit = {
|
|
method: 'POST', headers: getAuthHeaders(options.headers || {}), body: JSON.stringify(body),
|
|
};
|
|
if (options.signal) fetchInit.signal = options.signal;
|
|
const response = await fetch(`${API_BASE_URL}${endpoint}`, fetchInit);
|
|
if (!response.ok) throw await buildApiError(response);
|
|
if (response.status === 204) return null as T;
|
|
_captureTraceId(response);
|
|
return await response.json() as T;
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
log('api', 'EXPLORE', 'postApi failed', { endpoint }, apiError?.message || 'unknown');
|
|
if (!options.suppressToast) notifyApiError(apiError);
|
|
throw error;
|
|
}
|
|
}
|
|
// #endregion postApi
|
|
|
|
// #region deleteApi [C:4] [TYPE Function] [SEMANTICS api, delete, remove]
|
|
// @BRIEF Perform an authenticated DELETE request and return typed response.
|
|
// @PRE endpoint is a non-empty string path.
|
|
// @POST Returns Promise<T> with parsed JSON. Returns null for 204 No Content.
|
|
// Throws ApiError on non-2xx response. Always dispatches error toast on failure.
|
|
// @SIDE_EFFECT Sends HTTP DELETE request. On failure, dispatches error toast.
|
|
// Writes CoT log line on entry and failure.
|
|
// @RELATION DEPENDS_ON -> [buildApiError]
|
|
// @RELATION DEPENDS_ON -> [notifyApiError]
|
|
// @RELATION DEPENDS_ON -> [getAuthHeaders]
|
|
// @RATIONALE deleteApi always notifies on error (no suppressToast option) because deletions
|
|
// are destructive operations — the user must know if one failed.
|
|
async function deleteApi<T = unknown>(endpoint: string, options: FetchOptions = {}): Promise<T> {
|
|
try {
|
|
const fetchInit: RequestInit = { method: 'DELETE', headers: getAuthHeaders(options.headers || {}) };
|
|
if (options.signal) fetchInit.signal = options.signal;
|
|
const response = await fetch(`${API_BASE_URL}${endpoint}`, fetchInit);
|
|
if (!response.ok) throw await buildApiError(response);
|
|
if (response.status === 204) return null as T;
|
|
_captureTraceId(response);
|
|
return await response.json() as T;
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
log('api', 'EXPLORE', 'deleteApi failed', { endpoint }, apiError?.message || 'unknown');
|
|
notifyApiError(apiError);
|
|
throw error;
|
|
}
|
|
}
|
|
// #endregion deleteApi
|
|
|
|
// #region requestApi [C:4] [TYPE Function] [SEMANTICS api, request, generic, patch, put]
|
|
// @BRIEF Generic authenticated HTTP request — supports any method, optional JSON body, and contextual toast suppression.
|
|
// @PRE endpoint is a non-empty string path.
|
|
// @PRE body is JSON-serializable if provided (null = no body).
|
|
// @POST Returns Promise<T> with parsed JSON. Returns null for 204 No Content.
|
|
// Throws ApiError on non-2xx response.
|
|
// Error toast suppressed if shouldSuppressApiErrorToast returns true for the endpoint+error combo.
|
|
// @SIDE_EFFECT Sends HTTP request. On failure, conditionally dispatches error toast (subject to suppression heuristics).
|
|
// Writes CoT log line on entry and failure.
|
|
// @RELATION DEPENDS_ON -> [buildApiError]
|
|
// @RELATION DEPENDS_ON -> [notifyApiError]
|
|
// @RELATION DEPENDS_ON -> [shouldSuppressApiErrorToast]
|
|
// @RELATION DEPENDS_ON -> [getAuthHeaders]
|
|
async function requestApi<T = unknown>(endpoint: string, method: string = 'GET', body: unknown = null, requestOptions: FetchOptions = {}): Promise<T> {
|
|
try {
|
|
const fetchInit: RequestInit = { method, headers: getAuthHeaders(requestOptions.headers || {}) };
|
|
if (body) fetchInit.body = JSON.stringify(body);
|
|
if (requestOptions.signal) fetchInit.signal = requestOptions.signal;
|
|
const response = await fetch(`${API_BASE_URL}${endpoint}`, fetchInit);
|
|
if (!response.ok) throw await buildApiError(response);
|
|
if (response.status === 204) return null as T;
|
|
_captureTraceId(response);
|
|
return await response.json() as T;
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
log('api', 'EXPLORE', 'requestApi failed', { method, endpoint }, apiError?.message || 'unknown');
|
|
if (!requestOptions.suppressToast && !shouldSuppressApiErrorToast(endpoint, apiError)) {
|
|
notifyApiError(apiError);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
// #endregion requestApi
|
|
|
|
// ── Named type helpers for API methods ───────────────────────
|
|
// These are internal interfaces used by the api registry below.
|
|
|
|
interface TaskListQueryOptions {
|
|
limit?: number;
|
|
offset?: number;
|
|
status?: string;
|
|
task_type?: string;
|
|
completed_only?: boolean;
|
|
plugin_id?: string[];
|
|
}
|
|
|
|
interface TaskLogQueryOptions {
|
|
level?: string;
|
|
source?: string;
|
|
search?: string;
|
|
offset?: number;
|
|
limit?: number;
|
|
}
|
|
|
|
interface DashboardThumbnailOptions {
|
|
force?: boolean;
|
|
}
|
|
|
|
interface DatasetQueryOptions {
|
|
search?: string;
|
|
filter?: string;
|
|
page?: string;
|
|
page_size?: string;
|
|
}
|
|
|
|
interface SupersetAccountOptions {
|
|
search?: string;
|
|
page_index?: number;
|
|
page_size?: number;
|
|
sort_column?: string;
|
|
sort_order?: string;
|
|
}
|
|
|
|
interface ValidationTaskQueryParams {
|
|
page?: number;
|
|
page_size?: number;
|
|
is_active?: boolean;
|
|
environment_id?: string;
|
|
search?: string;
|
|
}
|
|
|
|
interface ValidationRunQueryParams {
|
|
page?: number;
|
|
page_size?: number;
|
|
}
|
|
|
|
// #region ApiRegistry [C:3] [TYPE Block] [SEMANTICS api, endpoints, registry]
|
|
// @BRIEF Named endpoint registry — maps backend API paths to typed frontend methods.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @RELATION DEPENDS_ON -> [deleteApi]
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
// @RELATION DEPENDS_ON -> [fetchApiBlob]
|
|
// @INVARIANT Every method delegates to fetchApi/postApi/deleteApi/requestApi — never native fetch.
|
|
// @RATIONALE The registry pattern keeps endpoint paths in one place and eliminates path-string duplication across components.
|
|
export const api = {
|
|
fetchApi: fetchApi as <T = unknown>(endpoint: string, options?: FetchOptions) => Promise<T>,
|
|
postApi: postApi as <T = unknown>(endpoint: string, body: unknown, options?: FetchOptions) => Promise<T>,
|
|
deleteApi: deleteApi as <T = unknown>(endpoint: string, options?: FetchOptions) => Promise<T>,
|
|
requestApi: requestApi as <T = unknown>(endpoint: string, method?: string, body?: unknown, requestOptions?: FetchOptions) => Promise<T>,
|
|
fetchApiBlob: fetchApiBlob as (endpoint: string, options?: FetchOptions) => Promise<Blob>,
|
|
|
|
// ═══ Tasks ════════════════════════════════════════════════════
|
|
|
|
// #region getPlugins [C:2] [TYPE Function] [SEMANTICS plugins,api,list]
|
|
// @BRIEF Fetch all registered plugins.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { plugins: { id, name, description, version, category }[] }
|
|
getPlugins: <T = unknown>() => fetchApi<T>('/plugins'),
|
|
// #endregion getPlugins
|
|
|
|
// #region getTasks [C:2] [TYPE Function] [SEMANTICS tasks,api,list,pagination]
|
|
// @BRIEF Fetch paginated task list with optional status/type filters.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { limit?, offset?, status?, task_type?, completed_only?, plugin_id?[] }
|
|
// @DATA_CONTRACT response -> { tasks: { id, plugin_id, status, started_at, completed_at, progress?, error?, retry_count?, params? }[], total: number }
|
|
getTasks: <T = unknown>(options: TaskListQueryOptions = {}) => {
|
|
const params = new URLSearchParams();
|
|
if (options.limit != null) params.append('limit', String(options.limit));
|
|
if (options.offset != null) params.append('offset', String(options.offset));
|
|
if (options.status) params.append('status', options.status);
|
|
if (options.task_type) params.append('task_type', options.task_type);
|
|
if (options.completed_only != null) params.append('completed_only', String(Boolean(options.completed_only)));
|
|
if (Array.isArray(options.plugin_id)) options.plugin_id.forEach((pid: string) => params.append('plugin_id', pid));
|
|
const query = params.toString();
|
|
return fetchApi<T>(`/tasks${query ? `?${query}` : ''}`);
|
|
},
|
|
// #endregion getTasks
|
|
|
|
// #region getTask [C:2] [TYPE Function] [SEMANTICS tasks,api,detail]
|
|
// @BRIEF Fetch a single task by ID with full status and result.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { taskId }
|
|
// @DATA_CONTRACT response -> { id, plugin_id, status, started_at, completed_at, progress?, error?, retry_count?, params?, result?, input_request?: { type, databases?[] } }
|
|
getTask: <T = unknown>(taskId: string) => fetchApi<T>(`/tasks/${taskId}`),
|
|
// #endregion getTask
|
|
|
|
// #region getTaskLogs [C:2] [TYPE Function] [SEMANTICS tasks,api,logs]
|
|
// @BRIEF Fetch paginated task log entries with level/source/search filters.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { taskId, level?, source?, search?, offset?, limit? }
|
|
// @DATA_CONTRACT response -> { logs: { timestamp, level, source, message }[], total: number }
|
|
getTaskLogs: <T = unknown>(taskId: string, options: TaskLogQueryOptions = {}) => {
|
|
const params = new URLSearchParams();
|
|
if (options.level) params.append('level', options.level);
|
|
if (options.source) params.append('source', options.source);
|
|
if (options.search) params.append('search', options.search);
|
|
if (options.offset != null) params.append('offset', String(options.offset));
|
|
if (options.limit != null) params.append('limit', String(options.limit));
|
|
return fetchApi<T>(`/tasks/${taskId}/logs${params.toString() ? `?${params.toString()}` : ''}`);
|
|
},
|
|
// #endregion getTaskLogs
|
|
|
|
// #region createTask [C:2] [TYPE Function] [SEMANTICS tasks,api,create]
|
|
// @BRIEF Create a new background task with plugin ID and params.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT params -> { plugin_id, params: object }
|
|
// @DATA_CONTRACT response -> { task_id: string }
|
|
createTask: <T = unknown>(pluginId: string, params: unknown, requestOptions?: FetchOptions) =>
|
|
postApi<T>('/tasks', { plugin_id: pluginId, params }, requestOptions),
|
|
// #endregion createTask
|
|
|
|
// ═══ Profile ══════════════════════════════════════════════════
|
|
|
|
// #region getProfilePreferences [C:2] [TYPE Function] [SEMANTICS profile,api,preferences]
|
|
// @BRIEF Fetch current user profile preferences.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { auto_open_task_drawer?: boolean, default_environment_id?: string, default_profile_filter?: object }
|
|
getProfilePreferences: <T = unknown>() => fetchApi<T>('/profile/preferences'),
|
|
// #endregion getProfilePreferences
|
|
|
|
// #region updateProfilePreferences [C:2] [TYPE Function] [SEMANTICS profile,api,update]
|
|
// @BRIEF Update user profile preferences (PATCH).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
// @DATA_CONTRACT params -> { auto_open_task_drawer?, default_environment_id?, default_profile_filter? }
|
|
// @DATA_CONTRACT response -> { success: boolean }
|
|
updateProfilePreferences: <T = unknown>(payload: unknown) => requestApi<T>('/profile/preferences', 'PATCH', payload),
|
|
// #endregion updateProfilePreferences
|
|
|
|
// #region lookupSupersetAccounts [C:2] [TYPE Function] [SEMANTICS profile,api,superset,lookup]
|
|
// @BRIEF Search Superset accounts for profile identity mapping.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { environmentId, search?, page_index?, page_size?, sort_column?, sort_order? }
|
|
// @DATA_CONTRACT response -> { accounts: { id, username, first_name?, last_name?, email? }[], total: number }
|
|
lookupSupersetAccounts: <T = unknown>(environmentId: string, options: SupersetAccountOptions = {}) => {
|
|
const eid = String(environmentId || '').trim();
|
|
if (!eid) throw new Error('environmentId is required for Superset account lookup');
|
|
const params = new URLSearchParams({ environment_id: eid });
|
|
if (options.search) params.append('search', options.search);
|
|
if (options.page_index != null) params.append('page_index', String(options.page_index));
|
|
if (options.page_size != null) params.append('page_size', String(options.page_size));
|
|
if (options.sort_column) params.append('sort_column', options.sort_column);
|
|
if (options.sort_order) params.append('sort_order', options.sort_order);
|
|
return fetchApi<T>(`/profile/superset-accounts?${params.toString()}`);
|
|
},
|
|
// #endregion lookupSupersetAccounts
|
|
|
|
// ═══ Settings ═════════════════════════════════════════════════
|
|
|
|
// #region getSettings [C:2] [TYPE Function] [SEMANTICS settings,api,list]
|
|
// @BRIEF Fetch all settings (environments, storage, logging).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { environments, storage, logging, llm, migration, features, system }
|
|
getSettings: <T = unknown>() => fetchApi<T>('/settings'),
|
|
// #endregion getSettings
|
|
|
|
// #region updateGlobalSettings [C:2] [TYPE Function] [SEMANTICS settings,api,update,global]
|
|
// @BRIEF Update global settings (PATCH).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
updateGlobalSettings: <T = unknown>(s: unknown) => requestApi<T>('/settings/global', 'PATCH', s),
|
|
// #endregion updateGlobalSettings
|
|
|
|
// #region getEnvironments [C:2] [TYPE Function] [SEMANTICS settings,api,environments,list]
|
|
// @BRIEF Fetch all configured Superset environments.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { environments: { id, name, url, database?, schedule? }[] }
|
|
getEnvironments: <T = unknown>() => fetchApi<T>('/settings/environments'),
|
|
// #endregion getEnvironments
|
|
|
|
// #region addEnvironment [C:2] [TYPE Function] [SEMANTICS settings,api,environments,create]
|
|
// @BRIEF Add a new Superset environment.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT params -> { name, url, database?, schedule? }
|
|
// @DATA_CONTRACT response -> { id, name, url }
|
|
addEnvironment: <T = unknown>(env: unknown) => postApi<T>('/settings/environments', env),
|
|
// #endregion addEnvironment
|
|
|
|
// #region updateEnvironment [C:2] [TYPE Function] [SEMANTICS settings,api,environments,update]
|
|
// @BRIEF Full update of an environment by ID (PUT).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
updateEnvironment: <T = unknown>(id: string, env: unknown) => requestApi<T>(`/settings/environments/${id}`, 'PUT', env),
|
|
// #endregion updateEnvironment
|
|
|
|
// #region deleteEnvironment [C:2] [TYPE Function] [SEMANTICS settings,api,environments,delete]
|
|
// @BRIEF Delete an environment by ID.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
deleteEnvironment: <T = unknown>(id: string) => requestApi<T>(`/settings/environments/${id}`, 'DELETE'),
|
|
// #endregion deleteEnvironment
|
|
|
|
// #region testEnvironmentConnection [C:2] [TYPE Function] [SEMANTICS settings,api,environments,test]
|
|
// @BRIEF Test connection to an environment.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT response -> { success: boolean, message?: string }
|
|
testEnvironmentConnection: <T = unknown>(id: string) => postApi<T>(`/settings/environments/${id}/test`, {}),
|
|
// #endregion testEnvironmentConnection
|
|
|
|
// #region updateEnvironmentSchedule [C:2] [TYPE Function] [SEMANTICS settings,api,environments,schedule]
|
|
// @BRIEF Update environment refresh schedule (PUT).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
updateEnvironmentSchedule: <T = unknown>(id: string, s: unknown, options?: FetchOptions) =>
|
|
requestApi<T>(`/environments/${id}/schedule`, 'PUT', s, options),
|
|
// #endregion updateEnvironmentSchedule
|
|
|
|
// #region getStorageSettings [C:2] [TYPE Function] [SEMANTICS settings,api,storage,list]
|
|
// @BRIEF Fetch storage configuration.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { path, max_size?, allowed_types? }
|
|
getStorageSettings: <T = unknown>() => fetchApi<T>('/settings/storage'),
|
|
// #endregion getStorageSettings
|
|
|
|
// #region updateStorageSettings [C:2] [TYPE Function] [SEMANTICS settings,api,storage,update]
|
|
// @BRIEF Update storage configuration (PUT).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
updateStorageSettings: <T = unknown>(s: unknown) => requestApi<T>('/settings/storage', 'PUT', s),
|
|
// #endregion updateStorageSettings
|
|
|
|
// #region getEnvironmentsList [C:2] [TYPE Function] [SEMANTICS settings,api,environments,list,flat]
|
|
// @BRIEF Fetch flat list of environments (simplified form).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { id, name, url, status }[] // flat list form
|
|
getEnvironmentsList: <T = unknown>(options?: FetchOptions) => fetchApi<T>('/environments', options),
|
|
// #endregion getEnvironmentsList
|
|
|
|
// ═══ LLM ══════════════════════════════════════════════════════
|
|
|
|
// #region getLlmStatus [C:2] [TYPE Function] [SEMANTICS llm,api,status]
|
|
// @BRIEF Fetch LLM service status and provider health.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { configured: boolean, providers: { id, name, status }[] }
|
|
getLlmStatus: <T = unknown>() => fetchApi<T>('/llm/status'),
|
|
// #endregion getLlmStatus
|
|
|
|
// #region fetchLlmModels [C:2] [TYPE Function] [SEMANTICS llm,api,providers,fetch-models]
|
|
// @BRIEF Fetch available models from an LLM provider.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
fetchLlmModels: <T = unknown>(p: unknown) => postApi<T>('/llm/providers/fetch-models', p),
|
|
// #endregion fetchLlmModels
|
|
|
|
// #region getEnvironmentDatabases [C:2] [TYPE Function] [SEMANTICS environments,api,databases]
|
|
// @BRIEF Fetch databases for an environment (used for mapping).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { env_id }
|
|
// @DATA_CONTRACT response -> { databases: { uuid, database_name, backend? }[] }
|
|
getEnvironmentDatabases: <T = unknown>(id: string) => fetchApi<T>(`/environments/${id}/databases`),
|
|
// #endregion getEnvironmentDatabases
|
|
|
|
// ═══ Storage ══════════════════════════════════════════════════
|
|
|
|
// #region getStorageFileBlob [C:2] [TYPE Function] [SEMANTICS storage,api,file,blob]
|
|
// @BRIEF Download a storage file as a Blob.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApiBlob]
|
|
// @DATA_CONTRACT params -> { path }
|
|
// @DATA_CONTRACT response -> Blob (binary file)
|
|
getStorageFileBlob: (path: string) => fetchApiBlob(`/storage/file?path=${encodeURIComponent(path)}`),
|
|
// #endregion getStorageFileBlob
|
|
|
|
// ═══ Dashboards ═══════════════════════════════════════════════
|
|
|
|
// #region getDashboards [C:2] [TYPE Function] [SEMANTICS dashboards,api,list,pagination]
|
|
// @BRIEF Fetch paginated dashboards for an environment with filters and profile context.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { env_id, page?, page_size?, page_context?, apply_profile_default?, override_show_all?, search?, filters?: { title?, git_status?, llm_status?, changed_on?, actor? } }
|
|
// @DATA_CONTRACT response -> { dashboards: { id, title, slug, last_modified, owners, git_status, last_task }[], total: number, page: number, page_size: number, total_pages: number, effective_profile_filter?: { applied: boolean, override_show_all: boolean, username?: string, match_logic?: string } }
|
|
getDashboards: <T = unknown>(envId: string, options: DashboardListParams = {}) => {
|
|
const params = new URLSearchParams({ env_id: envId });
|
|
if (options.search) params.append('search', options.search);
|
|
if (options.page) params.append('page', options.page);
|
|
if (options.page_size) params.append('page_size', options.page_size);
|
|
if (options.page_context) params.append('page_context', options.page_context);
|
|
if (options.apply_profile_default != null) params.append('apply_profile_default', String(Boolean(options.apply_profile_default)));
|
|
if (options.override_show_all != null) params.append('override_show_all', String(Boolean(options.override_show_all)));
|
|
if (options.filters?.title) for (const v of options.filters.title) params.append('filter_title', v);
|
|
if (options.filters?.git_status) for (const v of options.filters.git_status) params.append('filter_git_status', v);
|
|
if (options.filters?.llm_status) for (const v of options.filters.llm_status) params.append('filter_llm_status', v);
|
|
if (options.filters?.changed_on) for (const v of options.filters.changed_on) params.append('filter_changed_on', v);
|
|
if (options.filters?.actor) for (const v of options.filters.actor) params.append('filter_actor', v);
|
|
return fetchApi<T>(`/dashboards?${params.toString()}`);
|
|
},
|
|
// #endregion getDashboards
|
|
|
|
// #region getDashboardDetail [C:2] [TYPE Function] [SEMANTICS dashboards,api,detail]
|
|
// @BRIEF Fetch a single dashboard by ref (ID or slug).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { env_id, ref: dashboard_id_or_slug }
|
|
// @DATA_CONTRACT response -> { id, title, slug, last_modified, status, chart_count?, dataset_count?, owner_ids?, tags?, metadata_json? }
|
|
getDashboardDetail: <T = unknown>(envId: string, ref: string) => fetchApi<T>(`/dashboards/${encodeURIComponent(String(ref))}?env_id=${envId}`),
|
|
// #endregion getDashboardDetail
|
|
|
|
// #region getDashboardTaskHistory [C:2] [TYPE Function] [SEMANTICS dashboards,api,tasks,history]
|
|
// @BRIEF Fetch task history for a specific dashboard.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { env_id, ref, opts?: { limit? } }
|
|
// @DATA_CONTRACT response -> { tasks: { id, plugin_id, status, started_at, completed_at, error?, params? }[] }
|
|
getDashboardTaskHistory: <T = unknown>(envId: string, ref: string, opts: { limit?: number } = {}) => {
|
|
const params = new URLSearchParams();
|
|
if (envId) params.append('env_id', envId);
|
|
if (opts.limit) params.append('limit', opts.limit);
|
|
return fetchApi<T>(`/dashboards/${encodeURIComponent(String(ref))}/tasks?${params.toString()}`);
|
|
},
|
|
// #endregion getDashboardTaskHistory
|
|
|
|
// #region getDashboardThumbnail [C:2] [TYPE Function] [SEMANTICS dashboards,api,thumbnail,blob]
|
|
// @BRIEF Fetch dashboard thumbnail as Blob with optional force regeneration.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApiBlob]
|
|
// @DATA_CONTRACT params -> { env_id, ref, opts?: { force? } }
|
|
// @DATA_CONTRACT response -> Blob (image/png thumbnail)
|
|
getDashboardThumbnail: (envId: string, ref: string, opts: DashboardThumbnailOptions = {}) => {
|
|
const params = new URLSearchParams({ env_id: envId });
|
|
if (opts.force != null) params.append('force', String(Boolean(opts.force)));
|
|
return fetchApiBlob(`/dashboards/${encodeURIComponent(String(ref))}/thumbnail?${params.toString()}`, { notifyError: false });
|
|
},
|
|
// #endregion getDashboardThumbnail
|
|
|
|
// #region getDatabaseMappings [C:2] [TYPE Function] [SEMANTICS dashboards,api,mappings,database]
|
|
// @BRIEF Fetch database mappings between source and target environments.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { src: source_env_id, tgt: target_env_id }
|
|
// @DATA_CONTRACT response -> { mappings: { source_db_uuid, target_db_uuid, source_db_name, target_db_name, confidence? }[] }
|
|
getDatabaseMappings: <T = unknown>(src: string, tgt: string) => fetchApi<T>(`/dashboards/db-mappings?source_env_id=${src}&target_env_id=${tgt}`),
|
|
// #endregion getDatabaseMappings
|
|
|
|
// #region calculateMigrationDryRun [C:2] [TYPE Function] [SEMANTICS migration,api,dry-run]
|
|
// @BRIEF POST dry-run calculation for dashboard migration preview.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT params -> { source_env_id, target_env_id, selected_dashboard_ids: number[], replace_db_config?, fix_cross_filters? }
|
|
// @DATA_CONTRACT response -> { diff: { dashboards, charts, datasets }, summary: { dashboards, charts, datasets }, risk: { score, level, items[] }, selected_dashboard_titles[] }
|
|
calculateMigrationDryRun: <T = unknown>(p: unknown) => postApi<T>('/migration/dry-run', p),
|
|
// #endregion calculateMigrationDryRun
|
|
|
|
// ═══ Datasets ═════════════════════════════════════════════════
|
|
|
|
// #region getDatasets [C:2] [TYPE Function] [SEMANTICS datasets,api,list,pagination]
|
|
// @BRIEF Fetch paginated datasets for an environment.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { env_id, search?, filter?, page?, page_size? }
|
|
// @DATA_CONTRACT response -> { datasets: { id, table_name, schema, database, mapped_fields?: { total, mapped }, metric_count?, last_task? }[], stats?: object, total: number, page: number, total_pages: number }
|
|
getDatasets: <T = unknown>(envId: string, opts: DatasetQueryOptions = {}) => {
|
|
const params = new URLSearchParams({ env_id: envId });
|
|
if (opts.search) params.append('search', opts.search);
|
|
if (opts.filter) params.append('filter', opts.filter);
|
|
if (opts.page) params.append('page', opts.page);
|
|
if (opts.page_size) params.append('page_size', opts.page_size);
|
|
return fetchApi<T>(`/datasets?${params.toString()}`);
|
|
},
|
|
// #endregion getDatasets
|
|
|
|
// #region getDatasetIds [C:2] [TYPE Function] [SEMANTICS datasets,api,ids,lookup]
|
|
// @BRIEF Fetch dataset IDs with optional search (lightweight lookup).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { env_id, search? }
|
|
// @DATA_CONTRACT response -> { ids: { id, table_name, schema, database }[] }
|
|
getDatasetIds: <T = unknown>(envId: string, opts: { search?: string } = {}) => {
|
|
const params = new URLSearchParams({ env_id: envId });
|
|
if (opts.search) params.append('search', opts.search);
|
|
return fetchApi<T>(`/datasets/ids?${params.toString()}`);
|
|
},
|
|
// #endregion getDatasetIds
|
|
|
|
// #region getDatasetDetail [C:2] [TYPE Function] [SEMANTICS datasets,api,detail]
|
|
// @BRIEF Fetch a single dataset detail with columns and metrics.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { env_id, datasetId }
|
|
// @DATA_CONTRACT response -> { id, table_name, schema, database, columns?: { name, type }[], metrics?: { name, expression }[], mapped_fields?: object }
|
|
getDatasetDetail: <T = unknown>(envId: string, datasetId: string) => fetchApi<T>(`/datasets/${datasetId}?env_id=${envId}`),
|
|
// #endregion getDatasetDetail
|
|
|
|
// ═══ Consolidated Settings ════════════════════════════════════
|
|
|
|
// #region getConsolidatedSettings [C:2] [TYPE Function] [SEMANTICS settings,api,consolidated,list]
|
|
// @BRIEF Fetch all settings in one consolidated response.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { environments, storage, logging, llm, migration, features, system }
|
|
getConsolidatedSettings: <T = unknown>() => fetchApi<T>('/settings/consolidated'),
|
|
// #endregion getConsolidatedSettings
|
|
|
|
// #region getAllowedLanguages [C:1] [TYPE Function] [SEMANTICS settings,api,languages,allowed]
|
|
// @BRIEF Fetch the list of allowed BCP-47 language codes (public, no auth required).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> string[]
|
|
getAllowedLanguages: () => fetchApi<string[]>('/settings/allowed-languages'),
|
|
// #endregion getAllowedLanguages
|
|
|
|
// #region updateConsolidatedSettings [C:2] [TYPE Function] [SEMANTICS settings,api,consolidated,update]
|
|
// @BRIEF Update consolidated settings (PATCH).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
// @DATA_CONTRACT params -> { environments?, storage?, logging?, llm?, migration?, features?, system? }
|
|
// @DATA_CONTRACT response -> { success: boolean }
|
|
updateConsolidatedSettings: <T = unknown>(s: unknown) => requestApi<T>('/settings/consolidated', 'PATCH', s),
|
|
// #endregion updateConsolidatedSettings
|
|
|
|
// ═══ Connections ═══════════════════════════════════════════════
|
|
|
|
// #region fetchConnections [C:1] [TYPE Function] [SEMANTICS settings,api,connections,list]
|
|
// @BRIEF Fetch all database connections with masked passwords.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> [{ id, name, host, port, database, username, dialect, pool_size, extra_params, created_at, updated_at, used_by }]
|
|
fetchConnections: <T = unknown>() => fetchApi<T>('/settings/connections'),
|
|
// #endregion fetchConnections
|
|
|
|
// #region getConnection [C:1] [TYPE Function] [SEMANTICS settings,api,connections,get]
|
|
// @BRIEF Fetch a single database connection by ID.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
getConnection: <T = unknown>(id: string) => fetchApi<T>(`/settings/connections/${id}`),
|
|
// #endregion getConnection
|
|
|
|
// #region createConnection [C:1] [TYPE Function] [SEMANTICS settings,api,connections,create]
|
|
// @BRIEF Create a new database connection.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT body -> { name, host, port, database, username, password, dialect, extra_params?, pool_size? }
|
|
// @DATA_CONTRACT response -> { id, name, host, port, database, username, dialect, ... } (password masked)
|
|
createConnection: <T = unknown>(data: unknown) => postApi<T>('/settings/connections', data),
|
|
// #endregion createConnection
|
|
|
|
// #region updateConnection [C:1] [TYPE Function] [SEMANTICS settings,api,connections,update]
|
|
// @BRIEF Update a database connection by ID. Empty/masked password = keep existing.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
updateConnection: <T = unknown>(id: string, data: unknown) => requestApi<T>(`/settings/connections/${id}`, 'PUT', data),
|
|
// #endregion updateConnection
|
|
|
|
// #region deleteConnection [C:1] [TYPE Function] [SEMANTICS settings,api,connections,delete]
|
|
// @BRIEF Delete a database connection by ID. Blocked if referenced by active jobs.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
// @DATA_CONTRACT response -> { message: "Connection deleted" } or 409 { detail: { blocking_jobs: string[] } }
|
|
deleteConnection: <T = unknown>(id: string) => requestApi<T>(`/settings/connections/${id}`, 'DELETE'),
|
|
// #endregion deleteConnection
|
|
|
|
// #region testConnection [C:1] [TYPE Function] [SEMANTICS settings,api,connections,test]
|
|
// @BRIEF Test connectivity to a database connection. Runs SELECT 1.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT response -> { success: bool, latency_ms?: int, db_version?: string, error?: string }
|
|
testConnection: <T = unknown>(id: string) => postApi<T>(`/settings/connections/${id}/test`, {}),
|
|
// #endregion testConnection
|
|
|
|
// ═══ Automation ═══════════════════════════════════════════════
|
|
|
|
// #region getValidationPolicies [C:2] [TYPE Function] [SEMANTICS automation,api,policies,list]
|
|
// @BRIEF Fetch all validation automation policies.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { policies: { id, name, is_active, schedule?, environment_id, dashboard_ids?, task_type? }[] }
|
|
getValidationPolicies: <T = unknown>() => fetchApi<T>('/settings/automation/policies'),
|
|
// #endregion getValidationPolicies
|
|
|
|
// #region createValidationPolicy [C:2] [TYPE Function] [SEMANTICS automation,api,policies,create]
|
|
// @BRIEF Create a new validation automation policy.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
createValidationPolicy: <T = unknown>(p: unknown) => postApi<T>('/settings/automation/policies', p),
|
|
// #endregion createValidationPolicy
|
|
|
|
// #region updateValidationPolicy [C:2] [TYPE Function] [SEMANTICS automation,api,policies,update]
|
|
// @BRIEF Update a validation policy by ID (PATCH).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
updateValidationPolicy: <T = unknown>(id: string, p: unknown) => requestApi<T>(`/settings/automation/policies/${id}`, 'PATCH', p),
|
|
// #endregion updateValidationPolicy
|
|
|
|
// #region deleteValidationPolicy [C:2] [TYPE Function] [SEMANTICS automation,api,policies,delete]
|
|
// @BRIEF Delete a validation policy by ID.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
deleteValidationPolicy: <T = unknown>(id: string) => requestApi<T>(`/settings/automation/policies/${id}`, 'DELETE'),
|
|
// #endregion deleteValidationPolicy
|
|
|
|
// #region getTranslationSchedules [C:2] [TYPE Function] [SEMANTICS automation,api,translation,schedules]
|
|
// @BRIEF Fetch all translation automation schedules.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { schedules: { id, name, config_id?, cron_expression?, is_active }[] }
|
|
getTranslationSchedules: <T = unknown>() => fetchApi<T>('/settings/automation/translation-schedules'),
|
|
// #endregion getTranslationSchedules
|
|
|
|
// ═══ Health ═══════════════════════════════════════════════════
|
|
|
|
// #region getHealthSummary [C:2] [TYPE Function] [SEMANTICS health,api,summary]
|
|
// @BRIEF Fetch dashboard health summary, optionally scoped to environment.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { environmentId? }
|
|
// @DATA_CONTRACT response -> { summary: { total_dashboards, failing_dashboards, total_datasets, environments }[], items?: { dashboard_id, dashboard_slug, title, last_validation_status, last_validation_run_at, failing_count }[] }
|
|
getHealthSummary: <T = unknown>(environmentId?: string) => {
|
|
const query = environmentId ? `?env_id=${encodeURIComponent(environmentId)}` : '';
|
|
return fetchApi<T>(`/health/summary${query}`, { suppressToast: true });
|
|
},
|
|
// #endregion getHealthSummary
|
|
|
|
// ═══ LLM Providers ════════════════════════════════════════════
|
|
|
|
// #region getLlmProviders [C:2] [TYPE Function] [SEMANTICS llm,api,providers,list]
|
|
// @BRIEF Fetch all configured LLM providers.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { providers: { id, name, type, status }[] }
|
|
getLlmProviders: <T = unknown>() => fetchApi<T>('/llm/providers'),
|
|
// #endregion getLlmProviders
|
|
|
|
// ═══ Validation Tasks ═════════════════════════════════════════
|
|
|
|
// #region parseValidationUrl [C:2] [TYPE Function] [SEMANTICS validation,api,url,parse]
|
|
// @BRIEF Parse a Superset dashboard URL into validation task params.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT params -> { url, environment_id }
|
|
// @DATA_CONTRACT response -> { dashboard_id, title?, environment_id, charts?[] }
|
|
parseValidationUrl: <T = unknown>(url: string, envId: string) => postApi<T>('/validation-tasks/parse-url', { url, environment_id: envId }),
|
|
// #endregion parseValidationUrl
|
|
|
|
// #region getValidationTasks [C:2] [TYPE Function] [SEMANTICS validation,api,tasks,list,pagination]
|
|
// @BRIEF Fetch paginated validation tasks with status/environment/search filters.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { page?, page_size?, is_active?, environment_id?, search? }
|
|
// @DATA_CONTRACT response -> { tasks: { id, name, environment_id, is_active, schedule?, last_run_at?, last_run_status?, dashboard_ids? }[], total: number, page: number, page_size: number }
|
|
getValidationTasks: <T = unknown>(params: ValidationTaskQueryParams = {}) => {
|
|
const qs = new URLSearchParams();
|
|
if (params.page != null) qs.append('page', String(params.page));
|
|
if (params.page_size != null) qs.append('page_size', String(params.page_size));
|
|
if (params.is_active != null) qs.append('is_active', String(Boolean(params.is_active)));
|
|
if (params.environment_id) qs.append('environment_id', params.environment_id);
|
|
if (params.search) qs.append('search', params.search);
|
|
const query = qs.toString();
|
|
return fetchApi<T>(`/validation-tasks${query ? `?${query}` : ''}`);
|
|
},
|
|
// #endregion getValidationTasks
|
|
|
|
// #region createValidationTask [C:2] [TYPE Function] [SEMANTICS validation,api,tasks,create]
|
|
// @BRIEF Create a new validation task with schedule and dashboard scope.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT params -> { name, environment_id, dashboard_ids: number[], schedule?, llm_provider?, validation_type? }
|
|
// @DATA_CONTRACT response -> { id, name }
|
|
createValidationTask: <T = unknown>(data: unknown) => postApi<T>('/validation-tasks', data),
|
|
// #endregion createValidationTask
|
|
|
|
// #region getValidationTask [C:2] [TYPE Function] [SEMANTICS validation,api,tasks,detail]
|
|
// @BRIEF Fetch a single validation task by ID with full config.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { id }
|
|
// @DATA_CONTRACT response -> { id, name, environment_id, is_active, schedule?, params?, dashboard_ids?, last_run?, runs_count? }
|
|
getValidationTask: <T = unknown>(id: string) => fetchApi<T>(`/validation-tasks/${id}`),
|
|
// #endregion getValidationTask
|
|
|
|
// #region updateValidationTask [C:2] [TYPE Function] [SEMANTICS validation,api,tasks,update]
|
|
// @BRIEF Update a validation task by ID (PUT).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
updateValidationTask: <T = unknown>(id: string, data: unknown) => requestApi<T>(`/validation-tasks/${id}`, 'PUT', data),
|
|
// #endregion updateValidationTask
|
|
|
|
// #region deleteValidationTask [C:2] [TYPE Function] [SEMANTICS validation,api,tasks,delete]
|
|
// @BRIEF Delete a validation task and optionally its runs.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [deleteApi]
|
|
// @DATA_CONTRACT params -> { id, deleteRuns?: boolean }
|
|
deleteValidationTask: <T = unknown>(id: string, deleteRuns: boolean = true) => {
|
|
const qs = deleteRuns ? '?delete_runs=true' : '';
|
|
return deleteApi<T>(`/validation-tasks/${id}${qs}`);
|
|
},
|
|
// #endregion deleteValidationTask
|
|
|
|
// #region triggerValidationRun [C:2] [TYPE Function] [SEMANTICS validation,api,run,trigger]
|
|
// @BRIEF Trigger a new validation run for a task.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT params -> { id: policy_id }
|
|
// @DATA_CONTRACT response -> { run_id: string, status: string }
|
|
triggerValidationRun: <T = unknown>(id: string) => postApi<T>(`/validation-tasks/${id}/run`, {}),
|
|
// #endregion triggerValidationRun
|
|
|
|
// #region toggleValidationTaskStatus [C:2] [TYPE Function] [SEMANTICS validation,api,tasks,toggle]
|
|
// @BRIEF Toggle active/inactive status of a validation task (PATCH).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
// @DATA_CONTRACT params -> { id, isActive: boolean }
|
|
toggleValidationTaskStatus: <T = unknown>(id: string, isActive: boolean) => requestApi<T>(`/validation-tasks/${id}/status`, 'PATCH', { is_active: isActive }),
|
|
// #endregion toggleValidationTaskStatus
|
|
|
|
// #region getValidationRuns [C:2] [TYPE Function] [SEMANTICS validation,api,runs,list,pagination]
|
|
// @BRIEF Fetch paginated validation runs for a task.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { taskId, page?, page_size? }
|
|
// @DATA_CONTRACT response -> { runs: { id, status, started_at, completed_at, summary?, error? }[], total: number, page: number }
|
|
getValidationRuns: <T = unknown>(taskId: string, params: ValidationRunQueryParams = {}) => {
|
|
const qs = new URLSearchParams();
|
|
if (params.page != null) qs.append('page', String(params.page));
|
|
if (params.page_size != null) qs.append('page_size', String(params.page_size));
|
|
const query = qs.toString();
|
|
return fetchApi<T>(`/validation-tasks/${taskId}/runs${query ? `?${query}` : ''}`);
|
|
},
|
|
// #endregion getValidationRuns
|
|
|
|
// #region getValidationRunDetail [C:2] [TYPE Function] [SEMANTICS validation,api,runs,detail]
|
|
// @BRIEF Fetch a single validation run detail with results and logs.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { taskId, runId }
|
|
// @DATA_CONTRACT response -> { id, task_id, status, started_at, completed_at, result?: { items?: { dashboard_id, dashboard_title, chart_id?, status, issues? }[] }, error?, logs? }
|
|
getValidationRunDetail: <T = unknown>(taskId: string, runId: string) => fetchApi<T>(`/validation-tasks/${taskId}/runs/${runId}`),
|
|
// #endregion getValidationRunDetail
|
|
|
|
// #region getValidationStatusBatch [C:2] [TYPE Function] [SEMANTICS validation,api,status,batch]
|
|
// @BRIEF Batch-fetch latest validation status for multiple dashboard IDs.
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT params -> { env_id, dashboard_ids: comma-separated string }
|
|
// @DATA_CONTRACT response -> { [dashboard_id]: { status: PASS|FAIL|WARN, last_run_at?, task_name?, run_id?, history?: { status, last_run_at, task_name }[] } }
|
|
getValidationStatusBatch: <T = unknown>(envId: string, dashboardIds: string) =>
|
|
fetchApi<T>(`/validation-tasks/status/batch?env_id=${encodeURIComponent(envId)}&dashboard_ids=${encodeURIComponent(dashboardIds)}`),
|
|
// #endregion getValidationStatusBatch
|
|
|
|
// ═══ API Keys ═════════════════════════════════════════════════
|
|
|
|
// #region listApiKeys [C:2] [TYPE Function] [SEMANTICS admin,api,api-keys,list]
|
|
// @BRIEF List all API keys (admin only).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [fetchApi]
|
|
// @DATA_CONTRACT response -> { api_keys: { id, name, key_prefix, created_at, last_used_at?, is_active }[] }
|
|
listApiKeys: <T = unknown>() => fetchApi<T>('/admin/api-keys/'),
|
|
// #endregion listApiKeys
|
|
|
|
// #region createApiKey [C:2] [TYPE Function] [SEMANTICS admin,api,api-keys,create]
|
|
// @BRIEF Create a new API key, returns the secret once (admin only).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [postApi]
|
|
// @DATA_CONTRACT params -> { name, scopes?[] }
|
|
// @DATA_CONTRACT response -> { id, name, key: string, key_prefix, created_at }
|
|
createApiKey: <T = unknown>(payload: unknown) => postApi<T>('/admin/api-keys/', payload, { suppressToast: true }),
|
|
// #endregion createApiKey
|
|
|
|
// #region revokeApiKey [C:2] [TYPE Function] [SEMANTICS admin,api,api-keys,delete]
|
|
// @BRIEF Revoke an API key (admin only).
|
|
// @LAYER API
|
|
// @RELATION DEPENDS_ON -> [requestApi]
|
|
revokeApiKey: <T = unknown>(keyId: string) => requestApi<T>(`/admin/api-keys/${keyId}`, 'DELETE'),
|
|
// #endregion revokeApiKey
|
|
};
|
|
// #endregion ApiRegistry
|
|
// #endregion ApiModule
|
|
|
|
export { fetchApi, postApi, deleteApi, requestApi };
|
|
export const getPlugins = api.getPlugins;
|
|
export const getTasks = api.getTasks;
|
|
export const getTask = api.getTask;
|
|
export const createTask = api.createTask;
|
|
export const getProfilePreferences = api.getProfilePreferences;
|
|
export const updateProfilePreferences = api.updateProfilePreferences;
|
|
export const lookupSupersetAccounts = api.lookupSupersetAccounts;
|
|
export const getSettings = api.getSettings;
|
|
export const updateGlobalSettings = api.updateGlobalSettings;
|
|
export const getEnvironments = api.getEnvironments;
|
|
export const addEnvironment = api.addEnvironment;
|
|
export const updateEnvironment = api.updateEnvironment;
|
|
export const deleteEnvironment = api.deleteEnvironment;
|
|
export const testEnvironmentConnection = api.testEnvironmentConnection;
|
|
export const updateEnvironmentSchedule = api.updateEnvironmentSchedule;
|
|
export const getEnvironmentsList = api.getEnvironmentsList;
|
|
export const getStorageSettings = api.getStorageSettings;
|
|
export const updateStorageSettings = api.updateStorageSettings;
|
|
export const getDashboards = api.getDashboards;
|
|
export const getDatasets = api.getDatasets;
|
|
export const getConsolidatedSettings = api.getConsolidatedSettings;
|
|
export const getAllowedLanguages = api.getAllowedLanguages;
|
|
export const updateConsolidatedSettings = api.updateConsolidatedSettings;
|
|
export const getValidationPolicies = api.getValidationPolicies;
|
|
export const createValidationPolicy = api.createValidationPolicy;
|
|
export const updateValidationPolicy = api.updateValidationPolicy;
|
|
export const deleteValidationPolicy = api.deleteValidationPolicy;
|
|
export const getTranslationSchedules = api.getTranslationSchedules;
|
|
export const getHealthSummary = api.getHealthSummary;
|
|
export const getValidationTasks = api.getValidationTasks;
|
|
export const getValidationTask = api.getValidationTask;
|
|
export const createValidationTask = api.createValidationTask;
|
|
export const updateValidationTask = api.updateValidationTask;
|
|
export const deleteValidationTask = api.deleteValidationTask;
|
|
export const triggerValidationRun = api.triggerValidationRun;
|
|
export const toggleValidationTaskStatus = api.toggleValidationTaskStatus;
|
|
export const getValidationRuns = api.getValidationRuns;
|
|
export const getValidationRunDetail = api.getValidationRunDetail;
|
|
export const fetchConnections = api.fetchConnections;
|
|
export const getConnection = api.getConnection;
|
|
export const createConnection = api.createConnection;
|
|
export const updateConnection = api.updateConnection;
|
|
export const deleteConnection = api.deleteConnection;
|
|
export const testConnection = api.testConnection;
|