mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
Add Knowledge Explorer deployment templates
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
# Start with a tiny Docker context and opt in only files used by Dockerfile.
|
||||
*
|
||||
!Dockerfile
|
||||
!.dockerignore
|
||||
!pyproject.toml
|
||||
!README.md
|
||||
!LICENSE
|
||||
!MANIFEST.in
|
||||
!semantica/
|
||||
!semantica/**
|
||||
!integrations/
|
||||
!integrations/**
|
||||
!explorer/
|
||||
!explorer/**
|
||||
|
||||
# VCS, local config, and secrets.
|
||||
.git
|
||||
.git/**
|
||||
.github
|
||||
.github/**
|
||||
.claude
|
||||
.claude/**
|
||||
.codex
|
||||
.codex/**
|
||||
.agents
|
||||
.agents/**
|
||||
.env
|
||||
.env.*
|
||||
*.env
|
||||
|
||||
# Python build/test/cache artifacts.
|
||||
__pycache__
|
||||
**/__pycache__
|
||||
*.py[cod]
|
||||
.pytest_cache
|
||||
.pytest_cache/**
|
||||
.mypy_cache
|
||||
.mypy_cache/**
|
||||
.ruff_cache
|
||||
.ruff_cache/**
|
||||
.tox
|
||||
.tox/**
|
||||
.venv
|
||||
.venv/**
|
||||
venv
|
||||
venv/**
|
||||
coverage
|
||||
coverage/**
|
||||
htmlcov
|
||||
htmlcov/**
|
||||
*.egg-info
|
||||
*.egg-info/**
|
||||
build
|
||||
build/**
|
||||
dist
|
||||
dist/**
|
||||
|
||||
# Frontend dependency/build artifacts.
|
||||
node_modules
|
||||
node_modules/**
|
||||
explorer/node_modules
|
||||
explorer/node_modules/**
|
||||
explorer/dist
|
||||
explorer/dist/**
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Local outputs and large generated samples.
|
||||
logs
|
||||
logs/**
|
||||
*.log
|
||||
*.tmp
|
||||
*.bak
|
||||
*.backup
|
||||
tests
|
||||
tests/**
|
||||
explorer/tests
|
||||
explorer/tests/**
|
||||
docs
|
||||
docs/**
|
||||
site
|
||||
site/**
|
||||
.mkdocs_cache
|
||||
.mkdocs_cache/**
|
||||
cookbook
|
||||
cookbook/**
|
||||
examples
|
||||
examples/**
|
||||
demo_assets
|
||||
demo_assets/**
|
||||
demo_out
|
||||
demo_out/**
|
||||
demo_out_*
|
||||
demo_out_*/**
|
||||
outputs
|
||||
outputs/**
|
||||
pytest-cache-files-*
|
||||
pytest-cache-files-*/**
|
||||
test_data
|
||||
test_data/**
|
||||
sample_data
|
||||
sample_data/**
|
||||
+28
-16
@@ -1,29 +1,41 @@
|
||||
FROM node:26-alpine AS frontend-builder
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:22-alpine AS frontend-builder
|
||||
|
||||
WORKDIR /app/semantica-explorer
|
||||
WORKDIR /app
|
||||
COPY explorer/package*.json ./explorer/
|
||||
WORKDIR /app/explorer
|
||||
RUN npm ci
|
||||
|
||||
COPY explorer/ ./
|
||||
RUN mkdir -p /app/semantica && npm run build
|
||||
|
||||
COPY semantica-explorer/package.json semantica-explorer/package-lock.json* ./
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
|
||||
RUN npm install
|
||||
|
||||
|
||||
COPY semantica-explorer/ ./
|
||||
RUN npm run build
|
||||
|
||||
|
||||
FROM python:3.14-slim AS runtime
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
FALKORDB_HOST=falkordb \
|
||||
FALKORDB_PORT=6379 \
|
||||
ALLOWED_ORIGINS=http://localhost:8000,http://127.0.0.1:8000
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml ./
|
||||
COPY semantica/ ./semantica/
|
||||
RUN groupadd --system semantica \
|
||||
&& useradd --system --gid semantica --home-dir /app --shell /usr/sbin/nologin semantica
|
||||
|
||||
COPY pyproject.toml README.md LICENSE MANIFEST.in ./
|
||||
COPY semantica/ ./semantica/
|
||||
COPY integrations/ ./integrations/
|
||||
COPY --from=frontend-builder /app/semantica/static ./semantica/static
|
||||
|
||||
RUN pip install --no-cache-dir ".[explorer]"
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir ".[explorer]" \
|
||||
&& chown -R semantica:semantica /app
|
||||
|
||||
USER semantica
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["python", "-m", "uvicorn", "semantica.explorer.app:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD python -c "import json, urllib.request; data=json.load(urllib.request.urlopen('http://127.0.0.1:8000/api/health', timeout=3)); raise SystemExit(0 if data.get('status') == 'ok' else 1)"
|
||||
|
||||
CMD ["python", "-m", "uvicorn", "semantica.explorer.app:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Azure Container Apps
|
||||
|
||||
Deploy with Azure Developer CLI from this template directory:
|
||||
|
||||
```bash
|
||||
cd deploy/azure
|
||||
azd auth login
|
||||
azd init --environment semantica-ke
|
||||
azd env set AZURE_LOCATION eastus
|
||||
azd up
|
||||
```
|
||||
|
||||
The Bicep template provisions a Container Apps managed environment, HTTP ingress, scale-to-zero, max 10 replicas, and a `/api/health` liveness probe.
|
||||
@@ -0,0 +1,14 @@
|
||||
name: semantica-knowledge-explorer
|
||||
metadata:
|
||||
template: semantica-knowledge-explorer@0.1.0
|
||||
services:
|
||||
explorer:
|
||||
project: ../..
|
||||
host: containerapp
|
||||
language: docker
|
||||
docker:
|
||||
path: ./Dockerfile
|
||||
context: .
|
||||
infra:
|
||||
provider: bicep
|
||||
path: .
|
||||
@@ -0,0 +1,113 @@
|
||||
targetScope = 'resourceGroup'
|
||||
|
||||
param environmentName string = 'semantica-ke'
|
||||
param location string = resourceGroup().location
|
||||
param imageName string
|
||||
param containerPort int = 8000
|
||||
param allowedOrigins string = '*'
|
||||
param falkordbHost string = 'falkordb'
|
||||
param falkordbPort string = '6379'
|
||||
|
||||
var appName = '${environmentName}-explorer'
|
||||
var logAnalyticsName = '${environmentName}-logs'
|
||||
var managedEnvironmentName = '${environmentName}-env'
|
||||
|
||||
resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
|
||||
name: logAnalyticsName
|
||||
location: location
|
||||
properties: {
|
||||
sku: {
|
||||
name: 'PerGB2018'
|
||||
}
|
||||
retentionInDays: 30
|
||||
}
|
||||
}
|
||||
|
||||
resource managedEnvironment 'Microsoft.App/managedEnvironments@2024-03-01' = {
|
||||
name: managedEnvironmentName
|
||||
location: location
|
||||
properties: {
|
||||
appLogsConfiguration: {
|
||||
destination: 'log-analytics'
|
||||
logAnalyticsConfiguration: {
|
||||
customerId: logAnalytics.properties.customerId
|
||||
sharedKey: logAnalytics.listKeys().primarySharedKey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
|
||||
name: appName
|
||||
location: location
|
||||
tags: {
|
||||
'azd-service-name': 'explorer'
|
||||
}
|
||||
properties: {
|
||||
managedEnvironmentId: managedEnvironment.id
|
||||
configuration: {
|
||||
activeRevisionsMode: 'Single'
|
||||
ingress: {
|
||||
external: true
|
||||
targetPort: containerPort
|
||||
transport: 'auto'
|
||||
allowInsecure: false
|
||||
}
|
||||
}
|
||||
template: {
|
||||
containers: [
|
||||
{
|
||||
name: 'explorer'
|
||||
image: imageName
|
||||
env: [
|
||||
{
|
||||
name: 'ALLOWED_ORIGINS'
|
||||
value: allowedOrigins
|
||||
}
|
||||
{
|
||||
name: 'FALKORDB_HOST'
|
||||
value: falkordbHost
|
||||
}
|
||||
{
|
||||
name: 'FALKORDB_PORT'
|
||||
value: falkordbPort
|
||||
}
|
||||
]
|
||||
probes: [
|
||||
{
|
||||
type: 'Liveness'
|
||||
httpGet: {
|
||||
path: '/api/health'
|
||||
port: containerPort
|
||||
}
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
}
|
||||
]
|
||||
resources: {
|
||||
cpu: json('0.5')
|
||||
memory: '1Gi'
|
||||
}
|
||||
}
|
||||
]
|
||||
scale: {
|
||||
minReplicas: 0
|
||||
maxReplicas: 10
|
||||
rules: [
|
||||
{
|
||||
name: 'http-concurrency'
|
||||
http: {
|
||||
metadata: {
|
||||
concurrentRequests: '100'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output endpoint string = 'https://${containerApp.properties.configuration.ingress.fqdn}'
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"environmentName": {
|
||||
"value": "${AZURE_ENV_NAME}"
|
||||
},
|
||||
"location": {
|
||||
"value": "${AZURE_LOCATION}"
|
||||
},
|
||||
"imageName": {
|
||||
"value": "${SERVICE_EXPLORER_IMAGE_NAME}"
|
||||
},
|
||||
"allowedOrigins": {
|
||||
"value": "*"
|
||||
},
|
||||
"falkordbHost": {
|
||||
"value": "falkordb"
|
||||
},
|
||||
"falkordbPort": {
|
||||
"value": "6379"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# Fly.io
|
||||
|
||||
Deploy from a clean checkout using the root Dockerfile:
|
||||
|
||||
```bash
|
||||
flyctl auth login
|
||||
flyctl launch --copy-config --config deploy/fly/fly.toml --no-deploy
|
||||
flyctl secrets set FALKORDB_HOST=localhost FALKORDB_PORT=6379
|
||||
flyctl deploy --config deploy/fly/fly.toml
|
||||
```
|
||||
|
||||
Change `app` in `fly.toml` before launch if the default app name is already taken.
|
||||
@@ -0,0 +1,30 @@
|
||||
app = "semantica-knowledge-explorer"
|
||||
primary_region = "iad"
|
||||
kill_signal = "SIGTERM"
|
||||
kill_timeout = "30s"
|
||||
|
||||
[build]
|
||||
dockerfile = "Dockerfile"
|
||||
|
||||
[env]
|
||||
ALLOWED_ORIGINS = "https://semantica-knowledge-explorer.fly.dev"
|
||||
FALKORDB_HOST = "localhost"
|
||||
FALKORDB_PORT = "6379"
|
||||
|
||||
[http_service]
|
||||
internal_port = 8000
|
||||
force_https = true
|
||||
auto_stop_machines = "stop"
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
|
||||
[[http_service.checks]]
|
||||
grace_period = "20s"
|
||||
interval = "30s"
|
||||
method = "GET"
|
||||
timeout = "5s"
|
||||
path = "/api/health"
|
||||
|
||||
[[vm]]
|
||||
size = "shared-cpu-1x"
|
||||
memory = "512mb"
|
||||
@@ -0,0 +1,16 @@
|
||||
# GCP Cloud Run
|
||||
|
||||
Create the Secret Manager entries, then submit the Cloud Build pipeline:
|
||||
|
||||
```bash
|
||||
gcloud services enable cloudbuild.googleapis.com run.googleapis.com secretmanager.googleapis.com
|
||||
printf "falkordb-host.example.internal" | gcloud secrets create falkordb-host --data-file=-
|
||||
printf "6379" | gcloud secrets create falkordb-port --data-file=-
|
||||
gcloud builds submit --config deploy/gcp/cloudbuild.yaml --substitutions _REGION=us-central1,_SERVICE_NAME=knowledge-explorer
|
||||
```
|
||||
|
||||
For declarative deploys, replace `PROJECT_ID` in `cloudrun-service.yaml`, then run:
|
||||
|
||||
```bash
|
||||
gcloud run services replace deploy/gcp/cloudrun-service.yaml --region us-central1
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
substitutions:
|
||||
_REGION: us-central1
|
||||
_SERVICE_NAME: knowledge-explorer
|
||||
_IMAGE: gcr.io/$PROJECT_ID/knowledge-explorer
|
||||
|
||||
steps:
|
||||
- name: gcr.io/cloud-builders/docker
|
||||
args:
|
||||
- build
|
||||
- -t
|
||||
- ${_IMAGE}:$SHORT_SHA
|
||||
- -t
|
||||
- ${_IMAGE}:latest
|
||||
- .
|
||||
|
||||
- name: gcr.io/cloud-builders/docker
|
||||
args:
|
||||
- push
|
||||
- ${_IMAGE}:$SHORT_SHA
|
||||
|
||||
- name: gcr.io/cloud-builders/docker
|
||||
args:
|
||||
- push
|
||||
- ${_IMAGE}:latest
|
||||
|
||||
- name: gcr.io/google.com/cloudsdktool/cloud-sdk
|
||||
entrypoint: gcloud
|
||||
args:
|
||||
- run
|
||||
- deploy
|
||||
- ${_SERVICE_NAME}
|
||||
- --image
|
||||
- ${_IMAGE}:$SHORT_SHA
|
||||
- --region
|
||||
- ${_REGION}
|
||||
- --platform
|
||||
- managed
|
||||
- --allow-unauthenticated
|
||||
- --port
|
||||
- "8000"
|
||||
- --min-instances
|
||||
- "0"
|
||||
- --max-instances
|
||||
- "10"
|
||||
- --set-env-vars
|
||||
- ALLOWED_ORIGINS=*
|
||||
- --set-secrets
|
||||
- FALKORDB_HOST=falkordb-host:latest,FALKORDB_PORT=falkordb-port:latest
|
||||
|
||||
images:
|
||||
- ${_IMAGE}:$SHORT_SHA
|
||||
- ${_IMAGE}:latest
|
||||
@@ -0,0 +1,45 @@
|
||||
apiVersion: serving.knative.dev/v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: knowledge-explorer
|
||||
annotations:
|
||||
run.googleapis.com/ingress: all
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
autoscaling.knative.dev/minScale: "0"
|
||||
autoscaling.knative.dev/maxScale: "10"
|
||||
spec:
|
||||
containerConcurrency: 80
|
||||
timeoutSeconds: 300
|
||||
containers:
|
||||
- image: gcr.io/PROJECT_ID/knowledge-explorer:latest
|
||||
ports:
|
||||
- name: http1
|
||||
containerPort: 8000
|
||||
env:
|
||||
- name: ALLOWED_ORIGINS
|
||||
value: "*"
|
||||
- name: FALKORDB_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: falkordb-host
|
||||
key: latest
|
||||
- name: FALKORDB_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: falkordb-port
|
||||
key: latest
|
||||
resources:
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 512Mi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: 8000
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
@@ -0,0 +1,9 @@
|
||||
# Helm
|
||||
|
||||
Deploy the Knowledge Explorer chart:
|
||||
|
||||
```bash
|
||||
helm lint deploy/helm/knowledge-explorer
|
||||
helm upgrade --install knowledge-explorer deploy/helm/knowledge-explorer --namespace semantica --create-namespace
|
||||
helm upgrade --install knowledge-explorer deploy/helm/knowledge-explorer --namespace semantica --create-namespace -f deploy/helm/knowledge-explorer/values.prod.yaml
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: knowledge-explorer
|
||||
description: Semantica Knowledge Explorer deployment chart
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "0.5.0"
|
||||
@@ -0,0 +1,9 @@
|
||||
# Knowledge Explorer Helm Chart
|
||||
|
||||
```bash
|
||||
helm lint deploy/helm/knowledge-explorer
|
||||
helm upgrade --install knowledge-explorer deploy/helm/knowledge-explorer --namespace semantica --create-namespace
|
||||
helm upgrade --install knowledge-explorer deploy/helm/knowledge-explorer --namespace semantica --create-namespace -f deploy/helm/knowledge-explorer/values.prod.yaml
|
||||
```
|
||||
|
||||
Set `autoscaling.enabled=true` to render the HPA. Put sensitive values in Kubernetes Secrets and reference them outside this chart, or pass non-secret env values through `env`.
|
||||
@@ -0,0 +1,29 @@
|
||||
{{- define "knowledge-explorer.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "knowledge-explorer.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "knowledge-explorer.labels" -}}
|
||||
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
|
||||
app.kubernetes.io/name: {{ include "knowledge-explorer.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "knowledge-explorer.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "knowledge-explorer.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "knowledge-explorer.fullname" . }}
|
||||
labels:
|
||||
{{- include "knowledge-explorer.labels" . | nindent 4 }}
|
||||
data:
|
||||
{{- range $key, $value := .Values.env }}
|
||||
{{ $key }}: {{ $value | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,89 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "knowledge-explorer.fullname" . }}
|
||||
labels:
|
||||
{{- include "knowledge-explorer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "knowledge-explorer.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "knowledge-explorer.selectorLabels" . | nindent 8 }}
|
||||
{{- with .Values.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: explorer
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.targetPort }}
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "knowledge-explorer.fullname" . }}
|
||||
{{- if .Values.secretEnv }}
|
||||
env:
|
||||
{{- range $key, $value := .Values.secretEnv }}
|
||||
- name: {{ $key }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $value.secretName }}
|
||||
key: {{ $value.secretKey }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.livenessProbe.path }}
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.readinessProbe.path }}
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,22 @@
|
||||
{{- if .Values.autoscaling.enabled -}}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "knowledge-explorer.fullname" . }}
|
||||
labels:
|
||||
{{- include "knowledge-explorer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "knowledge-explorer.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,41 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "knowledge-explorer.fullname" . }}
|
||||
labels:
|
||||
{{- include "knowledge-explorer.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.ingress.className }}
|
||||
ingressClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "knowledge-explorer.fullname" $ }}
|
||||
port:
|
||||
name: http
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "knowledge-explorer.fullname" . }}
|
||||
labels:
|
||||
{{- include "knowledge-explorer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "knowledge-explorer.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,27 @@
|
||||
image:
|
||||
repository: ghcr.io/semantica-agi/semantica-knowledge-explorer
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
hosts:
|
||||
- host: knowledge-explorer.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: knowledge-explorer-tls
|
||||
hosts:
|
||||
- knowledge-explorer.example.com
|
||||
|
||||
env:
|
||||
ALLOWED_ORIGINS: https://knowledge-explorer.example.com
|
||||
FALKORDB_HOST: falkordb.semantic-data.svc.cluster.local
|
||||
FALKORDB_PORT: "6379"
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
targetCPUUtilizationPercentage: 75
|
||||
@@ -0,0 +1,83 @@
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
repository: semantica-knowledge-explorer
|
||||
pullPolicy: IfNotPresent
|
||||
tag: latest
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
targetPort: 8000
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
hosts:
|
||||
- host: knowledge-explorer.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: knowledge-explorer-tls
|
||||
hosts:
|
||||
- knowledge-explorer.example.com
|
||||
|
||||
env:
|
||||
ALLOWED_ORIGINS: https://knowledge-explorer.example.com
|
||||
FALKORDB_HOST: falkordb
|
||||
FALKORDB_PORT: "6379"
|
||||
|
||||
secretEnv: {}
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
livenessProbe:
|
||||
path: /api/health
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
readinessProbe:
|
||||
path: /api/health
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
targetCPUUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
@@ -0,0 +1,12 @@
|
||||
# Kubernetes
|
||||
|
||||
Apply the raw manifests with Kustomize:
|
||||
|
||||
```bash
|
||||
cp deploy/kubernetes/secret.yaml.example deploy/kubernetes/secret.yaml
|
||||
kubectl apply -f deploy/kubernetes/secret.yaml
|
||||
kubectl apply -k deploy/kubernetes
|
||||
kubectl -n semantica rollout status deployment/knowledge-explorer
|
||||
```
|
||||
|
||||
Update the image name and ingress host before deploying to production. `secret.yaml` is intentionally ignored from the kustomization; keep only `secret.yaml.example` in git.
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: knowledge-explorer-config
|
||||
namespace: semantica
|
||||
data:
|
||||
ALLOWED_ORIGINS: "https://knowledge-explorer.example.com"
|
||||
FALKORDB_HOST: "falkordb"
|
||||
FALKORDB_PORT: "6379"
|
||||
@@ -0,0 +1,69 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: knowledge-explorer
|
||||
namespace: semantica
|
||||
labels:
|
||||
app.kubernetes.io/name: knowledge-explorer
|
||||
app.kubernetes.io/part-of: semantica
|
||||
spec:
|
||||
replicas: 2
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: knowledge-explorer
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: knowledge-explorer
|
||||
app.kubernetes.io/part-of: semantica
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: explorer
|
||||
image: semantica-knowledge-explorer:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: knowledge-explorer-config
|
||||
- secretRef:
|
||||
name: knowledge-explorer-secrets
|
||||
optional: true
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: http
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,24 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: knowledge-explorer
|
||||
namespace: semantica
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- knowledge-explorer.example.com
|
||||
secretName: knowledge-explorer-tls
|
||||
rules:
|
||||
- host: knowledge-explorer.example.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: knowledge-explorer
|
||||
port:
|
||||
name: http
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- configmap.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: semantica
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: knowledge-explorer-secrets
|
||||
namespace: semantica
|
||||
type: Opaque
|
||||
stringData:
|
||||
FALKORDB_PASSWORD: "replace-me-if-your-falkordb-requires-auth"
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: knowledge-explorer
|
||||
namespace: semantica
|
||||
labels:
|
||||
app.kubernetes.io/name: knowledge-explorer
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: knowledge-explorer
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
@@ -0,0 +1,15 @@
|
||||
# Railway
|
||||
|
||||
Deploys the Knowledge Explorer from the root `Dockerfile` and checks `/api/health`.
|
||||
|
||||
```bash
|
||||
railway login
|
||||
railway init
|
||||
railway add --database redis
|
||||
railway variable --set "FALKORDB_HOST=${{Redis.REDISHOST}}"
|
||||
railway variable --set "FALKORDB_PORT=${{Redis.REDISPORT}}"
|
||||
railway variable --set "ALLOWED_ORIGINS=https://${{RAILWAY_PUBLIC_DOMAIN}}"
|
||||
railway up
|
||||
```
|
||||
|
||||
The Redis plugin variables are wired to the requested FalkorDB env names for deployment compatibility. The Explorer currently reads these settings but does not persist graph state to FalkorDB.
|
||||
@@ -0,0 +1,9 @@
|
||||
[build]
|
||||
builder = "DOCKERFILE"
|
||||
dockerfilePath = "Dockerfile"
|
||||
|
||||
[deploy]
|
||||
healthcheckPath = "/api/health"
|
||||
healthcheckTimeout = 300
|
||||
restartPolicyType = "ON_FAILURE"
|
||||
restartPolicyMaxRetries = 10
|
||||
@@ -0,0 +1,11 @@
|
||||
# Render
|
||||
|
||||
This Blueprint provisions a Docker web service plus a Render Key Value instance and wires the datastore host/port into the Explorer env vars.
|
||||
|
||||
```bash
|
||||
render login
|
||||
render blueprints validate deploy/render/render.yaml
|
||||
render blueprint apply deploy/render/render.yaml
|
||||
```
|
||||
|
||||
After creation, update `ALLOWED_ORIGINS` in the Render dashboard if you attach a custom domain.
|
||||
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
- type: web
|
||||
name: semantica-knowledge-explorer
|
||||
runtime: docker
|
||||
plan: starter
|
||||
region: oregon
|
||||
dockerfilePath: ./Dockerfile
|
||||
dockerContext: .
|
||||
healthCheckPath: /api/health
|
||||
envVars:
|
||||
- key: ALLOWED_ORIGINS
|
||||
value: https://semantica-knowledge-explorer.onrender.com
|
||||
- key: FALKORDB_HOST
|
||||
fromService:
|
||||
type: keyvalue
|
||||
name: semantica-explorer-redis
|
||||
property: host
|
||||
- key: FALKORDB_PORT
|
||||
fromService:
|
||||
type: keyvalue
|
||||
name: semantica-explorer-redis
|
||||
property: port
|
||||
|
||||
- type: keyvalue
|
||||
name: semantica-explorer-redis
|
||||
plan: starter
|
||||
ipAllowList: []
|
||||
maxmemoryPolicy: noeviction
|
||||
persistenceMode: snapshot
|
||||
@@ -0,0 +1,42 @@
|
||||
services:
|
||||
explorer:
|
||||
command:
|
||||
- python
|
||||
- -m
|
||||
- uvicorn
|
||||
- semantica.explorer.app:app
|
||||
- --host
|
||||
- 0.0.0.0
|
||||
- --port
|
||||
- "8000"
|
||||
- --reload
|
||||
- --reload-dir
|
||||
- /app/semantica
|
||||
environment:
|
||||
ALLOWED_ORIGINS: http://localhost:5173,http://127.0.0.1:5173,http://localhost:8000,http://127.0.0.1:8000
|
||||
FALKORDB_HOST: falkordb
|
||||
FALKORDB_PORT: "6379"
|
||||
volumes:
|
||||
- ./semantica:/app/semantica
|
||||
- ./pyproject.toml:/app/pyproject.toml:ro
|
||||
|
||||
frontend:
|
||||
image: node:22-alpine
|
||||
working_dir: /app/explorer
|
||||
command: sh -c "npm ci && npm run dev -- --host 0.0.0.0"
|
||||
environment:
|
||||
VITE_EXPLORER_API_TARGET: http://explorer:8000
|
||||
VITE_EXPLORER_WS_TARGET: ws://explorer:8000
|
||||
ports:
|
||||
- "5173:5173"
|
||||
volumes:
|
||||
- ./explorer:/app/explorer
|
||||
- explorer_node_modules:/app/explorer/node_modules
|
||||
depends_on:
|
||||
explorer:
|
||||
condition: service_started
|
||||
networks:
|
||||
- semantica
|
||||
|
||||
volumes:
|
||||
explorer_node_modules:
|
||||
+25
-1
@@ -1,11 +1,35 @@
|
||||
services:
|
||||
explorer:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: semantica-knowledge-explorer:latest
|
||||
environment:
|
||||
FALKORDB_HOST: falkordb
|
||||
FALKORDB_PORT: "6379"
|
||||
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:8000,http://127.0.0.1:8000}
|
||||
depends_on:
|
||||
falkordb:
|
||||
condition: service_started
|
||||
ports:
|
||||
- "8000:8000"
|
||||
networks:
|
||||
- semantica
|
||||
restart: unless-stopped
|
||||
|
||||
falkordb:
|
||||
image: falkordb/falkordb:latest
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- falkordb_data:/data
|
||||
restart: always
|
||||
networks:
|
||||
- semantica
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
semantica:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
falkordb_data:
|
||||
|
||||
@@ -2,6 +2,9 @@ import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
|
||||
const apiTarget = process.env.VITE_EXPLORER_API_TARGET ?? 'http://127.0.0.1:8000'
|
||||
const wsTarget = process.env.VITE_EXPLORER_WS_TARGET ?? apiTarget.replace(/^http/, 'ws')
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -57,11 +60,11 @@ export default defineConfig({
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8000',
|
||||
target: apiTarget,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/ws': {
|
||||
target: 'ws://127.0.0.1:8000',
|
||||
target: wsTarget,
|
||||
ws: true,
|
||||
},
|
||||
},
|
||||
|
||||
+39
-10
@@ -1,4 +1,4 @@
|
||||
"""
|
||||
"""
|
||||
Semantica Explorer FastAPI application factory.
|
||||
"""
|
||||
|
||||
@@ -14,10 +14,36 @@ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from .. import __version__
|
||||
from ..context.context_graph import ContextGraph
|
||||
from .session import GraphSession
|
||||
from .ws import ConnectionManager
|
||||
|
||||
|
||||
def _read_int_env(name: str, default: int) -> int:
|
||||
raw_value = os.environ.get(name)
|
||||
if raw_value is None or raw_value.strip() == "":
|
||||
return default
|
||||
try:
|
||||
return int(raw_value)
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
|
||||
def _read_explorer_settings() -> dict:
|
||||
raw_origins = (
|
||||
os.environ.get("ALLOWED_ORIGINS")
|
||||
or os.environ.get("EXPLORER_CORS_ORIGINS")
|
||||
or "http://localhost:5173,http://127.0.0.1:5173"
|
||||
)
|
||||
return {
|
||||
"allowed_origins": [
|
||||
origin.strip() for origin in raw_origins.split(",") if origin.strip()
|
||||
],
|
||||
"falkordb_host": os.environ.get("FALKORDB_HOST", "localhost"),
|
||||
"falkordb_port": _read_int_env("FALKORDB_PORT", 6379),
|
||||
}
|
||||
|
||||
|
||||
def _install_mutation_bridge(app: FastAPI, session: GraphSession) -> None:
|
||||
previous_callback = getattr(session.graph, "mutation_callback", None)
|
||||
|
||||
@@ -43,13 +69,15 @@ def _install_mutation_bridge(app: FastAPI, session: GraphSession) -> None:
|
||||
|
||||
|
||||
def create_app(session: Optional[GraphSession] = None) -> FastAPI:
|
||||
active_session = session or GraphSession(ContextGraph(advanced_analytics=False))
|
||||
settings = _read_explorer_settings()
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
app.state.event_loop = asyncio.get_running_loop()
|
||||
app.state.ws_manager = ConnectionManager()
|
||||
if session is not None:
|
||||
app.state.session = session
|
||||
_install_mutation_bridge(app, session)
|
||||
app.state.session = active_session
|
||||
_install_mutation_bridge(app, active_session)
|
||||
yield
|
||||
|
||||
app = FastAPI(
|
||||
@@ -59,10 +87,11 @@ def create_app(session: Optional[GraphSession] = None) -> FastAPI:
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
_raw_origins = os.environ.get(
|
||||
"EXPLORER_CORS_ORIGINS", "http://localhost:5173,http://127.0.0.1:5173"
|
||||
)
|
||||
_cors_origins = [o.strip() for o in _raw_origins.split(",") if o.strip()]
|
||||
app.state.explorer_settings = settings
|
||||
app.state.falkordb_host = settings["falkordb_host"]
|
||||
app.state.falkordb_port = settings["falkordb_port"]
|
||||
app.state.allowed_origins = settings["allowed_origins"]
|
||||
|
||||
# allow_credentials lets browsers send cookies/auth headers cross-origin.
|
||||
# The Explorer has no authentication, so credentials serve no purpose and
|
||||
# enabling them when origins are broadened creates cross-site request risk.
|
||||
@@ -71,7 +100,7 @@ def create_app(session: Optional[GraphSession] = None) -> FastAPI:
|
||||
_allow_credentials = os.environ.get("EXPLORER_CORS_CREDENTIALS", "false").lower() == "true"
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=_cors_origins,
|
||||
allow_origins=settings["allowed_origins"],
|
||||
allow_credentials=_allow_credentials,
|
||||
allow_methods=["GET", "POST", "DELETE", "OPTIONS"],
|
||||
allow_headers=["Content-Type", "Authorization"],
|
||||
@@ -169,7 +198,7 @@ def create_app(session: Optional[GraphSession] = None) -> FastAPI:
|
||||
|
||||
@app.get("/api/health")
|
||||
async def health():
|
||||
return {"status": "healthy"}
|
||||
return {"status": "ok"}
|
||||
|
||||
@app.get("/api/info")
|
||||
async def info():
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Integration tests for the explorer API."""
|
||||
"""Integration tests for the explorer API."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
@@ -151,7 +151,7 @@ class TestHealthInfo:
|
||||
def test_health(self, client):
|
||||
response = client.get("/api/health")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "healthy"
|
||||
assert response.json() == {"status": "ok"}
|
||||
|
||||
def test_info(self, client):
|
||||
response = client.get("/api/info")
|
||||
@@ -161,6 +161,40 @@ class TestHealthInfo:
|
||||
assert payload["status"] == "active"
|
||||
assert payload["version"]
|
||||
|
||||
def test_env_settings_are_read_from_supported_names(self, monkeypatch):
|
||||
monkeypatch.setenv(
|
||||
"ALLOWED_ORIGINS",
|
||||
"https://app.example.com, https://team.example.com",
|
||||
)
|
||||
monkeypatch.setenv("FALKORDB_HOST", "falkordb.internal")
|
||||
monkeypatch.setenv("FALKORDB_PORT", "6380")
|
||||
|
||||
app = create_app()
|
||||
|
||||
assert app.state.allowed_origins == [
|
||||
"https://app.example.com",
|
||||
"https://team.example.com",
|
||||
]
|
||||
assert app.state.falkordb_host == "falkordb.internal"
|
||||
assert app.state.falkordb_port == 6380
|
||||
|
||||
def test_env_settings_fall_back_to_legacy_cors_name(self, monkeypatch):
|
||||
monkeypatch.delenv("ALLOWED_ORIGINS", raising=False)
|
||||
monkeypatch.setenv("EXPLORER_CORS_ORIGINS", "https://legacy.example.com")
|
||||
|
||||
app = create_app()
|
||||
|
||||
assert app.state.allowed_origins == ["https://legacy.example.com"]
|
||||
|
||||
def test_default_app_initializes_empty_graph_session(self):
|
||||
with TestClient(create_app()) as test_client:
|
||||
response = test_client.get("/api/graph/nodes")
|
||||
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert payload["nodes"] == []
|
||||
assert payload["total"] == 0
|
||||
|
||||
|
||||
class TestGraphNodes:
|
||||
def test_list_nodes(self, client):
|
||||
|
||||
Reference in New Issue
Block a user