feat: state persistence — Terminal + Explorer survive navigation (Zustand stores)
This commit is contained in:
113
backend/app.py
113
backend/app.py
@@ -1096,6 +1096,93 @@ async def test_oci(cid: str, u=Depends(require("admin","user"))):
|
||||
return {"status":"error","message":str(e)[:500]}
|
||||
|
||||
# ── OCI CLI Terminal ─────────────────────────────────────────────────────────
|
||||
# OCID resource type → OCI CLI get command mapping
|
||||
_OCID_CMD_MAP = {
|
||||
"instance": "oci compute instance get --instance-id",
|
||||
"image": "oci compute image get --image-id",
|
||||
"vcn": "oci network vcn get --vcn-id",
|
||||
"subnet": "oci network subnet get --subnet-id",
|
||||
"securitylist": "oci network security-list get --security-list-id",
|
||||
"routetable": "oci network route-table get --rt-id",
|
||||
"internetgateway": "oci network internet-gateway get --ig-id",
|
||||
"natgateway": "oci network nat-gateway get --nat-gateway-id",
|
||||
"servicegateway": "oci network service-gateway get --service-gateway-id",
|
||||
"drg": "oci network drg get --drg-id",
|
||||
"drgattachment": "oci network drg-attachment get --drg-attachment-id",
|
||||
"localpeeringgateway": "oci network local-peering-gateway get --local-peering-gateway-id",
|
||||
"networksecuritygroup": "oci network nsg get --nsg-id",
|
||||
"vnic": "oci network vnic get --vnic-id",
|
||||
"privateip": "oci network private-ip get --private-ip-id",
|
||||
"publicip": "oci network public-ip get --public-ip-id",
|
||||
"dhcpoptions": "oci network dhcp-options get --dhcp-id",
|
||||
"cpe": "oci network cpe get --cpe-id",
|
||||
"ipsecconnection": "oci network ip-sec-connection get --ipsc-id",
|
||||
"volume": "oci bv volume get --volume-id",
|
||||
"bootvolume": "oci bv boot-volume get --boot-volume-id",
|
||||
"volumebackup": "oci bv backup get --volume-backup-id",
|
||||
"bootvolumereplica": "oci bv boot-volume-replica get --boot-volume-replica-id",
|
||||
"volumegroup": "oci bv volume-group get --volume-group-id",
|
||||
"bucket": "oci os bucket get --bucket-name",
|
||||
"compartment": "oci iam compartment get --compartment-id",
|
||||
"tenancy": "oci iam tenancy get --tenancy-id",
|
||||
"user": "oci iam user get --user-id",
|
||||
"group": "oci iam group get --group-id",
|
||||
"policy": "oci iam policy get --policy-id",
|
||||
"dynamicgroup": "oci iam dynamic-group get --dynamic-group-id",
|
||||
"identityprovider": "oci iam identity-provider get --identity-provider-id",
|
||||
"autonomousdatabase": "oci db autonomous-database get --autonomous-database-id",
|
||||
"dbsystem": "oci db system get --db-system-id",
|
||||
"database": "oci db database get --database-id",
|
||||
"dbhome": "oci db db-home get --db-home-id",
|
||||
"dbnode": "oci db node get --db-node-id",
|
||||
"mysqldbsystem": "oci mysql db-system get --db-system-id",
|
||||
"loadbalancer": "oci lb load-balancer get --load-balancer-id",
|
||||
"networkloadbalancer": "oci nlb network-load-balancer get --network-load-balancer-id",
|
||||
"certificate": "oci certs-mgmt certificate get --certificate-id",
|
||||
"vault": "oci kms management vault get --vault-id",
|
||||
"key": "oci kms management key get --key-id",
|
||||
"secret": "oci vault secret get --secret-id",
|
||||
"fnapp": "oci fn application get --application-id",
|
||||
"fnfunc": "oci fn function get --function-id",
|
||||
"apigateway": "oci api-gateway gateway get --gateway-id",
|
||||
"containerinstance": "oci container-instances container-instance get --container-instance-id",
|
||||
"cluster": "oci ce cluster get --cluster-id",
|
||||
"nodepool": "oci ce node-pool get --node-pool-id",
|
||||
"filesystem": "oci fs file-system get --file-system-id",
|
||||
"mounttarget": "oci fs mount-target get --mount-target-id",
|
||||
"alarm": "oci monitoring alarm get --alarm-id",
|
||||
"loggroup": "oci logging log-group get --log-group-id",
|
||||
"log": "oci logging log get --log-id",
|
||||
"topic": "oci ons topic get --topic-id",
|
||||
"subscription": "oci ons subscription get --subscription-id",
|
||||
"stream": "oci streaming stream get --stream-id",
|
||||
"streampool": "oci streaming stream-pool get --stream-pool-id",
|
||||
"dnszone": "oci dns zone get --zone-name-or-id",
|
||||
"networkfirewall": "oci network-firewall network-firewall get --network-firewall-id",
|
||||
"networkfirewallpolicy": "oci network-firewall network-firewall-policy get --network-firewall-policy-id",
|
||||
"waaspolicy": "oci waas waas-policy get --waas-policy-id",
|
||||
"instancepool": "oci compute-management instance-pool get --instance-pool-id",
|
||||
"instanceconfiguration": "oci compute-management instance-configuration get --instance-configuration-id",
|
||||
"capacityreservation": "oci compute capacity-reservation get --capacity-reservation-id",
|
||||
"dedicatedvmhost": "oci compute dedicated-vm-host get --dedicated-vm-host-id",
|
||||
}
|
||||
|
||||
def _resolve_ocid_command(ocid: str) -> str | None:
|
||||
"""Parse an OCID and return the corresponding OCI CLI get command."""
|
||||
parts = ocid.strip().split(".")
|
||||
if len(parts) < 4 or parts[0] != "ocid1":
|
||||
return None
|
||||
resource_type = parts[1].lower()
|
||||
# Extract region from OCID if present (4th part, non-empty for regional resources)
|
||||
region = parts[3] if len(parts) > 3 and parts[3] and parts[3] != parts[2] else None
|
||||
cmd_template = _OCID_CMD_MAP.get(resource_type)
|
||||
if not cmd_template:
|
||||
return None
|
||||
cmd = f"{cmd_template} {ocid}"
|
||||
if region:
|
||||
cmd += f" --region {region}"
|
||||
return cmd
|
||||
|
||||
_TERMINAL_BLOCKED_PATTERNS = re.compile(
|
||||
r'(rm\s|mv\s|cp\s|chmod\s|chown\s|sudo\s|bash\s|sh\s|curl\s|wget\s|python|pip\s|apt\s|yum\s|>\s|>>\s|\|)',
|
||||
re.IGNORECASE
|
||||
@@ -1111,7 +1198,32 @@ async def terminal_execute(req: dict, u=Depends(current_user)):
|
||||
if not command:
|
||||
raise HTTPException(400, "Comando vazio")
|
||||
_verify_config_access("oci", oci_config_id, u)
|
||||
# Auto-lookup: OCID pasted directly → resolve to oci get command
|
||||
resolved_cmd = None
|
||||
if command.startswith("ocid1."):
|
||||
resolved_cmd = _resolve_ocid_command(command)
|
||||
if resolved_cmd:
|
||||
command = resolved_cmd
|
||||
else:
|
||||
raise HTTPException(400, f"Tipo de recurso não reconhecido no OCID: {command.split('.')[1]}")
|
||||
# find <query> → search resources by display name using OCI Search service
|
||||
elif command.startswith("find "):
|
||||
query = command[5:].strip()
|
||||
if not query:
|
||||
raise HTTPException(400, "Uso: find <nome-do-recurso> | find %partial% | find 10.0.1.5")
|
||||
# Detect IP address → search by IP
|
||||
if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', query):
|
||||
search_query = f"query privateip resources where ipAddress = '{query}'"
|
||||
elif '%' in query:
|
||||
# Partial match with LIKE
|
||||
search_query = f"query all resources where displayName =~ '{query}'"
|
||||
else:
|
||||
search_query = f"query all resources where displayName = '{query}'"
|
||||
resolved_cmd = f"oci search resource structured-search --query-text \"{search_query}\" --limit 20"
|
||||
command = resolved_cmd
|
||||
# Security: only allow 'oci' commands
|
||||
if command == "oci":
|
||||
command = "oci --help"
|
||||
if not command.startswith("oci "):
|
||||
raise HTTPException(400, "Apenas comandos 'oci' são permitidos. Ex: oci iam user list")
|
||||
if _TERMINAL_BLOCKED_PATTERNS.search(command):
|
||||
@@ -1141,6 +1253,7 @@ async def terminal_execute(req: dict, u=Depends(current_user)):
|
||||
"exit_code": exit_code,
|
||||
"output": output[:50000],
|
||||
"error": error[:5000] if exit_code != 0 else "",
|
||||
"resolved_cmd": resolved_cmd,
|
||||
}
|
||||
except asyncio.TimeoutError:
|
||||
with db() as c:
|
||||
|
||||
Reference in New Issue
Block a user