From 4e57a896fee765fe4b4ca321f7a84f06dd0dd036 Mon Sep 17 00:00:00 2001 From: andersonid Date: Thu, 25 Sep 2025 16:50:07 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20interface=20com=20acorde=C3=B5es=20por?= =?UTF-8?q?=20namespace=20e=20pagina=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/routes.py | 87 ++++++- app/core/kubernetes_client.py | 16 +- app/static/index.html | 438 +++++++++++++++++++++++++++++++++- 3 files changed, 524 insertions(+), 17 deletions(-) diff --git a/app/api/routes.py b/app/api/routes.py index 5fdafe6..6534ce8 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -123,9 +123,11 @@ async def get_pods( async def get_validations( namespace: Optional[str] = None, severity: Optional[str] = None, + page: int = 1, + page_size: int = 50, k8s_client=Depends(get_k8s_client) ): - """Listar validações de recursos""" + """Listar validações de recursos com paginação""" try: # Coletar pods if namespace: @@ -146,12 +148,93 @@ async def get_validations( v for v in all_validations if v.severity == severity ] - return all_validations + # Paginação + total = len(all_validations) + start = (page - 1) * page_size + end = start + page_size + paginated_validations = all_validations[start:end] + + return { + "validations": paginated_validations, + "pagination": { + "page": page, + "page_size": page_size, + "total": total, + "total_pages": (total + page_size - 1) // page_size + } + } except Exception as e: logger.error(f"Erro ao obter validações: {e}") raise HTTPException(status_code=500, detail=str(e)) +@api_router.get("/validations/by-namespace") +async def get_validations_by_namespace( + severity: Optional[str] = None, + page: int = 1, + page_size: int = 20, + k8s_client=Depends(get_k8s_client) +): + """Listar validações agrupadas por namespace com paginação""" + try: + # Coletar todos os pods + pods = await k8s_client.get_all_pods() + + # Validar recursos e agrupar por namespace + namespace_validations = {} + for pod in pods: + pod_validations = validation_service.validate_pod_resources(pod) + + if pod.namespace not in namespace_validations: + namespace_validations[pod.namespace] = { + "namespace": pod.namespace, + "pods": {}, + "total_validations": 0, + "severity_breakdown": {"error": 0, "warning": 0} + } + + # Agrupar validações por pod + if pod.name not in namespace_validations[pod.namespace]["pods"]: + namespace_validations[pod.namespace]["pods"][pod.name] = { + "pod_name": pod.name, + "validations": [] + } + + # Filtrar por severidade se especificado + if severity: + pod_validations = [v for v in pod_validations if v.severity == severity] + + namespace_validations[pod.namespace]["pods"][pod.name]["validations"] = pod_validations + namespace_validations[pod.namespace]["total_validations"] += len(pod_validations) + + # Contar severidades + for validation in pod_validations: + namespace_validations[pod.namespace]["severity_breakdown"][validation.severity] += 1 + + # Converter para lista e ordenar por total de validações + namespace_list = list(namespace_validations.values()) + namespace_list.sort(key=lambda x: x["total_validations"], reverse=True) + + # Paginação + total = len(namespace_list) + start = (page - 1) * page_size + end = start + page_size + paginated_namespaces = namespace_list[start:end] + + return { + "namespaces": paginated_namespaces, + "pagination": { + "page": page, + "page_size": page_size, + "total": total, + "total_pages": (total + page_size - 1) // page_size + } + } + + except Exception as e: + logger.error(f"Erro ao obter validações por namespace: {e}") + raise HTTPException(status_code=500, detail=str(e)) + @api_router.get("/vpa/recommendations") async def get_vpa_recommendations( namespace: Optional[str] = None, diff --git a/app/core/kubernetes_client.py b/app/core/kubernetes_client.py index 5160264..c390710 100644 --- a/app/core/kubernetes_client.py +++ b/app/core/kubernetes_client.py @@ -162,18 +162,10 @@ class K8sClient: recommendations = [] try: - # Listar VPA objects em todos os namespaces - vpa_list = self.autoscaling_v1.list_vertical_pod_autoscaler_for_all_namespaces() - - for vpa in vpa_list.items: - if vpa.status and vpa.status.recommendation: - recommendation = VPARecommendation( - name=vpa.metadata.name, - namespace=vpa.metadata.namespace, - target_ref=vpa.spec.target_ref, - recommendations=vpa.status.recommendation - ) - recommendations.append(recommendation) + # VPA não está disponível na API padrão do Kubernetes + # TODO: Implementar usando Custom Resource Definition (CRD) + logger.warning("VPA não está disponível na API padrão do Kubernetes") + return [] logger.info(f"Coletadas {len(recommendations)} recomendações VPA") return recommendations diff --git a/app/static/index.html b/app/static/index.html index f11e6c1..4b3610a 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -226,6 +226,185 @@ display: none; } + /* Accordion Styles */ + .accordion { + border: 1px solid #ddd; + border-radius: 8px; + margin-bottom: 1rem; + overflow: hidden; + } + + .accordion-header { + background: #f8f9fa; + padding: 1rem 1.5rem; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #ddd; + transition: background-color 0.2s; + } + + .accordion-header:hover { + background: #e9ecef; + } + + .accordion-header.active { + background: #cc0000; + color: white; + } + + .accordion-title { + font-weight: 600; + font-size: 1.1rem; + } + + .accordion-stats { + display: flex; + gap: 1rem; + font-size: 0.9rem; + } + + .accordion-stat { + background: rgba(255,255,255,0.2); + padding: 0.25rem 0.5rem; + border-radius: 4px; + } + + .accordion-content { + padding: 0; + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; + } + + .accordion-content.active { + max-height: 1000px; + } + + .pod-list { + padding: 1rem 1.5rem; + } + + .pod-item { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 1rem; + margin-bottom: 0.5rem; + } + + .pod-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; + } + + .pod-name { + font-weight: 600; + color: #cc0000; + } + + .pod-validations-count { + background: #6c757d; + color: white; + padding: 0.25rem 0.5rem; + border-radius: 12px; + font-size: 0.8rem; + } + + .validation-list { + margin-top: 0.5rem; + } + + .validation-item { + padding: 0.75rem; + border-left: 4px solid #ccc; + margin: 0.25rem 0; + background: white; + border-radius: 4px; + } + + .validation-item.error { + border-left-color: #dc3545; + } + + .validation-item.warning { + border-left-color: #ffc107; + } + + .validation-item.critical { + border-left-color: #dc3545; + font-weight: bold; + } + + /* Pagination Styles */ + .pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; + margin: 2rem 0; + } + + .pagination button { + padding: 0.5rem 1rem; + border: 1px solid #ddd; + background: white; + cursor: pointer; + border-radius: 4px; + transition: all 0.2s; + } + + .pagination button:hover:not(:disabled) { + background: #f8f9fa; + } + + .pagination button:disabled { + background: #f8f9fa; + color: #6c757d; + cursor: not-allowed; + } + + .pagination button.active { + background: #cc0000; + color: white; + border-color: #cc0000; + } + + .pagination-info { + color: #666; + font-size: 0.9rem; + } + + /* Filters */ + .filters { + display: flex; + gap: 1rem; + margin-bottom: 1rem; + flex-wrap: wrap; + align-items: center; + } + + .filter-group { + display: flex; + flex-direction: column; + gap: 0.25rem; + } + + .filter-group label { + font-size: 0.9rem; + color: #666; + } + + .filter-group select, + .filter-group input { + padding: 0.5rem; + border: 1px solid #ddd; + border-radius: 4px; + } + @media (max-width: 768px) { .container { padding: 1rem; @@ -239,6 +418,22 @@ flex-direction: column; align-items: stretch; } + + .filters { + flex-direction: column; + align-items: stretch; + } + + .accordion-stats { + flex-direction: column; + gap: 0.25rem; + } + + .pod-header { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } } @@ -274,7 +469,7 @@

Controles

- +
@@ -302,7 +497,32 @@ @@ -325,6 +545,9 @@