diff --git a/README.md b/README.md
index bf6c45d..7c3f7cc 100644
--- a/README.md
+++ b/README.md
@@ -68,8 +68,10 @@ The platform combines security compliance scanning, AI-powered chat with **RAG (
- Terraform CLI installed in container (v1.7.5)
- **Terraform Resource Reference**: auto-generated OCI provider resource catalog (~937 resources) injected into system prompt — built from `terraform providers schema -json` at container startup, stored in SQLite, refreshable via menu button
- **Official Resource Docs Injection**: on-demand fetch of Example Usage + Argument Reference from the official OCI Terraform provider docs (registry.terraform.io) — cached in SQLite, injected into model context based on detected resource types
-- **Smart variable resolution**: auto-generates `terraform.tfvars` by scanning declared variables and mapping them to OCI config values (`tenancy_ocid`, `user_ocid`, `fingerprint`, `compartment_id`) — region is never forced, respecting variable defaults
+- **Smart variable resolution**: auto-generates `terraform.tfvars` by scanning declared variables and mapping them to OCI config values (`tenancy_ocid`, `user_ocid`, `fingerprint`, `compartment_id`, `ssh_public_key`) — region is never forced, respecting variable defaults
- **Multi-region provider management**: auto-detects provider aliases and region variables in generated code, generates `provider.tf` with credentials and correct region references (`var.region` instead of hardcoded values)
+- **Region safety guard**: plan is blocked if the model does not declare `variable "region"` — prevents accidental provisioning in the wrong region by never falling back to hardcoded defaults
+- **Rollback button**: when `terraform apply` fails, a Rollback button appears allowing manual `terraform destroy` to clean up partially created resources (requires typing "ROLLBACK" to confirm)
- **System prompt sync**: Terraform system prompt in code is automatically synced to DB on every restart — single source of truth
- Compact system prompt optimized for OCI Terraform provider best practices (100K max_tokens)
- **Flat workspace enforcement**: system prompt prohibits `module` blocks (workspace has no subdirectories) and enforces single-file variable declarations (`variables.tf` only)
@@ -683,6 +685,7 @@ All models include OCID mapping for `us-ashburn-1`. For other regions, use the "
| Version | Date | Changes |
|---------|------|---------|
+| **v2.4** | 2026-03 | **Terraform Safety & UX**: region safety guard (plan blocked if model omits `variable "region"` — prevents wrong-region provisioning), rollback button on apply failure (manual `terraform destroy` with "ROLLBACK" confirmation), SSH public key field in OCI configs (auto-injected into `terraform.tfvars` for compute instances), reasoning_effort uppercase fix for OCI SDK, split-panel layout (terminal right 40%, files/plan/resources below chat 60%), plan button persists after destroy. **Terraform tfvars**: expanded `oci_var_map` with `ssh_public_key`/`ssh_authorized_keys` auto-mapping from OCI config |
| **v2.3** | 2026-03 | **Consult Embeddings**: dedicated sub-menu with chat-like interface for natural language Q&A against vector store — queries all active tables via cosine similarity, returns contextual answers with source citations (works with or without GenAI config). **Knowledge Base & RAG Enhancements**: Knowledge Base (Base de Conhecimento) with file upload + URL import to `engineerknowledgebase` vector table, CIS Recommendations upload to `cisrecom` table, URL content extraction (HTML tag/script stripping, remote PDF parsing), auto-resolve embedding config from OCI credentials (no separate GenAI config required), `report_date` metadata tracking for embedding freshness, purge & re-embed flow for updated reports. **ADB Vector Improvements**: case-insensitive table matching with quoted identifiers for Oracle ADB, table validation endpoint against `user_tables`, tables section moved inside edit connection card, case-preserving table registration (removed forced uppercase). **Vector search fix**: FLOAT32 compatibility for VECTOR_DISTANCE queries, base64-decoded compartment_id for auto-resolved OCI configs. **Embeddings tab cleanup**: removed delete buttons, simplified to read-only view, updated descriptions. **Dependencies**: added PyPDF2 for PDF text extraction |
| **v2.2** | 2026-03 | **UI Modernization (Phases 1B-5)**: Oracle Dark Premium theme, 45+ Lucide SVG icons replacing all emojis, KPI dashboard with compliance gauge + Chart.js donut/bar charts, report summary API endpoint, Phase 5 animations (staggered fade-in, smooth theme transitions, hover effects) — animations scoped to tab switch only (no flickering on re-render). **Terraform Intelligence**: official resource docs injection from registry.terraform.io (Example Usage + Argument Reference, on-demand fetch + SQLite cache), smart tfvars auto-generation from declared variables, multi-region provider management with `var.region` support, RPC anti-cycle rule, system prompt auto-sync to DB, 60-min polling timeout for long-running applies. **ADB fix**: wallet_password always passed for thin mode mTLS (fixes DPY-6005). CIS_EMBEDDINGS default removed from ADB config |
| **v2.1** | 2026-03 | Terraform Agent (AI-powered IaC generation with plan/apply/destroy lifecycle, workspace management, Terraform CLI in container, inline file editor, split-panel terminal, multi-file generation via `// filename:` markers, **smart file correction** with merge-based updates, **14-point validation checklist**, 100K max_tokens), **3-layer auto-split pipeline** (frontend + backend monolithic HCL splitting into categorized files + backend deduplication), **HCL syntax auto-fix** (single-line blocks expanded to multi-line), **SQLite-based resource type validation** (~937 types with `difflib` suggestions for invalid types), **Terraform Resource Reference** (auto-generated OCI provider catalog from `terraform providers schema -json`, generated at startup to survive volume mounts, stored in SQLite, refreshable via menu button), compact system prompt for Terraform agent, ChatGPT-style chat history for both Chat Agent and Terraform Agent (rename/delete sessions), OCI Resource Actions (start/stop instances and Autonomous DBs from **OCI Account Explorer**), OCI Explorer expanded to 40+ resource types across 8 categories with tree-view navigation and resizable panels, compartment filtering for MCP CIS scans, chat sidebar with model parameters, chat audit logs, collection error tracking in MCP server, tool timeout increased to 30min with auto-retry, memory compaction tuned (6K threshold, 20 recent messages), model catalog trimmed from 69 to 15 curated models, improved GenAI response extraction with fallback for empty responses, fixed layout overflow (no page scroll), provider alias detection with brace-matching parser, multi-region Terraform support |
diff --git a/backend/app.py b/backend/app.py
index 0e33e56..a98df6d 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -380,6 +380,11 @@ def init_db():
c.execute(f"ALTER TABLE terraform_workspaces ADD COLUMN {col}")
except sqlite3.OperationalError:
pass
+ for col in ["ssh_public_key TEXT"]:
+ try:
+ c.execute(f"ALTER TABLE oci_configs ADD COLUMN {col}")
+ except sqlite3.OperationalError:
+ pass
# Migrate legacy table_name from adb_vector_configs into adb_vector_tables
try:
for cfg_row in c.execute("SELECT id, table_name FROM adb_vector_configs WHERE table_name IS NOT NULL AND table_name != ''").fetchall():
@@ -641,6 +646,7 @@ async def save_oci(
user_ocid: str = Form(...), fingerprint: str = Form(...),
region: str = Form(...), compartment_id: str = Form(""),
key_passphrase: str = Form(""),
+ ssh_public_key: str = Form(""),
private_key: UploadFile = File(...), public_key: Optional[UploadFile] = File(None),
u = Depends(require("admin","user"))
):
@@ -664,8 +670,8 @@ async def save_oci(
cfg_file.write_text(cfg_content)
cfg_file.chmod(0o600)
with db() as c:
- c.execute("INSERT INTO oci_configs (id,user_id,tenancy_name,tenancy_ocid,user_ocid,fingerprint,region,key_file_path,compartment_id) VALUES (?,?,?,?,?,?,?,?,?)",
- (cid, u["id"], tenancy_name, _enc(tenancy_ocid), _enc(user_ocid), _enc(fingerprint), region, str(kp), _enc(compartment_id) if compartment_id else None))
+ c.execute("INSERT INTO oci_configs (id,user_id,tenancy_name,tenancy_ocid,user_ocid,fingerprint,region,key_file_path,compartment_id,ssh_public_key) VALUES (?,?,?,?,?,?,?,?,?,?)",
+ (cid, u["id"], tenancy_name, _enc(tenancy_ocid), _enc(user_ocid), _enc(fingerprint), region, str(kp), _enc(compartment_id) if compartment_id else None, ssh_public_key.strip() if ssh_public_key.strip() else None))
_audit(u["id"], u["username"], "save_oci_config", cid, f"tenancy={tenancy_name}")
_config_log("oci", cid, tenancy_name, "success", "save", f"Credencial salva: {tenancy_name} ({region})", u["id"], u["username"])
return {"id": cid, "tenancy_name": tenancy_name, "region": region}
@@ -673,7 +679,7 @@ async def save_oci(
@app.get("/api/oci/configs")
async def list_oci(u=Depends(current_user)):
with db() as c:
- cols = "id,user_id,tenancy_name,tenancy_ocid,user_ocid,fingerprint,region,compartment_id,created_at"
+ cols = "id,user_id,tenancy_name,tenancy_ocid,user_ocid,fingerprint,region,compartment_id,ssh_public_key,created_at"
if u["role"]=="admin": rows=c.execute(f"SELECT {cols} FROM oci_configs").fetchall()
else: rows=c.execute(f"SELECT {cols} FROM oci_configs WHERE user_id=?",(u["id"],)).fetchall()
result = []
@@ -694,6 +700,10 @@ async def list_oci(u=Depends(current_user)):
d["compartment_id"] = _mask(_dec(d["compartment_id"]))
except Exception:
d["compartment_id"] = _mask(d["compartment_id"])
+ if d.get("ssh_public_key"):
+ # Show just type + first/last few chars for identification
+ parts = d["ssh_public_key"].split()
+ d["ssh_public_key_preview"] = f"{parts[0]} ...{parts[1][-12:]}" if len(parts) >= 2 else "ssh-rsa ..."
result.append(d)
return result
@@ -715,6 +725,7 @@ async def update_oci(
user_ocid: str = Form(""), fingerprint: str = Form(""),
region: str = Form(...), compartment_id: str = Form(""),
key_passphrase: str = Form(""),
+ ssh_public_key: str = Form(""),
private_key: Optional[UploadFile] = File(None), public_key: Optional[UploadFile] = File(None),
u = Depends(require("admin","user"))
):
@@ -727,6 +738,11 @@ async def update_oci(
user_ocid = user_ocid or _safe_dec(existing["user_ocid"])
fingerprint = fingerprint or _safe_dec(existing["fingerprint"])
compartment_id = compartment_id or _safe_dec(existing["compartment_id"]) or ""
+ try:
+ existing_ssh = existing["ssh_public_key"] or ""
+ except (IndexError, KeyError):
+ existing_ssh = ""
+ ssh_public_key = ssh_public_key.strip() if ssh_public_key.strip() else existing_ssh
cdir = OCI_DIR / cid; cdir.mkdir(parents=True, exist_ok=True)
kp = cdir / "oci_api_key.pem"
if private_key and private_key.filename:
@@ -744,8 +760,8 @@ async def update_oci(
cfg_content += f"pass_phrase={key_passphrase}\n"
cfg_file.write_text(cfg_content); cfg_file.chmod(0o600)
with db() as c:
- c.execute("UPDATE oci_configs SET tenancy_name=?,tenancy_ocid=?,user_ocid=?,fingerprint=?,region=?,compartment_id=? WHERE id=?",
- (tenancy_name, _enc(tenancy_ocid), _enc(user_ocid), _enc(fingerprint), region, _enc(compartment_id) if compartment_id else None, cid))
+ c.execute("UPDATE oci_configs SET tenancy_name=?,tenancy_ocid=?,user_ocid=?,fingerprint=?,region=?,compartment_id=?,ssh_public_key=? WHERE id=?",
+ (tenancy_name, _enc(tenancy_ocid), _enc(user_ocid), _enc(fingerprint), region, _enc(compartment_id) if compartment_id else None, ssh_public_key or None, cid))
_audit(u["id"], u["username"], "update_oci_config", cid, f"tenancy={tenancy_name}")
_config_log("oci", cid, tenancy_name, "success", "save", f"Credencial atualizada: {tenancy_name} ({region})", u["id"], u["username"])
return {"id": cid, "tenancy_name": tenancy_name, "region": region}
@@ -1903,7 +1919,7 @@ def _call_genai(gc: dict, message: str, history: list = None, tools: list = None
chat_request.max_completion_tokens = clamped_tokens
re = gc.get("reasoning_effort")
if re and hasattr(chat_request, "reasoning_effort"):
- chat_request.reasoning_effort = re
+ chat_request.reasoning_effort = re.upper() if isinstance(re, str) else re
# Explicitly unset unsupported params
chat_request.temperature = None
chat_request.top_p = None
@@ -1940,6 +1956,16 @@ def _call_genai(gc: dict, message: str, history: list = None, tools: list = None
chat_request.frequency_penalty = None
chat_request.presence_penalty = None
+ # Log applied parameters
+ params_log = f"provider={provider}, reasoning={is_reasoning}"
+ params_log += f", max_tokens={getattr(chat_request, 'max_completion_tokens', None) or getattr(chat_request, 'max_tokens', None)}"
+ params_log += f", temp={getattr(chat_request, 'temperature', None)}, top_p={getattr(chat_request, 'top_p', None)}"
+ params_log += f", top_k={getattr(chat_request, 'top_k', None)}"
+ params_log += f", freq_pen={getattr(chat_request, 'frequency_penalty', None)}, pres_pen={getattr(chat_request, 'presence_penalty', None)}"
+ if is_reasoning:
+ params_log += f", reasoning_effort={getattr(chat_request, 'reasoning_effort', None)}"
+ log.info(f"GenAI params: {params_log}")
+
messages = []
if system_prompt:
sys_content = oci.generative_ai_inference.models.TextContent()
@@ -2181,7 +2207,7 @@ Se pedirem AWS, Azure, GCP ou outro provider, recuse educadamente.
- Nunca declare o mesmo recurso (tipo + nome) em mais de um arquivo.
- Use `var.compartment_id` nos recursos e declare sempre `variable "region"` em `variables.tf`.
- Se houver "RECURSOS OCI EXISTENTES NO COMPARTMENT", reutilize recursos com data sources sempre que possível.
-- Se houver "ARQUIVOS TERRAFORM ATUAIS NO WORKSPACE", gere somente os arquivos corrigidos, com os mesmos nomes.
+- Se houver "ARQUIVOS TERRAFORM ATUAIS NO WORKSPACE", gere **SOMENTE os arquivos que precisam de correção**. Arquivos sem erro NÃO devem ser regenerados. Mantenha os mesmos nomes de arquivo. Se o erro está em `networking.tf`, gere APENAS `// filename: networking.tf` com o conteúdo corrigido. NUNCA regenere `variables.tf`, `outputs.tf` ou outros arquivos que não têm erro, a menos que o erro exija alteração neles (ex: variável faltando).
- Sempre responda com blocos `hcl`, depois **Resource Plan** (`+` criar, `~` reutilizar), uma breve explicação e uma seção `✅ Validação`.
- Valide: referências cruzadas, CIDRs, route tables, security lists, dependências, segurança e variáveis.
- Use apenas resource types válidos do OCI. Exemplos corretos:
@@ -2197,6 +2223,13 @@ Se pedirem AWS, Azure, GCP ou outro provider, recuse educadamente.
- Nunca invente recursos inexistentes.
- Use sempre HCL multi-line, nunca blocos inline com múltiplos argumentos.
+### Correção de erros (IMPORTANTE)
+- Quando o usuário envia um erro de plan/apply com arquivos existentes, gere SOMENTE o(s) arquivo(s) que corrigem o erro.
+- Exemplo: se o erro é em `service_gateways.tf`, retorne APENAS `// filename: service_gateways.tf` com o conteúdo corrigido.
+- Se a correção exige criar/alterar uma variável, inclua também `// filename: variables.tf` apenas com as variáveis novas/alteradas + todas existentes.
+- NUNCA regenere todos os arquivos do zero. Isso descarta o trabalho anterior e cria loops infinitos de correção.
+- Preserve os nomes de arquivo exatos. Não renomeie `drg_attachments_and_routes.tf` para `drg.tf`.
+
### Regras críticas
- `oci_core_drg_route_distribution_statement` deve usar `match_criteria`.
- Remote peering: APENAS UM LADO do par define `peer_id` e `peer_region_name`. O outro lado é criado SEM `peer_id`. Nunca gere dois RPCs apontando um para o outro (causa Cycle error). Exemplo: RPC_mad1 (sem peer_id) e RPC_mad3 (com peer_id = RPC_mad1.id, peer_region_name = var.region).
@@ -4021,11 +4054,13 @@ async def _chat_background(mid: str, sid: str, msg: ChatMsg, user: dict, genai_c
base_prompt = cfg_dict.get("system_prompt", "")
cfg_dict["system_prompt"] = f"{base_prompt}\n\n{config_hint}" if base_prompt else config_hint
- # ── Terraform agent: boost max_tokens to model limit ──
+ # ── Terraform agent: boost max_tokens to model limit + force reasoning_effort=high ──
if agent_type == "terraform":
tf_model_info = GENAI_MODELS.get(cfg_dict.get("model_id", ""), {})
tf_model_max = tf_model_info.get("max_tokens", 32768)
cfg_dict["max_tokens"] = tf_model_max
+ if tf_model_info.get("reasoning") and not cfg_dict.get("reasoning_effort"):
+ cfg_dict["reasoning_effort"] = "HIGH"
# ── Inject existing OCI resources for terraform agent ──
if agent_type == "terraform" and active_oci_id:
@@ -5129,11 +5164,22 @@ async def _terraform_exec(wid: str, action: str, user: dict):
if passphrase:
cred_block += f'\n private_key_password = "{passphrase}"'
- # Primary region: use var.region if declared in .tf files, otherwise hardcode from OCI config
+ # Primary region: MUST use var.region — model is required to declare it
with db() as c:
oci_row = c.execute("SELECT region FROM oci_configs WHERE id=?", (ws["oci_config_id"],)).fetchone()
primary_region = oci_row["region"] if oci_row else oci_cfg.get("region", "")
- region_value = 'var.region' if has_region_var else f'"{primary_region}"'
+ if not has_region_var:
+ error_msg = (
+ f'ERRO: O código Terraform não declarou variable "region". '
+ f'Isso é obrigatório para garantir que os recursos sejam provisionados na região correta. '
+ f'Adicione no variables.tf: variable "region" {{ type = string; default = "{primary_region}" }}'
+ )
+ log.warning(f"Workspace {wid}: missing variable 'region' — blocking plan")
+ with db() as c:
+ c.execute("UPDATE terraform_workspaces SET status='failed', error=?, updated_at=datetime('now') WHERE id=?",
+ (error_msg, wid))
+ return
+ region_value = 'var.region'
provider_tf = f'''terraform {{
required_providers {{
@@ -5165,8 +5211,13 @@ provider "oci" {{
# Auto-generate terraform.tfvars — scan declared variables and provide values from OCI config
with db() as c:
- oci_row = c.execute("SELECT compartment_id, region FROM oci_configs WHERE id=?", (ws["oci_config_id"],)).fetchone()
+ oci_row = c.execute("SELECT compartment_id, region, ssh_public_key FROM oci_configs WHERE id=?", (ws["oci_config_id"],)).fetchone()
comp_id = ws["compartment_id"] if ws["compartment_id"] else (_safe_dec(oci_row["compartment_id"]) if oci_row and oci_row["compartment_id"] else "")
+ try:
+ ssh_pub_key = oci_row["ssh_public_key"] if oci_row else ""
+ except (IndexError, KeyError):
+ ssh_pub_key = ""
+ ssh_pub_key = ssh_pub_key or ""
# Scan all declared variables in .tf files
declared_vars = set()
@@ -5185,6 +5236,8 @@ provider "oci" {{
"user_ocid": oci_cfg.get("user", ""),
"fingerprint": oci_cfg.get("fingerprint", ""),
"private_key_path": oci_cfg.get("key_file", ""),
+ "ssh_public_key": ssh_pub_key,
+ "ssh_authorized_keys": ssh_pub_key,
}
# NOTE: "region" is intentionally NOT included — Terraform uses the default from variable declarations
for vname, vval in oci_var_map.items():
diff --git a/frontend/index.html b/frontend/index.html
index e11f3ac..02d1151 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -375,11 +375,15 @@ html .spinner,html [class*="kpi-bar-fill"],html canvas{transition:none!important
.exp-cat{padding:.3rem .5rem;font-size:.6rem}
}
/* ── Terraform Agent ── */
-.tf-wrap{display:flex;flex-direction:column;height:calc(100vh - 56px - 3rem);overflow:hidden}
-.tf-chat{flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0}
+.tf-wrap{display:flex;flex-direction:row;height:calc(100vh - 56px - 3rem);overflow:hidden}
+.tf-left{flex:6;display:flex;flex-direction:column;overflow:hidden;min-width:0}
+.tf-chat{flex:7;display:flex;flex-direction:column;overflow:hidden;min-height:0}
+.tf-output-panel{flex:4;display:flex;flex-direction:column;overflow:hidden;border-left:1px solid var(--bd);min-width:0}
.tf-toolbar{display:flex;align-items:center;gap:.5rem;padding:.45rem .7rem;border-bottom:1px solid var(--bd);flex-wrap:wrap;background:var(--bg)}
.tf-msgs{flex:1;overflow-y:auto;padding:.7rem}
-.tfp-act{display:inline-flex;align-items:center;gap:.25rem;padding:.2rem .5rem;font-size:.6rem;border-radius:5px;border:1px solid var(--bd);background:var(--bg2);color:var(--t2);cursor:pointer;transition:all .15s;font-family:inherit;line-height:1.3}
+.tfp-acts{display:flex;gap:.35rem;margin-top:.5rem;padding-top:.45rem;border-top:1px solid var(--bd)}
+.tfp-act{display:inline-flex;align-items:center;gap:.2rem;padding:.2rem .45rem;font-size:.62rem;border-radius:5px;border:1px solid var(--bd);background:var(--bg2);color:var(--t2);cursor:pointer;transition:all .15s;font-family:inherit;line-height:1.3}
+.tfp-act svg{width:11px;height:11px}
.tfp-act:hover{background:var(--bg3);border-color:var(--t4)}
.tfp-act-tf{background:#7b42bc18;border-color:#7b42bc55;color:#7b42bc}
.tfp-act-tf:hover{background:#7b42bc30;border-color:#7b42bc}
@@ -389,19 +393,17 @@ html .spinner,html [class*="kpi-bar-fill"],html canvas{transition:none!important
.tf-resize:hover,.tf-resize.dragging{background:var(--pp);opacity:.7}
.tf-resize::after{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:28px;height:3px;border-top:1px solid var(--t4);border-bottom:1px solid var(--t4);opacity:.4}
.tf-resize:hover::after{border-color:var(--pp);opacity:.8}
-.tf-bottom{flex:1;display:flex;flex-direction:column;overflow:hidden;border-top:1px solid var(--bd);background:var(--bg1);min-height:0}
+.tf-bottom{flex:3;display:flex;flex-direction:column;overflow:hidden;border-top:1px solid var(--bd);background:var(--bg1);min-height:0}
.tf-bottom-tabs{display:flex;align-items:center;gap:0;border-bottom:1px solid var(--bd);background:var(--bg);padding:0 .5rem}
.tf-btab{padding:.4rem .75rem;font-size:.72rem;font-weight:600;cursor:pointer;border-bottom:2px solid transparent;color:var(--t3);display:flex;align-items:center;gap:.3rem;transition:all .15s}
.tf-btab:hover{color:var(--t1)}
.tf-btab.active{color:var(--pp);border-bottom-color:var(--pp)}
.tf-btab svg{width:14px;height:14px}
.tf-bottom-actions{margin-left:auto;display:flex;align-items:center;gap:.4rem;padding-right:.3rem}
-.tf-bottom-content{flex:1;overflow:hidden;display:flex;flex-direction:row}
-.tf-bc-left{flex:1;overflow:auto;padding:.5rem .7rem;min-width:0}
-.tf-bc-right{width:50%;overflow:auto;border-left:1px solid var(--bd);flex-shrink:0;display:flex;flex-direction:column}
-.tf-bc-right .tf-term-hdr{padding:.3rem .6rem;font-size:.68rem;font-weight:700;color:var(--t2);background:var(--bg);border-bottom:1px solid var(--bd);display:flex;align-items:center;gap:.4rem}
-.tf-bc-right .tf-term-hdr svg{width:12px;height:12px;fill:var(--t2)}
-.tf-bc-right .tf-term-body{flex:1;overflow:auto;background:var(--bg);padding:.5rem;font-family:var(--fm);font-size:.68rem;line-height:1.5;color:var(--t2);white-space:pre-wrap;word-break:break-word}
+.tf-bottom-content{flex:1;overflow:auto;padding:.5rem .7rem}
+.tf-output-panel .tf-term-hdr{padding:.3rem .6rem;font-size:.68rem;font-weight:700;color:var(--t2);background:var(--bg);border-bottom:1px solid var(--bd);display:flex;align-items:center;gap:.4rem}
+.tf-output-panel .tf-term-hdr svg{width:12px;height:12px;fill:var(--t2)}
+.tf-output-panel .tf-term-body{flex:1;overflow:auto;background:var(--bg);padding:.5rem;font-family:var(--fm);font-size:.68rem;line-height:1.5;color:var(--t2);white-space:pre-wrap;word-break:break-word}
.tf-file{display:flex;align-items:center;gap:.5rem;padding:.45rem .6rem;border-radius:8px;border:1px solid var(--bd);margin-bottom:.4rem;background:var(--bg);transition:border-color .15s;cursor:pointer}
.tf-file:hover{border-color:var(--pp)}
.tf-file.editing{border-color:var(--pp);background:rgba(191,90,242,.06)}
@@ -455,6 +457,9 @@ html .spinner,html [class*="kpi-bar-fill"],html canvas{transition:none!important
.tf-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--t3);font-size:.76rem;gap:.3rem}
.tf-empty svg{width:40px;height:40px;fill:#7b42bc;opacity:.3}
@media(max-width:768px){
+.tf-wrap{flex-direction:column}
+.tf-left{flex:none;height:60%}
+.tf-output-panel{flex:none;height:40%;border-left:none;border-top:1px solid var(--bd)}
.tf-bottom{min-height:0}
}
@@ -507,6 +512,7 @@ const IC={
python:_i('
Descreva a infraestrutura OCI que você precisa
O agente vai gerar um prompt estruturado e otimizado para o Terraform Agent.
| Tenancy | Region | Detalhes | Ações | |
|---|---|---|---|---|
| ${c.tenancy_name} ${c.created_at} | ${c.region} | -${IC.key} •••••••••••••••••••• ${IC.building} ${c.tenancy_ocid} ${IC.user} ${c.user_ocid} ${IC.pkg} ${c.compartment_id||'—'} |
+${IC.key} •••••••••••••••••••• ${IC.building} ${c.tenancy_ocid} ${IC.user} ${c.user_ocid} ${IC.pkg} ${c.compartment_id||'—'} ${IC.key} SSH: ${c.ssh_public_key_preview||'não configurada'} |
Nenhuma credencial registrada.
Nenhuma credencia
'+eo.ssh_public_key_preview+'