feat: OCI Services page, compliance report v3, DOCX with camo strip, timezone per-user

- New OCI Services page with Service Status (auto-detect + overrides) and OCI Health (real-time Oracle status) tabs
- Compliance report: OCI Services section with Cloud Guard recommendations table, back page with Connect with us
- DOCX generation: professional styling with Pillow camouflage strip, green header tables, result boxes
- Separate Download DOCX button in reports page
- Timezone now per-user (not global) — each user configures their own timezone
- ADB connection pre-check before report generation (fail fast 503)
- Compliance report iframe shows friendly HTML instead of JSON 404 during generation
- OCI Health dashboard: proxies ocistatus.oraclecloud.com with search, geo filters, expandable region cards
This commit is contained in:
nogueiraguh
2026-03-29 00:43:20 -03:00
parent 937d0c8318
commit 35a2bf786b
14 changed files with 2134 additions and 75 deletions

View File

@@ -69,4 +69,13 @@ export const adminApi = {
qs.set('limit', String(params.limit ?? 50));
return client.get(`/chat-logs?${qs}`) as unknown as Promise<ChatLogEntry[]>;
},
getMyTimezone: () =>
client.get('/users/me/timezone') as unknown as Promise<{ timezone: string }>,
setMyTimezone: (timezone: string) =>
client.put('/users/me/timezone', { timezone }) as unknown as Promise<{ ok: boolean; timezone: string }>,
getTimezoneOptions: () =>
client.get('/settings/timezone/options') as unknown as Promise<{ timezones: string[] }>,
};

View File

@@ -8,6 +8,7 @@ export interface User {
last_name?: string;
email?: string;
mfa_enabled?: boolean;
timezone?: string;
}
export interface LoginResponse {

View File

@@ -5,6 +5,7 @@ import client from '../client';
export interface Report {
id: string;
tenancy_name: string;
config_id?: string;
status: string;
level: number | null;
regions?: string[];
@@ -74,6 +75,37 @@ export interface CisEngineUpdate {
update_available: boolean;
}
export interface OciServiceStatus {
service: string;
auto_status?: string;
auto_description?: string;
status: string;
description: string;
overridden: boolean;
}
export interface OciHealthService {
serviceId: string;
serviceName: string;
serviceCanonicalName: string;
serviceCategoryName: string;
serviceStatus: string;
incidents: { incidentId: string; title: string; status: string }[];
}
export interface OciHealthRegion {
regionId: string;
regionName: string;
regionCanonicalName: string;
geographicAreaName: string;
serviceHealthReports: OciHealthService[];
}
export interface OciHealthResponse {
realm?: string;
regionHealthReports: OciHealthRegion[];
}
/* ── API ── */
export const reportsApi = {
@@ -95,9 +127,9 @@ export const reportsApi = {
summary: (rid: string) =>
client.get(`/reports/${rid}/summary`) as unknown as Promise<ReportSummary>,
htmlUrl: (rid: string) => `/api/reports/${rid}/html`,
htmlUrl: (rid: string) => `/api/reports/${rid}/html?_t=${Date.now()}`,
complianceReportUrl: (rid: string) => `/api/reports/${rid}/compliance-report`,
complianceReportUrl: (rid: string) => `/api/reports/${rid}/compliance-report?_t=${Date.now()}`,
generateComplianceReport: (rid: string) =>
client.post(`/reports/${rid}/compliance-report/generate`) as unknown as Promise<{ status: string; has_rag: boolean }>,
@@ -110,6 +142,11 @@ export const reportsApi = {
return `/api/reports/${rid}/compliance-report/download?token=${encodeURIComponent(token)}`;
},
complianceReportDocxUrl: (rid: string) => {
const token = localStorage.getItem('t') || '';
return `/api/reports/${rid}/compliance-report/docx?token=${encodeURIComponent(token)}`;
},
files: (rid: string) =>
client.get(`/reports/${rid}/files`) as unknown as Promise<ReportFile[]>,
@@ -147,4 +184,13 @@ export const reportsApi = {
embeddingStatus: (taskId: string) =>
client.get(`/embeddings/status/${taskId}`) as unknown as Promise<{ status: string; message: string; inserted?: number; total?: number }>,
getOciServices: (configId: string, detect = true) =>
client.get(`/oci-services/${configId}?detect=${detect ? 1 : 0}`) as unknown as Promise<OciServiceStatus[]>,
setOciServices: (configId: string, services: OciServiceStatus[]) =>
client.put(`/oci-services/${configId}`, services) as unknown as Promise<{ ok: boolean }>,
getOciHealth: () =>
client.get('/oci-health') as unknown as Promise<OciHealthResponse>,
};