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."""