diff --git a/casa-explorer-ui/src/components/mas/index.ts b/casa-explorer-ui/src/components/mas/index.ts index 7103f82b..be56047d 100644 --- a/casa-explorer-ui/src/components/mas/index.ts +++ b/casa-explorer-ui/src/components/mas/index.ts @@ -16,4 +16,3 @@ export {MASTable, MASDataTable, createMASColumns} from './mas-list'; export {MASInfoTab, MASAppsTab, MASScopesTab, MASTracesTab, MASDenyConditionsTab, MASAppsTable} from './mas-details'; -export {MASGraphView} from './mas-graph'; diff --git a/casa-explorer-ui/src/components/mas/mas-details/mas-apps-table.tsx b/casa-explorer-ui/src/components/mas/mas-details/mas-apps-table.tsx index 577a8207..5668ca82 100644 --- a/casa-explorer-ui/src/components/mas/mas-details/mas-apps-table.tsx +++ b/casa-explorer-ui/src/components/mas/mas-details/mas-apps-table.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import {useCallback, useMemo, useState} from 'react'; +import {lazy, Suspense, useCallback, useMemo, useState} from 'react'; import type React from 'react'; import {useNavigate} from 'react-router-dom'; import {PATHS} from '@/router/paths'; @@ -42,7 +42,7 @@ import {Button} from '@/components/ui/button'; import {Card} from '@/components/ui/card'; import {Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle} from '@/components/ui/dialog'; import {Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetClose} from '@/components/ui/sheet'; -import {MASGraphView} from '@/components/mas/mas-graph'; +const MASGraphView = lazy(() => import('@/components/mas/mas-graph/mas-graph-view').then(m => ({default: m.MASGraphView}))); import {toast} from 'sonner'; import type {MAS} from '@/types/mas.types'; import type {App, AppType, Tool} from '@/types/app.types'; @@ -348,13 +348,15 @@ export function MASAppsTable({mas, apps}: MASAppsTableProps) { {view === 'graph' && (
- + + +
)} diff --git a/casa-explorer-ui/src/router/router.tsx b/casa-explorer-ui/src/router/router.tsx index 9d908228..17461bad 100644 --- a/casa-explorer-ui/src/router/router.tsx +++ b/casa-explorer-ui/src/router/router.tsx @@ -14,27 +14,31 @@ * limitations under the License. */ +import {lazy, Suspense} from 'react'; import {createBrowserRouter} from 'react-router-dom'; import {AppLayout} from '@/components/app-layout'; -import {DashboardPage} from '@/pages/dashboard-page'; -import {MASPage} from '@/pages/mas/mas-page'; -import {MASDetailPage} from '@/pages/mas/mas-detail-page'; -import {AuthRequestsPage} from '@/pages/auth-requests/auth-requests-page'; -import {AuthRequestDetailPage} from '@/pages/auth-requests/auth-request-detail-page'; -import {NotFoundPage} from '@/pages/not-found-page'; import {PATHS} from './paths'; +const DashboardPage = lazy(() => import('@/pages/dashboard-page').then(m => ({default: m.DashboardPage}))); +const MASPage = lazy(() => import('@/pages/mas/mas-page').then(m => ({default: m.MASPage}))); +const MASDetailPage = lazy(() => import('@/pages/mas/mas-detail-page').then(m => ({default: m.MASDetailPage}))); +const AuthRequestsPage = lazy(() => import('@/pages/auth-requests/auth-requests-page').then(m => ({default: m.AuthRequestsPage}))); +const AuthRequestDetailPage = lazy(() => import('@/pages/auth-requests/auth-request-detail-page').then(m => ({default: m.AuthRequestDetailPage}))); +const NotFoundPage = lazy(() => import('@/pages/not-found-page').then(m => ({default: m.NotFoundPage}))); + +const S = ({children}: {children: React.ReactNode}) => {children}; + export const router = createBrowserRouter([ { path: PATHS.dashboard, element: , children: [ - {index: true, element: }, - {path: PATHS.mas.list, element: }, - {path: PATHS.mas.detailPattern, element: }, - {path: PATHS.authRequests.list, element: }, - {path: PATHS.authRequests.detailPattern, element: }, - {path: '*', element: } + {index: true, element: }, + {path: PATHS.mas.list, element: }, + {path: PATHS.mas.detailPattern, element: }, + {path: PATHS.authRequests.list, element: }, + {path: PATHS.authRequests.detailPattern, element: }, + {path: '*', element: } ] } ]); diff --git a/casa-explorer-ui/vite.config.ts b/casa-explorer-ui/vite.config.ts index 57a63a53..ac5c7fc9 100644 --- a/casa-explorer-ui/vite.config.ts +++ b/casa-explorer-ui/vite.config.ts @@ -27,6 +27,27 @@ export default defineConfig({ '@': path.resolve(__dirname, './src') } }, + build: { + chunkSizeWarningLimit: 1600, + rollupOptions: { + output: { + manualChunks(id) { + if (!id.includes('node_modules')) return; + if (id.includes('elkjs') || id.includes('@xyflow')) return 'vendor-flow'; + if (id.includes('recharts') || id.includes('/d3-') || id.includes('/d3/')) return 'vendor-charts'; + if (id.includes('@dnd-kit')) return 'vendor-dnd'; + if (id.includes('@tanstack')) return 'vendor-query'; + if (id.includes('@tabler') || id.includes('lucide-react')) return 'vendor-icons'; + if (id.includes('react-hook-form') || id.includes('@hookform') || id.includes('/zod/')) return 'vendor-forms'; + if ( + id.includes('@radix-ui') || id.includes('radix-ui') || + id.includes('/cmdk/') || id.includes('/vaul/') + ) return 'vendor-radix'; + if (id.includes('react-router') || id.includes('react-dom') || id.match(/\/react\//) || id.includes('/sonner/') || id.includes('next-themes')) return 'vendor-react'; + } + } + } + }, server: { port: 1234, open: true diff --git a/deployments/helm/casa-runtime/templates/ui-explorer/configmap.yaml b/deployments/helm/casa-runtime/templates/ui-explorer/configmap.yaml index 8bd5b9b0..9a054dfc 100644 --- a/deployments/helm/casa-runtime/templates/ui-explorer/configmap.yaml +++ b/deployments/helm/casa-runtime/templates/ui-explorer/configmap.yaml @@ -51,6 +51,7 @@ data: location /api/ { proxy_pass {{ include "casa-runtime.authServiceUrl" . }}/; proxy_http_version 1.1; + proxy_buffering off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/deployments/helm/casa-runtime/values.yaml b/deployments/helm/casa-runtime/values.yaml index c6ddf975..c549f8a0 100644 --- a/deployments/helm/casa-runtime/values.yaml +++ b/deployments/helm/casa-runtime/values.yaml @@ -103,7 +103,8 @@ uiExplorer: nginx: # When true, nginx proxies /api/ → auth-service (port 8000). # Build the UI image with VITE_API_BASE_URL=/api when this is enabled. - # When false, the UI image must be built with the full auth-service URL. + # When false (minikube), build with VITE_API_BASE_URL=http://api. and + # enable authService.ingress so the browser calls the auth-service directly. apiProxyEnabled: true # ─── PostgreSQL (auth-service backend) ─────────────────────────────────────── diff --git a/scripts/dev/local-setup-standalone.sh b/scripts/dev/local-setup-standalone.sh index 17621e5d..004ca335 100755 --- a/scripts/dev/local-setup-standalone.sh +++ b/scripts/dev/local-setup-standalone.sh @@ -111,7 +111,9 @@ log "Step 4 — Build control plane images" docker build -f deployments/docker/Dockerfile -t casa-auth-server:local . docker build -f deployments/docker/Dockerfile.keycloak -t casa-auth-server-keycloak:local deployments/docker docker build -f deployments/docker/Dockerfile.operator -t casa-operator:local . -docker build --no-cache -f deployments/docker/Dockerfile.ui -t casa-auth-server-ui:local . +docker build --no-cache -f deployments/docker/Dockerfile.ui \ + --build-arg VITE_API_BASE_URL=http://api.casa.outshift.ai \ + -t casa-auth-server-ui:local . docker build -f deployments/docker/Dockerfile.extauth -t ext-auth-service:local . log "Step 4 — Build demo images" @@ -164,7 +166,11 @@ helm upgrade --install casa-dev \ --set uiExplorer.ingress.className=nginx \ --set uiExplorer.ingress.apiDomainName=casa.outshift.ai \ --set uiExplorer.ingress.domainPrefix=explorer \ - --set-string 'uiExplorer.ingress.annotations.nginx\.ingress\.kubernetes\.io/ssl-redirect=false' \ + --set uiExplorer.nginx.apiProxyEnabled=false \ + --set authService.ingress.enabled=true \ + --set authService.ingress.className=nginx \ + --set authService.ingress.apiDomainName=casa.outshift.ai \ + --set authService.ingress.domainPrefix=api \ --set keycloak.image.repository=casa-auth-server-keycloak \ --set keycloak.image.tag=local \ --set keycloak.image.pullPolicy=Never \ @@ -233,7 +239,6 @@ helm upgrade --install casa-demo \ --set 'chatUis[0].ingress.className=nginx' \ --set 'chatUis[0].ingress.apiDomainName=casa.outshift.ai' \ --set 'chatUis[0].ingress.domainPrefix=chat-safe' \ - --set-string 'chatUis[0].ingress.annotations.nginx\.ingress\.kubernetes\.io/ssl-redirect=false' \ --set 'chatUis[1].name=compromised' \ --set 'chatUis[1].docker.registry=' \ --set 'chatUis[1].docker.image=demo-chat-ui' \ @@ -243,7 +248,6 @@ helm upgrade --install casa-demo \ --set 'chatUis[1].ingress.className=nginx' \ --set 'chatUis[1].ingress.apiDomainName=casa.outshift.ai' \ --set 'chatUis[1].ingress.domainPrefix=chat-compromised' \ - --set-string 'chatUis[1].ingress.annotations.nginx\.ingress\.kubernetes\.io/ssl-redirect=false' \ --set "llmCredentials.apiBaseUrl=${CASA_LLM_API_BASE_URL}" \ --set "llmCredentials.apiKey=${CASA_LLM_API_KEY}" \ --set 'masSafe.name=CASA Demo Safe' \ @@ -257,6 +261,39 @@ helm upgrade --install casa-demo \ --set 'masCompromised.enabledToolChecks[2]=AI_POWERED_TOOL_MATCH' \ --set "masCompromised.llm_host=${CASA_LLM_HOST}" +log "Step 6b. — Patching ingress annotations (minikube-specific)" +kubectl annotate ingress \ + casa-dev-ui-explorer \ + -n "$NAMESPACE" \ + "nginx.ingress.kubernetes.io/ssl-redirect=false" \ + "nginx.ingress.kubernetes.io/proxy-buffer-size=256k" \ + "nginx.ingress.kubernetes.io/proxy-buffers-number=8" \ + "nginx.ingress.kubernetes.io/proxy-body-size=20m" \ + "nginx.ingress.kubernetes.io/proxy-read-timeout=300" \ + "nginx.ingress.kubernetes.io/proxy-send-timeout=300" \ + --overwrite + +kubectl annotate ingress \ + casa-dev-auth-service \ + -n "$NAMESPACE" \ + "nginx.ingress.kubernetes.io/ssl-redirect=false" \ + "nginx.ingress.kubernetes.io/proxy-buffering=off" \ + "nginx.ingress.kubernetes.io/proxy-buffer-size=128k" \ + "nginx.ingress.kubernetes.io/proxy-read-timeout=300" \ + "nginx.ingress.kubernetes.io/proxy-send-timeout=300" \ + --overwrite + +kubectl annotate ingress \ + casa-demo-chat-ui-safe \ + casa-demo-chat-ui-compromised \ + -n "$NAMESPACE" \ + "nginx.ingress.kubernetes.io/ssl-redirect=false" \ + "nginx.ingress.kubernetes.io/proxy-buffering=off" \ + "nginx.ingress.kubernetes.io/proxy-buffer-size=128k" \ + "nginx.ingress.kubernetes.io/proxy-read-timeout=300" \ + "nginx.ingress.kubernetes.io/proxy-send-timeout=300" \ + --overwrite + # --------------------------------------------------------------------------- # 7. Verify # --------------------------------------------------------------------------- @@ -293,11 +330,11 @@ kubectl port-forward -n "$NAMESPACE" svc/casa-dev-postgres-auth 5432:5432 & kubectl port-forward -n "$NAMESPACE" svc/casa-dev-keycloak 8080:8080 & kubectl port-forward -n "$NAMESPACE" svc/jaeger 16686:16686 & -# nginx ingress controller — exposes all 3 UIs on port 80 (requires sudo on macOS) +# nginx ingress controller — exposes all UIs on port 80 (requires sudo on macOS) sudo -n kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller 80:80 & log "Step 8 — Updating /etc/hosts for ingress hostnames" -HOSTS_LINE="127.0.0.1 explorer.casa.outshift.ai chat-safe.casa.outshift.ai chat-compromised.casa.outshift.ai" +HOSTS_LINE="127.0.0.1 explorer.casa.outshift.ai api.casa.outshift.ai chat-safe.casa.outshift.ai chat-compromised.casa.outshift.ai" if grep -q "casa.outshift.ai" /etc/hosts; then sudo sed -i '' '/casa\.outshift\.ai/d' /etc/hosts fi @@ -311,7 +348,8 @@ echo " Keycloak → http://localhost:8080" echo " Jaeger traces → http://localhost:16686" echo "" echo "UIs available via nginx ingress:" -echo " Explorer UI → http://explorer.casa.outshift.ai" +echo " Explorer UI → http://explorer.casa.outshift.ai" +echo " Auth API → http://api.casa.outshift.ai" echo " Safe chat UI → http://chat-safe.casa.outshift.ai" echo " Compromised UI → http://chat-compromised.casa.outshift.ai" echo "" diff --git a/src/casa_auth_server/telemetry/tracer_repository.py b/src/casa_auth_server/telemetry/tracer_repository.py index 59246345..cced7709 100644 --- a/src/casa_auth_server/telemetry/tracer_repository.py +++ b/src/casa_auth_server/telemetry/tracer_repository.py @@ -21,7 +21,7 @@ from uuid import UUID, uuid4 from pydantic import BaseModel, ConfigDict -from sqlalchemy import DateTime, Integer, String, case, literal, union_all +from sqlalchemy import DateTime, Index, Integer, String, case, literal, text, union_all from sqlalchemy import cast as sa_cast from sqlmodel import JSON, Column, Field, Session, SQLModel, asc, desc, func, select @@ -49,6 +49,12 @@ class Trace(SQLModel, table=True): # type: ignore[call-arg] event_type: str event: dict[str, Any] = Field(sa_column=Column(JSON)) + __table_args__ = ( + Index("ix_trace_event_mas_id", text("(event->>'mas_id')")), + Index("ix_trace_created_at", "created_at"), + Index("ix_trace_event_type", "event_type"), + ) + class TraceList(BaseModel): """Paginated list of events."""