feat: LAD A-Team CIS Compliance Report, React SPA, i18n, report management, explorer UX fixes

- Professional Oracle-format compliance report with RAG remediation from ADB vector store
- React 19 SPA at /app/ (16 pages, TypeScript, Vite, Zustand, i18n pt/en 625 keys)
- Report delete endpoint, embed individual report files into vector store
- Terraform ZIP download (client-side, pure JS)
- Explorer silent polling during start/stop (no flickering)
- HTML report and compliance report sections start minimized
- Token auth fix for compliance report CSV download links
This commit is contained in:
nogueiraguh
2026-03-19 07:34:06 -03:00
parent baacc1ba21
commit 442e710b9f
60 changed files with 23094 additions and 13 deletions

View File

@@ -0,0 +1,30 @@
import axios from 'axios';
const client = axios.create({
baseURL: '/api',
headers: { 'Content-Type': 'application/json' },
});
// Request: attach JWT
client.interceptors.request.use((config) => {
const token = localStorage.getItem('t');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response: handle 401 + extract data
client.interceptors.response.use(
(res) => res.data,
(err) => {
if (err.response?.status === 401) {
localStorage.removeItem('t');
window.location.href = '/app/login';
}
const detail = err.response?.data?.detail || err.response?.data?.message || err.message || 'Erro';
return Promise.reject(new Error(detail));
}
);
export default client;

View File

@@ -0,0 +1,71 @@
import client from '../client';
export interface AdbTableDef {
id: string;
table_name: string;
description?: string;
is_active: boolean;
}
export interface AdbConfigFull {
id: string;
config_name: string;
dsn: string;
username: string;
table_name?: string;
use_mtls: boolean;
is_active: boolean;
wallet_dir?: string;
genai_config_id?: string;
embedding_model_id?: string;
tables?: AdbTableDef[];
created_at?: string;
}
export interface AdbTestResult {
status: 'success' | 'error';
message: string;
}
export interface WalletParseResult {
dsn_names: string[];
files: string[];
}
export const adbApi = {
list: () =>
client.get('/adb/configs') as unknown as Promise<AdbConfigFull[]>,
create: (fd: FormData) =>
client.post('/adb/config', fd, { headers: { 'Content-Type': 'multipart/form-data' } }) as unknown as Promise<{ id: string; config_name: string }>,
update: (id: string, fd: FormData) =>
client.put(`/adb/configs/${id}`, fd, { headers: { 'Content-Type': 'multipart/form-data' } }) as unknown as Promise<{ id: string; config_name: string }>,
remove: (id: string) =>
client.delete(`/adb/configs/${id}`) as unknown as Promise<{ ok: boolean }>,
test: (id: string) =>
client.post(`/adb/test/${id}`) as unknown as Promise<AdbTestResult>,
parseWallet: (file: File) => {
const fd = new FormData();
fd.append('wallet', file);
return client.post('/adb/parse-wallet', fd, { headers: { 'Content-Type': 'multipart/form-data' } }) as unknown as Promise<WalletParseResult>;
},
uploadWallet: (id: string, file: File) => {
const fd = new FormData();
fd.append('wallet', file);
return client.post(`/adb/${id}/upload-wallet`, fd, { headers: { 'Content-Type': 'multipart/form-data' } }) as unknown as Promise<{ ok: boolean; files: string[] }>;
},
addTable: (vid: string, tableName: string, description: string) =>
client.post(`/adb/${vid}/tables`, { table_name: tableName, description }) as unknown as Promise<{ id: string }>,
updateTable: (vid: string, tid: string, body: { table_name?: string; description?: string; is_active?: boolean }) =>
client.put(`/adb/${vid}/tables/${tid}`, body) as unknown as Promise<{ ok: boolean }>,
removeTable: (vid: string, tid: string) =>
client.delete(`/adb/${vid}/tables/${tid}`) as unknown as Promise<{ ok: boolean }>,
};

View File

@@ -0,0 +1,72 @@
import client from '../client';
/* ── Types ── */
export interface AuditEntry {
id: string;
user_id: string | null;
username: string | null;
action: string;
resource: string | null;
details: string | null;
ip: string | null;
created_at: string;
}
export interface ConfigLogEntry {
id: string;
config_type: string;
config_id: string;
config_name: string | null;
severity: string;
action: string;
message: string;
user_id: string | null;
username: string | null;
created_at: string;
}
export interface ChatLogEntry {
id: string;
session_id: string | null;
message_id: string | null;
user_id: string | null;
severity: string;
source: string;
action: string;
message: string;
created_at: string;
}
/* ── API calls ── */
export const adminApi = {
getAuditLog: (limit = 100) =>
client.get(`/audit-log?limit=${limit}`) as unknown as Promise<AuditEntry[]>,
getConfigLogs: (params: {
config_type?: string;
config_id?: string;
severity?: string;
limit?: number;
} = {}) => {
const qs = new URLSearchParams();
if (params.config_type) qs.set('config_type', params.config_type);
if (params.config_id) qs.set('config_id', params.config_id);
if (params.severity) qs.set('severity', params.severity);
qs.set('limit', String(params.limit ?? 50));
return client.get(`/config-logs?${qs}`) as unknown as Promise<ConfigLogEntry[]>;
},
getChatLogs: (params: {
severity?: string;
session_id?: string;
limit?: number;
} = {}) => {
const qs = new URLSearchParams();
if (params.severity) qs.set('severity', params.severity);
if (params.session_id) qs.set('session_id', params.session_id);
qs.set('limit', String(params.limit ?? 50));
return client.get(`/chat-logs?${qs}`) as unknown as Promise<ChatLogEntry[]>;
},
};

View File

@@ -0,0 +1,31 @@
import client from '../client';
export interface User {
id: string;
username: string;
role: string;
first_name?: string;
last_name?: string;
email?: string;
mfa_enabled?: boolean;
}
export interface LoginResponse {
token?: string;
user?: User;
mfa_required?: boolean;
mfa_setup?: boolean;
totp_uri?: string;
}
export const authApi = {
login: (username: string, password: string, totp_code?: string) =>
client.post<never, LoginResponse>('/auth/login', { username, password, ...(totp_code && { totp_code }) }),
logout: () => client.post('/auth/logout'),
me: () => client.get<never, User>('/users/me'),
changePassword: (current_password: string, new_password: string) =>
client.post('/auth/change-password', { current_password, new_password }),
};

View File

@@ -0,0 +1,94 @@
import client from '../client';
/* ── Types ── */
export interface ChatSession {
id: string;
title: string;
agent_type: string;
created_at: string;
updated_at: string;
message_count?: number;
}
export interface ChatMessage {
id: string;
session_id?: string;
role: 'user' | 'assistant';
content: string;
model_id?: string;
status?: 'processing' | 'done' | 'failed';
created_at?: string;
}
export interface ChatSendBody {
message: string;
session_id?: string;
genai_config_id?: string;
model_id?: string;
oci_config_id?: string;
genai_region?: string;
compartment_id?: string;
temperature?: number;
max_tokens?: number;
top_p?: number;
top_k?: number;
frequency_penalty?: number;
presence_penalty?: number;
reasoning_effort?: string;
use_tools?: boolean;
}
export interface ChatSendResponse {
message_id: string;
session_id: string;
status: 'processing' | 'done';
}
export interface MessageStatusResponse {
id: string;
session_id: string;
role: string;
content: string;
model_id?: string;
status: 'processing' | 'done' | 'failed';
}
export interface SessionMessagesResponse {
session: ChatSession;
messages: ChatMessage[];
}
/* ── API ── */
export const chatApi = {
/** Send a chat message (text only) */
send: (body: ChatSendBody) =>
client.post('/chat', body) as unknown as Promise<ChatSendResponse>,
/** Send a chat message with file attachments */
upload: (formData: FormData) =>
client.post('/chat/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
}) as unknown as Promise<ChatSendResponse>,
/** Poll message processing status */
pollStatus: (messageId: string) =>
client.get(`/chat/${messageId}/status`) as unknown as Promise<MessageStatusResponse>,
/** List chat sessions */
listSessions: (agentType = 'chat', limit = 50) =>
client.get(`/chat/sessions?agent_type=${agentType}&limit=${limit}`) as unknown as Promise<ChatSession[]>,
/** Load session with all messages */
loadSession: (sessionId: string) =>
client.get(`/chat/sessions/${sessionId}/messages`) as unknown as Promise<SessionMessagesResponse>,
/** Delete a session */
deleteSession: (sessionId: string) =>
client.delete(`/chat/${sessionId}`) as unknown as Promise<{ ok: boolean }>,
/** Rename a session */
renameSession: (sessionId: string, title: string) =>
client.put(`/chat/sessions/${sessionId}/title`, { title }) as unknown as Promise<{ ok: boolean }>,
};

View File

@@ -0,0 +1,97 @@
import client from '../client';
/* ── Types ── */
export interface EmbeddingDoc {
id: string;
metadata: string;
}
export interface EmbeddingListResult {
total: number;
offset: number;
limit: number;
documents: EmbeddingDoc[];
}
export interface EmbedUploadResult {
ok: boolean;
message: string;
chunks: number;
filename?: string;
url?: string;
}
export interface PurgeResult {
ok: boolean;
deleted: number;
table: string;
tenancy: string;
}
export interface ConsultResult {
answer: string;
documents: {
content: string;
source: string;
distance: number;
metadata: string;
}[];
total: number;
}
/* ── API ── */
export const embeddingsApi = {
/** List embeddings for a given ADB config + optional table */
list: (vid: string, tableName?: string, limit = 50, offset = 0) => {
const params = new URLSearchParams();
if (tableName) params.set('table_name', tableName);
params.set('limit', String(limit));
params.set('offset', String(offset));
return client.get(`/embeddings/${vid}/list?${params}`) as unknown as Promise<EmbeddingListResult>;
},
/** Delete a single embedding document */
remove: (vid: string, docId: string, tableName?: string) => {
const qs = tableName ? `?table_name=${encodeURIComponent(tableName)}` : '';
return client.delete(`/embeddings/${vid}/${docId}${qs}`) as unknown as Promise<{ ok: boolean }>;
},
/** Upload a file to generate embeddings */
uploadFile: (adbConfigId: string, tableName: string, file: File) => {
const fd = new FormData();
fd.append('adb_config_id', adbConfigId);
fd.append('table_name', tableName);
fd.append('file', file);
return client.post('/embeddings/upload', fd, {
headers: { 'Content-Type': 'multipart/form-data' },
}) as unknown as Promise<EmbedUploadResult>;
},
/** Import a URL to generate embeddings */
uploadUrl: (adbConfigId: string, tableName: string, url: string) => {
const fd = new FormData();
fd.append('adb_config_id', adbConfigId);
fd.append('table_name', tableName);
fd.append('url', url);
return client.post('/embeddings/upload-url', fd, {
headers: { 'Content-Type': 'multipart/form-data' },
}) as unknown as Promise<EmbedUploadResult>;
},
/** Purge embeddings from a table (optionally filtered by tenancy) */
purge: (vid: string, tableName: string, tenancy?: string) =>
client.post(`/embeddings/${vid}/purge`, {
table_name: tableName,
tenancy: tenancy || '',
}) as unknown as Promise<PurgeResult>,
/** Consult embeddings with a question */
consult: (query: string, tableName?: string, topK = 10) =>
client.post('/embeddings/consult', {
query,
table_name: tableName || '',
top_k: topK,
}) as unknown as Promise<ConsultResult>,
};

View File

@@ -0,0 +1,129 @@
import client from '../client';
/* ── Types ─────────────────────────────────────────────────────────────────── */
export interface CompartmentNode {
id: string;
name: string;
parent_id: string | null;
lifecycle_state?: string;
}
export interface Region {
name: string;
key: string;
status: string;
is_home: boolean;
}
export interface CountEntry {
count: number;
updated_at: string;
}
export type CountsMap = Record<string, CountEntry>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ResourceItem = Record<string, any>;
export interface ActionResult {
ok: boolean;
action: string;
[key: string]: unknown;
}
export interface MyIpResult {
ip: string;
}
/* ── API ───────────────────────────────────────────────────────────────────── */
export const explorerApi = {
/* Compartment tree (flat list with parent_id) */
getCompartmentTree: (cid: string) =>
client.get(`/oci/explore/${cid}/compartment-tree`) as unknown as Promise<CompartmentNode[]>,
/* Regions subscribed */
getRegions: (cid: string) =>
client.get(`/oci/explore/${cid}/regions`) as unknown as Promise<Region[]>,
/* Cached counts */
getCounts: (cid: string, compartmentId: string, regions: string = '') =>
client.get(`/oci/explore/${cid}/counts`, {
params: { compartment_id: compartmentId, regions },
}) as unknown as Promise<CountsMap>,
/* Trigger background counts refresh */
refreshCounts: (cid: string, compartmentId: string, regions: string[]) =>
client.post(`/oci/explore/${cid}/counts/refresh`, {
compartment_id: compartmentId,
regions,
}) as unknown as Promise<{ status: string; types: number }>,
/* Fetch resources of a given type */
getResources: (cid: string, resourceType: string, compartmentId: string, region?: string) =>
client.get(`/oci/explore/${cid}/${resourceType}`, {
params: { compartment_id: compartmentId, region: region || undefined },
}) as unknown as Promise<ResourceItem[]>,
/* ── Actions ─────────────────────────────────────────────────────────────── */
instanceAction: (id: string, action: 'START' | 'STOP', ociConfigId: string, region?: string) =>
client.post(`/oci/instances/${id}/action`, {
action, oci_config_id: ociConfigId, region,
}) as unknown as Promise<ActionResult>,
adbAction: (id: string, action: 'start' | 'stop', ociConfigId: string, region?: string) =>
client.post(`/oci/autonomous-databases/${id}/action`, {
action, oci_config_id: ociConfigId, region,
}) as unknown as Promise<ActionResult>,
dbSystemAction: (id: string, action: 'START' | 'STOP', ociConfigId: string, region?: string) =>
client.post(`/oci/db-systems/${id}/action`, {
action, oci_config_id: ociConfigId, region,
}) as unknown as Promise<ActionResult>,
mysqlAction: (id: string, action: 'start' | 'stop', ociConfigId: string, region?: string) =>
client.post(`/oci/mysql-db-systems/${id}/action`, {
action, oci_config_id: ociConfigId, region,
}) as unknown as Promise<ActionResult>,
containerAction: (id: string, action: 'start' | 'stop', ociConfigId: string, region?: string) =>
client.post(`/oci/container-instances/${id}/action`, {
action, oci_config_id: ociConfigId, region,
}) as unknown as Promise<ActionResult>,
getMyIp: () =>
client.get('/oci/my-ip') as unknown as Promise<MyIpResult>,
adbUpdateNetwork: (id: string, ip: string, ociConfigId: string, region?: string) =>
client.post(`/oci/autonomous-databases/${id}/update-network-access`, {
ip, oci_config_id: ociConfigId, region,
}) as unknown as Promise<ActionResult>,
/* ── Security List Rules ─────────────────────────────────────────────────── */
addSlRule: (cid: string, slId: string, body: {
direction: string; protocol: string; source_dest: string;
port_min?: number; port_max?: number; description?: string;
is_stateless?: boolean; region?: string;
}) =>
client.post(`/oci/explore/${cid}/security_lists/${slId}/rules`, body) as unknown as Promise<ActionResult>,
removeSlRule: (cid: string, slId: string, body: {
direction: string; rule_index: number; region?: string;
}) =>
client.delete(`/oci/explore/${cid}/security_lists/${slId}/rules`, { data: body }) as unknown as Promise<ActionResult>,
/* ── NSG Rules ───────────────────────────────────────────────────────────── */
addNsgRule: (cid: string, nsgId: string, body: {
direction: string; protocol: string; source_dest: string;
source_dest_type?: string; port_min?: number; port_max?: number;
description?: string; is_stateless?: boolean; region?: string;
}) =>
client.post(`/oci/explore/${cid}/nsgs/${nsgId}/rules`, body) as unknown as Promise<ActionResult>,
removeNsgRule: (cid: string, nsgId: string, ruleId: string, region?: string) =>
client.delete(`/oci/explore/${cid}/nsgs/${nsgId}/rules/${ruleId}${region ? `?region=${region}` : ''}`) as unknown as Promise<ActionResult>,
};

View File

@@ -0,0 +1,66 @@
import client from '../client';
export interface GenAiConfigFull {
id: string;
name: string;
oci_config_id: string;
model_id: string;
model_ocid?: string;
compartment_id: string;
genai_region: string;
endpoint?: string;
serving_type: string;
dedicated_endpoint_id?: string;
temperature: number;
max_tokens: number;
top_p: number;
top_k: number;
frequency_penalty: number;
presence_penalty: number;
reasoning_effort?: string;
is_default: boolean;
created_at?: string;
}
export interface GenAiConfigPayload {
name: string;
oci_config_id: string;
model_id: string;
model_ocid?: string | null;
compartment_id: string;
genai_region: string;
endpoint?: string | null;
serving_type?: string;
dedicated_endpoint_id?: string | null;
temperature?: number;
max_tokens?: number;
top_p?: number;
top_k?: number;
frequency_penalty?: number;
presence_penalty?: number;
reasoning_effort?: string | null;
is_default?: boolean;
}
export interface GenAiTestResult {
status: 'success' | 'error';
message: string;
response?: string;
}
export const genaiApi = {
list: () =>
client.get('/genai/configs') as unknown as Promise<GenAiConfigFull[]>,
create: (body: GenAiConfigPayload) =>
client.post('/genai/config', body) as unknown as Promise<{ id: string; model_id: string; endpoint: string }>,
update: (id: string, body: GenAiConfigPayload) =>
client.put(`/genai/configs/${id}`, body) as unknown as Promise<{ id: string; model_id: string; endpoint: string }>,
remove: (id: string) =>
client.delete(`/genai/configs/${id}`) as unknown as Promise<{ ok: boolean }>,
test: (id: string) =>
client.post(`/genai/test/${id}`) as unknown as Promise<GenAiTestResult>,
};

View File

@@ -0,0 +1,68 @@
import client from '../client';
export interface McpServerFull {
id: string;
name: string;
description?: string;
server_type: string;
command?: string;
args?: string[];
env_vars?: Record<string, string>;
url?: string;
module_path?: string;
is_active: boolean;
tools?: McpToolDef[];
linked_adb_id?: string;
created_at?: string;
}
export interface McpToolDef {
name: string;
description?: string;
input_schema?: Record<string, unknown>;
}
export interface McpServerPayload {
name: string;
description?: string | null;
server_type: string;
command?: string | null;
args?: string[] | null;
env_vars?: Record<string, string> | null;
url?: string | null;
module_path?: string | null;
tools?: McpToolDef[] | null;
linked_adb_id?: string | null;
}
export const mcpApi = {
list: () =>
client.get('/mcp/servers') as unknown as Promise<McpServerFull[]>,
create: (body: McpServerPayload) =>
client.post('/mcp/servers', body) as unknown as Promise<{ id: string; name: string; server_type: string }>,
update: (id: string, body: McpServerPayload) =>
client.put(`/mcp/servers/${id}`, body) as unknown as Promise<{ id: string; name: string; server_type: string }>,
remove: (id: string) =>
client.delete(`/mcp/servers/${id}`) as unknown as Promise<{ ok: boolean }>,
toggle: (id: string) =>
client.put(`/mcp/servers/${id}/toggle`) as unknown as Promise<{ ok: boolean; is_active: boolean }>,
discoverTools: (id: string) =>
client.post(`/mcp/servers/${id}/discover-tools`) as unknown as Promise<{ ok: boolean; discovered: number; total: number; tools: McpToolDef[] }>,
updateTools: (id: string, tools: McpToolDef[]) =>
client.put(`/mcp/servers/${id}/tools`, { tools }) as unknown as Promise<{ ok: boolean }>,
upload: (id: string, file: File) => {
const fd = new FormData();
fd.append('file', file);
return client.post(`/mcp/servers/${id}/upload`, fd, { headers: { 'Content-Type': 'multipart/form-data' } }) as unknown as Promise<{ ok: boolean; path: string }>;
},
linkAdb: (id: string, adbId: string) =>
client.put(`/mcp/servers/${id}/link-adb?adb_id=${adbId}`) as unknown as Promise<{ ok: boolean }>,
};

View File

@@ -0,0 +1,39 @@
import client from '../client';
export interface OciConfigFull {
id: string;
user_id: string;
tenancy_name: string;
tenancy_ocid: string;
user_ocid: string;
fingerprint: string;
region: string;
compartment_id?: string;
ssh_public_key?: string;
ssh_public_key_preview?: string;
auth_type: 'api_key' | 'session_token';
created_at: string;
}
export interface OciTestResult {
status: 'success' | 'error';
message: string;
data?: unknown;
}
export const ociApi = {
list: () =>
client.get('/oci/configs') as unknown as Promise<OciConfigFull[]>,
create: (fd: FormData) =>
client.post('/oci/config', fd, { headers: { 'Content-Type': 'multipart/form-data' } }) as unknown as Promise<{ id: string; tenancy_name: string; region: string }>,
update: (id: string, fd: FormData) =>
client.put(`/oci/configs/${id}`, fd, { headers: { 'Content-Type': 'multipart/form-data' } }) as unknown as Promise<{ id: string; tenancy_name: string; region: string }>,
remove: (id: string) =>
client.delete(`/oci/configs/${id}`) as unknown as Promise<{ ok: boolean }>,
test: (id: string) =>
client.post(`/oci/test/${id}`) as unknown as Promise<OciTestResult>,
};

View File

@@ -0,0 +1,142 @@
import client from '../client';
/* ── Types ── */
export interface Report {
id: string;
tenancy_name: string;
status: string;
level: number | null;
regions?: string[];
obp?: boolean;
raw?: boolean;
redact_output?: boolean;
created_at: string;
completed_at: string | null;
error_msg?: string | null;
}
export interface RunReportBody {
config_id: string;
regions?: string[];
level: 1 | 2;
obp: boolean;
raw: boolean;
redact_output: boolean;
}
export interface RunReportResult {
report_id: string;
status: string;
}
export interface ReportProgress {
status: string;
progress: string[];
created_at: string;
completed_at: string | null;
error_msg: string | null;
}
export interface ReportSummary {
tenancy: string;
level: number;
regions: string[];
generated_at: string;
total: number;
passed: number;
failed: number;
total_findings: number;
total_compliant: number;
score: number;
sections: Record<string, { passed: number; failed: number; total: number }>;
}
export interface ReportFile {
id: string;
file_name: string;
file_type: string;
file_category: string;
file_size: number;
}
export interface CisEngineVersion {
version: string;
updated_date: string;
file_path: string;
}
export interface CisEngineUpdate {
local_version: string;
local_date: string;
remote_version: string;
remote_date: string;
update_available: boolean;
}
/* ── API ── */
export const reportsApi = {
run: (body: RunReportBody) =>
client.post('/reports/run', body) as unknown as Promise<RunReportResult>,
list: () =>
client.get('/reports') as unknown as Promise<Report[]>,
progress: (rid: string) =>
client.get(`/reports/${rid}/progress`) as unknown as Promise<ReportProgress>,
cancel: (rid: string) =>
client.post(`/reports/${rid}/cancel`) as unknown as Promise<{ ok: boolean }>,
delete: (rid: string) =>
client.delete(`/reports/${rid}`) as unknown as Promise<{ ok: boolean }>,
summary: (rid: string) =>
client.get(`/reports/${rid}/summary`) as unknown as Promise<ReportSummary>,
htmlUrl: (rid: string) => {
const token = localStorage.getItem('t') || '';
return `/api/reports/${rid}/html?token=${encodeURIComponent(token)}`;
},
complianceReportUrl: (rid: string) => {
const token = localStorage.getItem('t') || '';
return `/api/reports/${rid}/compliance-report?token=${encodeURIComponent(token)}`;
},
generateComplianceReport: (rid: string) =>
client.post(`/reports/${rid}/compliance-report/generate`) as unknown as Promise<{ status: string; has_rag: boolean }>,
complianceReportStatus: (rid: string) =>
client.get(`/reports/${rid}/compliance-report/status`) as unknown as Promise<{ ready: boolean }>,
files: (rid: string) =>
client.get(`/reports/${rid}/files`) as unknown as Promise<ReportFile[]>,
downloadUrl: (rid: string, fid: string) => {
const token = localStorage.getItem('t') || '';
return `/api/reports/${rid}/files/${fid}/download?token=${encodeURIComponent(token)}`;
},
engineVersion: () =>
client.get('/cis-engine/version') as unknown as Promise<CisEngineVersion>,
engineCheckUpdate: () =>
client.get('/cis-engine/check-update') as unknown as Promise<CisEngineUpdate>,
engineUpdate: () =>
client.post('/cis-engine/update') as unknown as Promise<{ ok: boolean }>,
embedReport: (rid: string, adbConfigId: string, tableName?: string) =>
client.post(`/embeddings/report/${rid}`, {
adb_config_id: adbConfigId,
...(tableName ? { table_name: tableName } : {}),
}) as unknown as Promise<{ ok: boolean; message: string; sections: number; tenancy: string }>,
embedFile: (rid: string, fid: string, adbConfigId: string, tableName?: string) =>
client.post(`/embeddings/report/${rid}/file/${fid}`, {
adb_config_id: adbConfigId,
...(tableName ? { table_name: tableName } : {}),
}) as unknown as Promise<{ ok: boolean; message: string; chunks: number }>,
};

View File

@@ -0,0 +1,136 @@
import client from '../client';
/* ── Types ── */
export type WsStatus =
| 'draft' | 'planning' | 'planned'
| 'applying' | 'applied'
| 'destroying' | 'destroyed'
| 'failed';
export interface Workspace {
id: string;
name: string;
session_id: string;
session_title?: string;
oci_config_id: string;
compartment_id?: string;
status: WsStatus;
plan_output?: string;
apply_output?: string;
destroy_output?: string;
error?: string;
tf_code?: string;
created_at: string;
updated_at: string;
}
export interface WsStatusResult {
status: WsStatus;
plan_output?: string;
apply_output?: string;
destroy_output?: string;
error?: string;
updated_at: string;
}
export interface ChatSession {
id: string;
title: string;
agent_type: string;
created_at: string;
updated_at: string;
}
export interface ChatMessage {
id: string;
role: 'user' | 'assistant';
content: string;
created_at?: string;
}
export interface PromptResponse {
message_id: string;
session_id: string;
status: 'processing' | 'done';
prompt?: string;
}
export interface MessageStatus {
status: 'processing' | 'done' | 'failed';
content: string;
}
/* ── Workspace API ── */
export const terraformApi = {
/* Workspaces */
listWorkspaces: () =>
client.get('/terraform/workspaces') as unknown as Promise<Workspace[]>,
getWorkspace: (wid: string) =>
client.get(`/terraform/workspaces/${wid}`) as unknown as Promise<Workspace>,
deleteWorkspace: (wid: string) =>
client.delete(`/terraform/workspaces/${wid}`) as unknown as Promise<{ ok: boolean }>,
plan: (wid: string) =>
client.post(`/terraform/workspaces/${wid}/plan`) as unknown as Promise<{ status: string }>,
apply: (wid: string) =>
client.post(`/terraform/workspaces/${wid}/apply`) as unknown as Promise<{ status: string }>,
destroy: (wid: string) =>
client.post(`/terraform/workspaces/${wid}/destroy`, { confirm: 'DESTROY' }) as unknown as Promise<{ status: string }>,
cancel: (wid: string) =>
client.post(`/terraform/workspaces/${wid}/cancel`) as unknown as Promise<{ ok: boolean }>,
status: (wid: string) =>
client.get(`/terraform/workspaces/${wid}/status`) as unknown as Promise<WsStatusResult>,
downloadUrl: (wid: string) => {
const token = localStorage.getItem('t') || '';
return `/api/terraform/workspaces/${wid}/download?token=${encodeURIComponent(token)}`;
},
/* Prompt Generator */
generatePrompt: (body: {
message: string;
session_id?: string;
genai_config: Record<string, string | undefined>;
history?: { role: string; content: string }[] | null;
}) =>
client.post('/terraform/generate-prompt', body) as unknown as Promise<PromptResponse>,
/* Chat sessions / history */
listSessions: (agentType: string, limit = 50) =>
client.get(`/chat/sessions?agent_type=${agentType}&limit=${limit}`) as unknown as Promise<ChatSession[]>,
loadSession: (sid: string) =>
client.get(`/chat/sessions/${sid}/messages`) as unknown as Promise<{ messages: ChatMessage[] }>,
deleteSession: (sid: string) =>
client.delete(`/chat/sessions/${sid}`) as unknown as Promise<{ ok: boolean }>,
pollMessageStatus: (mid: string) =>
client.get(`/chat/${mid}/status`) as unknown as Promise<MessageStatus>,
createWorkspace: (body: { session_id?: string; oci_config_id: string; compartment_id?: string; name: string; tf_code: string }) =>
client.post('/terraform/workspaces', body) as unknown as Promise<Workspace>,
updateCode: (wid: string, tf_code: string) =>
client.put(`/terraform/workspaces/${wid}/code`, { tf_code }) as unknown as Promise<{ ok: boolean }>,
chat: (body: { message: string; session_id?: string; genai_config_id?: string; model_id?: string; oci_config_id?: string; genai_region?: string; compartment_id?: string; temperature?: number; max_tokens?: number; use_tools?: boolean }) =>
client.post('/terraform/chat', body) as unknown as Promise<{ message_id: string; session_id: string; status: string }>,
loadResources: (ociConfigId: string, compartmentId: string, region?: string) => {
const params = new URLSearchParams({ oci_config_id: ociConfigId, compartment_id: compartmentId });
if (region) params.set('region', region);
return client.get(`/terraform/resources?${params}`) as unknown as Promise<Record<string, any[]>>;
},
getCompartmentTree: (cid: string) =>
client.get(`/oci/explore/${cid}/compartment-tree`) as unknown as Promise<any[]>,
};