Prepare EE to merge with MIT

This commit is contained in:
Weves 2024-02-15 14:45:00 -08:00 committed by Chris Weaver
parent f0b2b57d81
commit 1ee8ee9e8b
46 changed files with 352 additions and 322 deletions

View File

@ -5,6 +5,9 @@ on:
tags:
- '*'
env:
REGISTRY_IMAGE: danswer/danswer-backend
jobs:
build-and-push:
# for EE, run on special image-builders runners
@ -33,8 +36,8 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
tags: |
danswer/danswer-ee-backend:${{ github.ref_name }}
danswer/danswer-ee-backend:latest
${{ env.REGISTRY_IMAGE }}:${{ github.ref_name }}
${{ env.REGISTRY_IMAGE }}:latest
build-args: |
DANSWER_VERSION=${{ github.ref_name }}
@ -42,6 +45,6 @@ jobs:
uses: aquasecurity/trivy-action@master
with:
# To run locally: trivy image --severity HIGH,CRITICAL danswer/danswer-backend
image-ref: docker.io/danswer/danswer-backend:${{ github.ref_name }}
image-ref: docker.io/${{ env.REGISTRY_IMAGE }}:${{ github.ref_name }}
severity: 'CRITICAL,HIGH'
trivyignores: ./backend/.trivyignore

View File

@ -6,7 +6,7 @@ on:
- '*'
env:
REGISTRY_IMAGE: danswer/danswer-ee-web-server
REGISTRY_IMAGE: danswer/danswer-web-server
jobs:
build:
@ -34,8 +34,8 @@ jobs:
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: |
type=raw,value=danswer/danswer-ee-web-server:${{ github.ref_name }}
type=raw,value=danswer/danswer-ee-web-server:latest
type=raw,value=${{ env.REGISTRY_IMAGE }}:${{ github.ref_name }}
type=raw,value=${{ env.REGISTRY_IMAGE }}:latest
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@ -56,7 +56,6 @@ jobs:
push: true
build-args: |
DANSWER_VERSION=${{ github.ref_name }}
NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES=true
# needed due to weird interactions with the builds for different platforms
no-cache: true
labels: ${{ steps.meta.outputs.labels }}

View File

@ -25,8 +25,8 @@ jobs:
- name: Pull, Tag and Push Web Server Image
run: |
docker buildx imagetools create -t danswer/danswer-ee-web-server:latest danswer/danswer-ee-web-server:${{ github.event.inputs.version }}
docker buildx imagetools create -t danswer/danswer-web-server:latest danswer/danswer-web-server:${{ github.event.inputs.version }}
- name: Pull, Tag and Push API Server Image
run: |
docker buildx imagetools create -t danswer/danswer-ee-backend:latest danswer/danswer-ee-backend:${{ github.event.inputs.version }}
docker buildx imagetools create -t danswer/danswer-backend:latest danswer/danswer-backend:${{ github.event.inputs.version }}

View File

@ -23,7 +23,7 @@ REQUIRE_EMAIL_VERIFICATION=False
# Toggles on/off the EE Features
NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES=False
ENABLE_PAID_EE_FEATURES=False
# Set these so if you wipe the DB, you don't end up having to go through the UI every time

View File

@ -152,13 +152,13 @@ python ./scripts/dev_run_background_jobs.py
To run the backend API server, navigate back to `danswer/backend` and run:
```bash
AUTH_TYPE=disabled uvicorn ee.danswer.main:app --reload --port 8080
AUTH_TYPE=disabled uvicorn danswer.main:app --reload --port 8080
```
_For Windows (for compatibility with both PowerShell and Command Prompt):_
```bash
powershell -Command "
$env:AUTH_TYPE='disabled'
uvicorn ee.danswer.main:app --reload --port 8080
uvicorn danswer.main:app --reload --port 8080
"
```

View File

@ -1,8 +1,8 @@
Copyright (c) 2023 DanswerAI, Inc.
Copyright (c) 2023-present DanswerAI, Inc.
Portions of this software are licensed as follows:
* All content that resides under "ee" directories of this repository, if that directory exists, is licensed under the license defined in "backend/ee/LICENSE".
* All content that resides under "ee" directories of this repository, if that directory exists, is licensed under the license defined in "backend/ee/LICENSE". Specifically all content under "backend/ee" and "web/src/app/ee" is licensed under the license defined in "backend/ee/LICENSE".
* All third party components incorporated into the Danswer Software are licensed under the original license provided by the owner of the applicable component.
* Content outside of the above mentioned directories or restrictions above is available under the "MIT Expat" license as defined below.

View File

@ -105,5 +105,25 @@ Efficiently pulls the latest changes from:
* Websites
* And more ...
## 📚 Editions
There are two editions of Danswer:
* Danswer Community Edition (CE) is available freely under the MIT Expat license. This version has ALL the core features discussed above. This is the version of Danswer you will get if you follow the Deployment guide above.
* Danswer Enterprise Edition (EE) includes extra features that are primarily useful for larger organizations. Specifically, this includes:
* Single Sign-On (SSO), with support for both SAML and OIDC
* Role-based access control
* Document permission inheritance from connected sources
* Admin usage analytics / global query history
* Whitelabeling
* API key authentication
* Encryption of secrets
* Any many more! Checkout [our website](https://www.danswer.ai/) for the latest.
To try the Danswer Enterprise Edition:
1. Checkout our [Cloud product](https://app.danswer.ai/signup).
2. For self-hosting, contact us at [founders@danswer.ai](mailto:founders@danswer.ai) or book a call with us on our [Cal](https://cal.com/team/danswer/founders).
## 💡 Contributing
Looking to contribute? Please check out the [Contribution Guide](CONTRIBUTING.md) for more details.

View File

@ -1,11 +1,11 @@
FROM python:3.11.7-slim-bookworm
LABEL com.danswer.maintainer="founders@danswer.ai"
LABEL com.danswer.description="This image is the Enterprise Edition (Paid Edition) backend of \
Danswer. If you do not have a contract or agreement with DanswerAI, you are not permitted to use \
this container outside of personal development or testing purposes. Please reach out to \
founders@danswer.ai for more information. You can access the MIT version of Danswer at \
https://github.com/danswer-ai/danswer"
LABEL com.danswer.description="This image is the web/frontend container of Danswer which \
contains code for both the Community and Enterprise editions of Danswer. If you do not \
have a contract or agreement with DanswerAI, you are not permitted to use the Enterprise \
Edition features outside of personal development or testing purposes. Please reach out to \
founders@danswer.ai for more information. Please visit https://github.com/danswer-ai/danswer"
# Default DANSWER_VERSION, typically overriden during builds by GitHub Actions.
ARG DANSWER_VERSION=0.3-dev
@ -18,20 +18,33 @@ RUN echo "DANSWER_VERSION: ${DANSWER_VERSION}"
# curl included just for users' convenience
# zip for Vespa step futher down
# ca-certificates for HTTPS
RUN apt-get update && \
apt-get install -y cmake curl zip ca-certificates libgnutls30=3.7.9-2+deb12u2 \
libblkid1=2.38.1-5+deb12u1 libmount1=2.38.1-5+deb12u1 libsmartcols1=2.38.1-5+deb12u1 \
libuuid1=2.38.1-5+deb12u1 && \
apt-get install -y \
cmake \
curl \
zip \
ca-certificates \
libgnutls30=3.7.9-2+deb12u2 \
libblkid1=2.38.1-5+deb12u1 \
libmount1=2.38.1-5+deb12u1 \
libsmartcols1=2.38.1-5+deb12u1 \
libuuid1=2.38.1-5+deb12u1 \
libxmlsec1-dev \
pkg-config \
gcc && \
rm -rf /var/lib/apt/lists/* && \
apt-get clean
# Install Python dependencies
# Remove py which is pulled in by retry, py is not needed and is a CVE
COPY ./requirements/default.txt /tmp/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /tmp/requirements.txt && \
COPY ./requirements/ee.txt /tmp/ee-requirements.txt
RUN pip install --no-cache-dir --upgrade \
-r /tmp/requirements.txt \
-r /tmp/ee-requirements.txt && \
pip uninstall -y py && \
playwright install chromium && playwright install-deps chromium && \
playwright install chromium && \
playwright install-deps chromium && \
ln -s /usr/local/bin/supervisord /usr/bin/supervisord
# Cleanup for CVEs and size reduction
@ -39,21 +52,20 @@ RUN pip install --no-cache-dir --upgrade -r /tmp/requirements.txt && \
# xserver-common and xvfb included by playwright installation but not needed after
# perl-base is part of the base Python Debian image but not needed for Danswer functionality
# perl-base could only be removed with --allow-remove-essential
RUN apt-get remove -y --allow-remove-essential perl-base xserver-common xvfb cmake \
libldap-2.5-0 libldap-2.5-0 && \
RUN apt-get update && \
apt-get remove -y --allow-remove-essential \
perl-base \
xserver-common \
xvfb \
cmake \
libldap-2.5-0 \
libxmlsec1-dev \
pkg-config \
gcc && \
apt-get install -y libxmlsec1-openssl && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* && \
rm /usr/local/lib/python3.11/site-packages/tornado/test/test.key
# Enterprise Install
RUN apt-get update && apt-get install -y libxmlsec1-dev pkg-config gcc
COPY ./requirements/ee.txt /tmp/ee-requirements.txt
RUN pip install --no-cache-dir --upgrade -r /tmp/ee-requirements.txt
# Enterprise lib removal for security reasons
RUN apt-get remove -y libxmlsec1-dev pkg-config gcc && \
apt-get update && apt-get install -y libxmlsec1-openssl && \
apt-get autoremove -y
rm -f /usr/local/lib/python3.11/site-packages/tornado/test/test.key
# Pre-downloading models for setups with limited egress
RUN python -c "from transformers import AutoTokenizer; AutoTokenizer.from_pretrained('intfloat/e5-base-v2')"
@ -69,7 +81,7 @@ WORKDIR /app
# Enterprise Version Files
COPY ./ee /app/ee
COPY ee.supervisord.conf /etc/supervisor/conf.d/ee.supervisord.conf
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Set up application files
COPY ./danswer /app/danswer

View File

@ -0,0 +1,9 @@
"""Entry point for running celery worker / celery beat."""
from danswer.utils.variable_functionality import fetch_versioned_implementation
from danswer.utils.variable_functionality import set_is_ee_based_on_env_variable
set_is_ee_based_on_env_variable()
celery_app = fetch_versioned_implementation(
"danswer.background.celery.celery_app", "celery_app"
)

View File

@ -36,6 +36,7 @@ from danswer.db.swap_index import check_index_swap
from danswer.search.search_nlp_models import warm_up_encoders
from danswer.utils.logger import setup_logger
from danswer.utils.variable_functionality import global_version
from danswer.utils.variable_functionality import set_is_ee_based_on_env_variable
from shared_configs.configs import INDEXING_MODEL_SERVER_HOST
from shared_configs.configs import LOG_LEVEL
from shared_configs.configs import MODEL_SERVER_PORT
@ -407,6 +408,8 @@ def update_loop(delay: int = 10, num_workers: int = NUM_INDEXING_WORKERS) -> Non
def update__main() -> None:
set_is_ee_based_on_env_variable()
logger.info("Starting Indexing Loop")
update_loop()

View File

@ -279,3 +279,15 @@ TOKEN_BUDGET_GLOBALLY_ENABLED = (
CUSTOM_ANSWER_VALIDITY_CONDITIONS = json.loads(
os.environ.get("CUSTOM_ANSWER_VALIDITY_CONDITIONS", "[]")
)
#####
# Enterprise Edition Configs
#####
# NOTE: this should only be enabled if you have purchased an enterprise license.
# if you're interested in an enterprise license, please reach out to us at
# founders@danswer.ai OR message Chris Weaver or Yuhong Sun in the Danswer
# Slack community (https://join.slack.com/t/danswer/shared_invite/zt-1w76msxmd-HJHLe3KNFIAIzk_0dSOKaQ)
ENTERPRISE_EDITION_ENABLED = (
os.environ.get("ENABLE_PAID_EE_FEATURES", "").lower() == "true"
)

View File

@ -90,6 +90,8 @@ from danswer.utils.logger import setup_logger
from danswer.utils.telemetry import optional_telemetry
from danswer.utils.telemetry import RecordType
from danswer.utils.variable_functionality import fetch_versioned_implementation
from danswer.utils.variable_functionality import global_version
from danswer.utils.variable_functionality import set_is_ee_based_on_env_variable
from shared_configs.configs import ENABLE_RERANKING_REAL_TIME_FLOW
from shared_configs.configs import MODEL_SERVER_HOST
from shared_configs.configs import MODEL_SERVER_PORT
@ -369,11 +371,18 @@ def get_application() -> FastAPI:
return application
app = get_application()
# NOTE: needs to be outside of the `if __name__ == "__main__"` block so that the
# app is exportable
set_is_ee_based_on_env_variable()
app = fetch_versioned_implementation(module="danswer.main", attribute="get_application")
if __name__ == "__main__":
logger.info(
f"Starting Danswer Backend version {__version__} on http://{APP_HOST}:{str(APP_PORT)}/"
)
if global_version.get_is_ee_version():
logger.info("Running Enterprise Edition")
uvicorn.run(app, host=APP_HOST, port=APP_PORT)

View File

@ -145,7 +145,9 @@ def create_deletion_attempt_for_connector_id(
_: User = Depends(current_admin_user),
db_session: Session = Depends(get_session),
) -> None:
from danswer.background.celery.celery import cleanup_connector_credential_pair_task
from danswer.background.celery.celery_app import (
cleanup_connector_credential_pair_task,
)
connector_id = connector_credential_pair_identifier.connector_id
credential_id = connector_credential_pair_identifier.credential_id

View File

@ -3,9 +3,9 @@ import importlib
from typing import Any
from typing import TypeVar
from danswer.configs.app_configs import ENTERPRISE_EDITION_ENABLED
from danswer.utils.logger import setup_logger
logger = setup_logger()
@ -23,6 +23,12 @@ class DanswerVersion:
global_version = DanswerVersion()
def set_is_ee_based_on_env_variable() -> None:
if ENTERPRISE_EDITION_ENABLED and not global_version.get_is_ee_version():
logger.info("Enterprise Edition enabled")
global_version.set_ee()
@functools.lru_cache(maxsize=128)
def fetch_versioned_implementation(module: str, attribute: str) -> Any:
logger.debug("Fetching versioned implementation for %s.%s", module, attribute)

View File

@ -1,57 +0,0 @@
[supervisord]
nodaemon=true
logfile=/dev/stdout
logfile_maxbytes=0
[program:indexing]
environment=CURRENT_PROCESS_IS_AN_INDEXING_JOB=true
command=python ee/danswer/background/update.py
stdout_logfile=/var/log/update.log
stdout_logfile_maxbytes=52428800
redirect_stderr=true
autorestart=true
# Automatic sync-ing of access to documents from sources
[program:permission_syncing]
command=python ee/danswer/background/permission_sync.py
stdout_logfile=/var/log/permission_sync.log
stdout_logfile_maxbytes=52428800
redirect_stderr=true
autorestart=true
# Background jobs that must be run async due to long time to completion
[program:celery_worker]
command=celery -A ee.danswer.background.celery worker --loglevel=INFO --logfile=/var/log/celery_worker.log
stdout_logfile=/var/log/celery_worker_supervisor.log
stdout_logfile_maxbytes=52428800
redirect_stderr=true
autorestart=true
# Job scheduler for periodic tasks
[program:celery_beat]
command=celery -A ee.danswer.background.celery beat --loglevel=INFO --logfile=/var/log/celery_beat.log
stdout_logfile=/var/log/celery_beat_supervisor.log
stdout_logfile_maxbytes=52428800
redirect_stderr=true
autorestart=true
# Listens for Slack messages and responds with answers
# for all channels that the DanswerBot has been added to.
# If not setup, this will just fail 5 times and then stop.
# More details on setup here: https://docs.danswer.dev/slack_bot_setup
[program:slack_bot_listener]
command=python danswer/danswerbot/slack/listener.py
stdout_logfile=/var/log/slack_bot_listener.log
stdout_logfile_maxbytes=52428800
redirect_stderr=true
autorestart=true
startretries=5
startsecs=60
# Pushes all logs from the above programs to stdout
[program:log-redirect-handler]
command=tail -qF /var/log/update.log /var/log/celery_worker.log /var/log/celery_worker_supervisor.log /var/log/celery_beat.log /var/log/celery_beat_supervisor.log /var/log/slack_bot_listener.log
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
redirect_stderr=true
autorestart=true

View File

@ -1,5 +1,5 @@
The DanswerAI Enterprise license (the “Enterprise License”)
Copyright (c) 2023 DanswerAI, Inc.
Copyright (c) 2023-present DanswerAI, Inc.
With regard to the Danswer Software:

View File

@ -2,7 +2,7 @@ from datetime import timedelta
from sqlalchemy.orm import Session
from danswer.background.celery.celery import celery_app
from danswer.background.celery.celery_app import celery_app
from danswer.configs.app_configs import JOB_TIMEOUT
from danswer.db.engine import get_sqlalchemy_engine
from danswer.db.tasks import check_live_task_not_timed_out

View File

@ -1,10 +0,0 @@
from danswer.background.update import update__main
from danswer.utils.variable_functionality import global_version
if __name__ == "__main__":
# mark this as the EE-enabled indexing job
global_version.set_ee()
# run the usual flow, any future branching will be done with a
# call to `global_version.get_is_ee_version()`
update__main()

View File

@ -1,18 +1,15 @@
import uvicorn
from fastapi import FastAPI
from httpx_oauth.clients.openid import OpenID
from danswer.auth.users import auth_backend
from danswer.auth.users import fastapi_users
from danswer.configs.app_configs import APP_HOST
from danswer.configs.app_configs import APP_PORT
from danswer.configs.app_configs import AUTH_TYPE
from danswer.configs.app_configs import OAUTH_CLIENT_ID
from danswer.configs.app_configs import OAUTH_CLIENT_SECRET
from danswer.configs.app_configs import USER_AUTH_SECRET
from danswer.configs.app_configs import WEB_DOMAIN
from danswer.configs.constants import AuthType
from danswer.main import get_application
from danswer.main import get_application as get_application_base
from danswer.main import include_router_with_global_prefix_prepended
from danswer.utils.logger import setup_logger
from danswer.utils.variable_functionality import global_version
@ -45,14 +42,14 @@ from ee.danswer.utils.encryption import test_encryption
logger = setup_logger()
def get_ee_application() -> FastAPI:
def get_application() -> FastAPI:
# Anything that happens at import time is not guaranteed to be running ee-version
# Anything after the server startup will be running ee version
global_version.set_ee()
test_encryption()
application = get_application()
application = get_application_base()
if AUTH_TYPE == AuthType.OIDC:
include_router_with_global_prefix_prepended(
@ -108,13 +105,3 @@ def get_ee_application() -> FastAPI:
seed_db()
return application
app = get_ee_application()
if __name__ == "__main__":
logger.info(
f"Running Enterprise Danswer API Service on http://{APP_HOST}:{str(APP_PORT)}/"
)
uvicorn.run(app, host=APP_HOST, port=APP_PORT)

View File

@ -25,7 +25,7 @@ autorestart=true
# relatively compute-light (e.g. they tend to just make a bunch of requests to
# Vespa / Postgres)
[program:celery_worker]
command=celery -A danswer.background.celery worker --pool=threads --autoscale=3,10 --loglevel=INFO --logfile=/var/log/celery_worker.log
command=celery -A danswer.background.celery.celery_run:celery_app worker --pool=threads --autoscale=3,10 --loglevel=INFO --logfile=/var/log/celery_worker.log
stdout_logfile=/var/log/celery_worker_supervisor.log
stdout_logfile_maxbytes=52428800
redirect_stderr=true
@ -33,7 +33,7 @@ autorestart=true
# Job scheduler for periodic tasks
[program:celery_beat]
command=celery -A danswer.background.celery beat --loglevel=INFO --logfile=/var/log/celery_beat.log
command=celery -A danswer.background.celery.celery_run:celery_app beat --loglevel=INFO --logfile=/var/log/celery_beat.log
stdout_logfile=/var/log/celery_beat_supervisor.log
stdout_logfile_maxbytes=52428800
redirect_stderr=true

View File

@ -1,14 +1,14 @@
version: '3'
services:
api_server:
image: danswer/danswer-ee-backend:latest
image: danswer/danswer-backend:latest
build:
context: ../../backend
dockerfile: Dockerfile
command: >
/bin/sh -c "alembic upgrade head &&
echo \"Starting Danswer Api Server\" &&
uvicorn ee.danswer.main:app --host 0.0.0.0 --port 8080"
uvicorn danswer.main:app --host 0.0.0.0 --port 8080"
depends_on:
- relational_db
- index
@ -86,7 +86,9 @@ services:
# (time spent on finding the right docs + time spent fetching summaries from disk)
- LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-}
- LOG_ENDPOINT_LATENCY=${LOG_ENDPOINT_LATENCY:-}
# Enterprise Edition only
- ENABLE_PAID_EE_FEATURES=${ENABLE_PAID_EE_FEATURES:-false}
- API_KEY_HASH_ROUNDS=${API_KEY_HASH_ROUNDS:-}
# Seeding configuration
- ENV_SEED_CONFIGURATION=${ENV_SEED_CONFIGURATION:-}
@ -100,11 +102,11 @@ services:
background:
image: danswer/danswer-ee-backend:latest
image: danswer/danswer-backend:latest
build:
context: ../../backend
dockerfile: Dockerfile
command: /usr/bin/supervisord -c /etc/supervisor/conf.d/ee.supervisord.conf
command: /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
depends_on:
- relational_db
- index
@ -187,6 +189,9 @@ services:
- LOG_LEVEL=${LOG_LEVEL:-info} # Set to debug to get more fine-grained logs
- LOG_ALL_MODEL_INTERACTIONS=${LOG_ALL_MODEL_INTERACTIONS:-} # Log all of the prompts to the LLM
- LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-}
# Enterprise Edition stuff
- ENABLE_PAID_EE_FEATURES=${ENABLE_PAID_EE_FEATURES:-false}
extra_hosts:
- "host.docker.internal:host-gateway"
logging:
@ -197,7 +202,7 @@ services:
web_server:
image: danswer/danswer-ee-web-server:latest
image: danswer/danswer-web-server:latest
build:
context: ../../web
dockerfile: Dockerfile
@ -207,8 +212,9 @@ services:
- NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-}
# Enterprise Edition only
- NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-}
- NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES=${NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES:-true}
# DO NOT TURN ON unless you have EXPLICIT PERMISSION from Danswer.
- NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED=${NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED:-false}
depends_on:
@ -219,6 +225,9 @@ services:
- WEB_DOMAIN=${WEB_DOMAIN:-}
- THEME_IS_DARK=${THEME_IS_DARK:-}
# Enterprise Edition only
- ENABLE_PAID_EE_FEATURES=${ENABLE_PAID_EE_FEATURES:-false}
inference_model_server:
image: danswer/danswer-model-server:latest

View File

@ -81,6 +81,10 @@ services:
# If set to `true` will enable additional logs about Vespa query performance
# (time spent on finding the right docs + time spent fetching summaries from disk)
- LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-}
# Enterprise Edition only
- API_KEY_HASH_ROUNDS=${API_KEY_HASH_ROUNDS:-}
- ENABLE_PAID_EE_FEATURES=${ENABLE_PAID_EE_FEATURES:-false}
extra_hosts:
- "host.docker.internal:host-gateway"
logging:
@ -177,6 +181,10 @@ services:
- LOG_LEVEL=${LOG_LEVEL:-info} # Set to debug to get more fine-grained logs
- LOG_ALL_MODEL_INTERACTIONS=${LOG_ALL_MODEL_INTERACTIONS:-} # Log all of the prompts to the LLM
- LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-}
# Enterprise Edition only
- API_KEY_HASH_ROUNDS=${API_KEY_HASH_ROUNDS:-}
- ENABLE_PAID_EE_FEATURES=${ENABLE_PAID_EE_FEATURES:-false}
extra_hosts:
- "host.docker.internal:host-gateway"
logging:
@ -197,12 +205,17 @@ services:
- NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-}
- NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-}
depends_on:
- api_server
restart: always
environment:
- INTERNAL_URL=http://api_server:8080
- WEB_DOMAIN=${WEB_DOMAIN:-}
- THEME_IS_DARK=${THEME_IS_DARK:-}
# Enterprise Edition only
- ENABLE_PAID_EE_FEATURES=${ENABLE_PAID_EE_FEATURES:-false}
inference_model_server:

View File

@ -1,14 +1,14 @@
version: '3'
services:
api_server:
image: danswer/danswer-ee-backend:latest
image: danswer/danswer-backend:latest
build:
context: ../../backend
dockerfile: Dockerfile
command: >
/bin/sh -c "alembic upgrade head &&
echo \"Starting Danswer Api Server\" &&
uvicorn ee.danswer.main:app --host 0.0.0.0 --port 8080"
uvicorn danswer.main:app --host 0.0.0.0 --port 8080"
depends_on:
- relational_db
- index
@ -31,11 +31,11 @@ services:
background:
image: danswer/danswer-ee-backend:latest
image: danswer/danswer-backend:latest
build:
context: ../../backend
dockerfile: Dockerfile
command: /usr/bin/supervisord -c /etc/supervisor/conf.d/ee.supervisord.conf
command: /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
depends_on:
- relational_db
- index
@ -60,7 +60,7 @@ services:
web_server:
image: danswer/danswer-ee-web-server:latest
image: danswer/danswer-web-server:latest
build:
context: ../../web
dockerfile: Dockerfile
@ -71,7 +71,6 @@ services:
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-}
- NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-}
- NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES=${NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES:-true}
depends_on:
- api_server
restart: always

View File

@ -1,14 +1,14 @@
version: '3'
services:
api_server:
image: danswer/danswer-ee-backend:latest
image: danswer/danswer-backend:latest
build:
context: ../../backend
dockerfile: Dockerfile
command: >
/bin/sh -c "alembic upgrade head &&
echo \"Starting Danswer Api Server\" &&
uvicorn ee.danswer.main:app --host 0.0.0.0 --port 8080"
uvicorn danswer.main:app --host 0.0.0.0 --port 8080"
depends_on:
- relational_db
- index
@ -31,11 +31,11 @@ services:
background:
image: danswer/danswer-ee-backend:latest
image: danswer/danswer-backend:latest
build:
context: ../../backend
dockerfile: Dockerfile
command: /usr/bin/supervisord -c /etc/supervisor/conf.d/ee.supervisord.conf
command: /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
depends_on:
- relational_db
- index
@ -59,7 +59,7 @@ services:
max-file: "6"
web_server:
image: danswer/danswer-ee-web-server:latest
image: danswer/danswer-web-server:latest
build:
context: ../../web
dockerfile: Dockerfile
@ -70,7 +70,6 @@ services:
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-}
- NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-}
- NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES=${NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES:-true}
depends_on:
- api_server
restart: always

View File

@ -28,7 +28,7 @@ spec:
spec:
containers:
- name: api-server
image: danswer/danswer-ee-backend:latest
image: danswer/danswer-backend:latest
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
@ -36,7 +36,7 @@ spec:
- |
alembic upgrade head &&
echo "Starting Danswer Api Server" &&
uvicorn ee.danswer.main:app --host 0.0.0.0 --port 8080
uvicorn danswer.main:app --host 0.0.0.0 --port 8080
ports:
- containerPort: 8080
# There are some extra values since this is shared between services

View File

@ -14,9 +14,9 @@ spec:
spec:
containers:
- name: background
image: danswer/danswer-ee-backend:latest
image: danswer/danswer-backend:latest
imagePullPolicy: IfNotPresent
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/ee.supervisord.conf"]
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
# There are some extra values since this is shared between services
# There are no conflicts though, extra env variables are simply ignored
envFrom:

View File

@ -27,7 +27,7 @@ spec:
spec:
containers:
- name: web-server
image: danswer/danswer-ee-web-server:latest
image: danswer/danswer-web-server:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
@ -36,5 +36,3 @@ spec:
envFrom:
- configMapRef:
name: env-configmap
args:
- "NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES=true"

View File

@ -1,11 +1,11 @@
FROM node:20-alpine AS base
LABEL com.danswer.maintainer="founders@danswer.ai"
LABEL com.danswer.description="This image is the Enterprise Edition (Paid Edition) \
frontend/webserver of Danswer. If you do not have a contract or agreement with DanswerAI, you are \
not permitted to use this container outside of personal development or testing purposes. Please \
reach out to founders@danswer.ai for more information. You can access the MIT version of Danswer \
at https://github.com/danswer-ai/danswer"
LABEL com.danswer.description="This image is the web/frontend container of Danswer which \
contains code for both the Community and Enterprise editions of Danswer. If you do not \
have a contract or agreement with DanswerAI, you are not permitted to use the Enterprise \
Edition features outside of personal development or testing purposes. Please reach out to \
founders@danswer.ai for more information. Please visit https://github.com/danswer-ai/danswer"
# Default DANSWER_VERSION, typically overriden during builds by GitHub Actions.
ARG DANSWER_VERSION=0.3-dev

View File

@ -9,64 +9,17 @@ const nextConfig = {
output: "standalone",
swcMinify: true,
rewrites: async () => {
const eeRedirects =
process.env.NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES?.toLowerCase() === "true"
? [
// user group pages
{
source: "/admin/groups",
destination: "/ee/admin/groups",
},
{
source: "/admin/groups/:path*",
destination: "/ee/admin/groups/:path*",
},
{
source: "/admin/api-key",
destination: "/ee/admin/api-key",
},
// analytics / audit log pages
{
source: "/admin/performance/usage",
destination: "/ee/admin/performance/usage",
},
{
source: "/admin/performance/query-history",
destination: "/ee/admin/performance/query-history",
},
{
source: "/admin/performance/query-history/:path*",
destination: "/ee/admin/performance/query-history/:path*",
},
// whitelabeling
{
source: "/admin/whitelabeling",
destination: "/ee/admin/whitelabeling",
},
// custom analytics/tracking
{
source: "/admin/performance/custom-analytics",
destination: "/ee/admin/performance/custom-analytics",
},
// token rate limits
{
source: "/admin/token-rate-limits",
destination: "/ee/admin/token-rate-limits",
},
]
: [];
// In production, something else (nginx in the one box setup) should take
// care of this rewrite. TODO (chris): better support setups where
// web_server and api_server are on different machines.
if (process.env.NODE_ENV === "production") return eeRedirects;
if (process.env.NODE_ENV === "production") return [];
return [
{
source: "/api/:path*",
destination: "http://127.0.0.1:8080/:path*", // Proxy to Backend
},
].concat(eeRedirects);
];
},
redirects: async () => {
// In production, something else (nginx in the one box setup) should take

View File

@ -25,7 +25,6 @@ import {
} from "@/components/admin/connectors/Field";
import { HidableSection } from "./HidableSection";
import { FiPlus, FiX } from "react-icons/fi";
import { EE_ENABLED } from "@/lib/constants";
import { useUserGroups } from "@/lib/hooks";
import { Bubble } from "@/components/Bubble";
import { GroupsIcon } from "@/components/icons/icons";
@ -37,6 +36,8 @@ import { ToolSnapshot } from "@/lib/tools/interfaces";
import { checkUserIsNoAuthUser } from "@/lib/user";
import { addAssistantToList } from "@/lib/assistants/updateAssistantPreferences";
import { checkLLMSupportsImageInput } from "@/lib/llm/utils";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
function findSearchTool(tools: ToolSnapshot[]) {
return tools.find((tool) => tool.in_code_tool_id === "SearchTool");
@ -80,6 +81,8 @@ export function AssistantEditor({
const router = useRouter();
const { popup, setPopup } = usePopup();
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
// EE only
const { data: userGroups, isLoading: userGroupsIsLoading } = useUserGroups();
@ -868,7 +871,7 @@ export function AssistantEditor({
<Divider />
{EE_ENABLED &&
{isPaidEnterpriseFeaturesEnabled &&
userGroups &&
(!user || user.role === "admin") && (
<>

View File

@ -25,13 +25,15 @@ import { getNameFromPath } from "@/lib/fileUtils";
import { Button, Card, Divider, Text } from "@tremor/react";
import { AdminPageTitle } from "@/components/admin/Title";
import IsPublicField from "@/components/admin/connectors/IsPublicField";
import { EE_ENABLED } from "@/lib/constants";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
const Main = () => {
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
const [filesAreUploading, setFilesAreUploading] = useState<boolean>(false);
const { popup, setPopup } = usePopup();
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
const { mutate } = useSWRConfig();
const {
@ -90,13 +92,13 @@ const Main = () => {
initialValues={{
name: "",
selectedFiles: [],
is_public: EE_ENABLED ? false : undefined,
is_public: isPaidEnterpriseFeaturesEnabled ? false : undefined,
}}
validationSchema={Yup.object().shape({
name: Yup.string().required(
"Please enter a descriptive name for the files"
),
...(EE_ENABLED && {
...(isPaidEnterpriseFeaturesEnabled && {
is_public: Yup.boolean().required(),
}),
})}
@ -226,7 +228,7 @@ const Main = () => {
setSelectedFiles={setSelectedFiles}
/>
{EE_ENABLED && (
{isPaidEnterpriseFeaturesEnabled && (
<>
<Divider />
<IsPublicField />

View File

@ -1,3 +1,5 @@
"use client";
import { ArrayHelpers, FieldArray, Form, Formik } from "formik";
import * as Yup from "yup";
import { PopupSpec } from "@/components/admin/connectors/Popup";
@ -9,8 +11,8 @@ import {
} from "@/components/admin/connectors/Field";
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
import { Button, Divider, Text } from "@tremor/react";
import { EE_ENABLED } from "@/lib/constants";
import { FiUsers } from "react-icons/fi";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
interface SetCreationPopupProps {
ccPairs: ConnectorIndexingStatus<any, any>[];
@ -27,6 +29,8 @@ export const DocumentSetCreationForm = ({
setPopup,
existingDocumentSet,
}: SetCreationPopupProps) => {
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
const isUpdate = existingDocumentSet !== undefined;
return (
@ -167,46 +171,48 @@ export const DocumentSetCreationForm = ({
)}
/>
{EE_ENABLED && userGroups && userGroups.length > 0 && (
<div>
<Divider />
{isPaidEnterpriseFeaturesEnabled &&
userGroups &&
userGroups.length > 0 && (
<div>
<Divider />
<BooleanFormField
name="is_public"
label="Is Public?"
subtext={
<BooleanFormField
name="is_public"
label="Is Public?"
subtext={
<>
If the document set is public, then it will be visible
to <b>all users</b>. If it is not public, then only
users in the specified groups will be able to see it.
</>
}
/>
<Divider />
<h2 className="mb-1 font-medium text-base">
Groups with Access
</h2>
{!values.is_public ? (
<>
If the document set is public, then it will be visible to{" "}
<b>all users</b>. If it is not public, then only users in
the specified groups will be able to see it.
</>
}
/>
<Divider />
<h2 className="mb-1 font-medium text-base">
Groups with Access
</h2>
{!values.is_public ? (
<>
<Text className="mb-3">
If any groups are specified, then this Document Set will
only be visible to the specified groups. If no groups are
specified, then the Document Set will be visible to all
users.
</Text>
<FieldArray
name="groups"
render={(arrayHelpers: ArrayHelpers) => (
<div className="flex gap-2 flex-wrap">
{userGroups.map((userGroup) => {
const ind = values.groups.indexOf(userGroup.id);
let isSelected = ind !== -1;
return (
<div
key={userGroup.id}
className={
`
<Text className="mb-3">
If any groups are specified, then this Document Set will
only be visible to the specified groups. If no groups
are specified, then the Document Set will be visible to
all users.
</Text>
<FieldArray
name="groups"
render={(arrayHelpers: ArrayHelpers) => (
<div className="flex gap-2 flex-wrap">
{userGroups.map((userGroup) => {
const ind = values.groups.indexOf(userGroup.id);
let isSelected = ind !== -1;
return (
<div
key={userGroup.id}
className={
`
px-3
py-1
rounded-lg
@ -215,38 +221,38 @@ export const DocumentSetCreationForm = ({
w-fit
flex
cursor-pointer ` +
(isSelected
? " bg-background-strong"
: " hover:bg-hover")
}
onClick={() => {
if (isSelected) {
arrayHelpers.remove(ind);
} else {
arrayHelpers.push(userGroup.id);
(isSelected
? " bg-background-strong"
: " hover:bg-hover")
}
}}
>
<div className="my-auto flex">
<FiUsers className="my-auto mr-2" />{" "}
{userGroup.name}
onClick={() => {
if (isSelected) {
arrayHelpers.remove(ind);
} else {
arrayHelpers.push(userGroup.id);
}
}}
>
<div className="my-auto flex">
<FiUsers className="my-auto mr-2" />{" "}
{userGroup.name}
</div>
</div>
</div>
);
})}
</div>
)}
/>
</>
) : (
<Text>
This Document Set is public, so this does not apply. If you
want to control which user groups see this Document Set,
mark it as non-public!
</Text>
)}
</div>
)}
);
})}
</div>
)}
/>
</>
) : (
<Text>
This Document Set is public, so this does not apply. If
you want to control which user groups see this Document
Set, mark it as non-public!
</Text>
)}
</div>
)}
<div className="flex mt-6">
<Button
type="submit"

View File

@ -1,4 +1,3 @@
import { EE_ENABLED } from "@/lib/constants";
import { errorHandlingFetcher } from "@/lib/fetcher";
import { DocumentSet } from "@/lib/types";
import useSWR, { mutate } from "swr";

View File

@ -1,6 +1,5 @@
import { NotebookIcon } from "@/components/icons/icons";
import { getWebVersion, getBackendVersion } from "@/lib/version";
import { EE_ENABLED } from "@/lib/constants";
const Page = async () => {
let web_version: string | null = null;
@ -22,9 +21,6 @@ const Page = async () => {
</div>
<div>
<p className="font-bold text-lg my-auto mb-2">
{EE_ENABLED ? "Danswer Enterprise Edition" : "Danswer MIT"}
</p>
<div className="flex mb-2">
<p className="my-auto mr-1">Backend Version: </p>
<p className="text-base my-auto text-slate-400 italic">

View File

@ -22,7 +22,7 @@ import { GenericTokenRateLimitTable } from "./TokenRateLimitTables";
import { mutate } from "swr";
import { usePopup } from "@/components/admin/connectors/Popup";
import { CreateRateLimitModal } from "./CreateRateLimitModal";
import { EE_ENABLED } from "@/lib/constants";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
const BASE_URL = "/api/admin/token-rate-limits";
const GLOBAL_TOKEN_FETCH_URL = `${BASE_URL}/global`;
@ -69,6 +69,8 @@ function Main() {
const [modalIsOpen, setModalIsOpen] = useState(false);
const { popup, setPopup } = usePopup();
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
const updateTable = (target_scope: Scope) => {
if (target_scope === Scope.GLOBAL) {
mutate(GLOBAL_TOKEN_FETCH_URL);
@ -120,7 +122,7 @@ function Main() {
token spend.
</Text>
</li>
{EE_ENABLED && (
{isPaidEnterpriseFeaturesEnabled && (
<>
<li>
<Text>
@ -150,7 +152,7 @@ function Main() {
Create a Token Rate Limit
</Button>
{EE_ENABLED && (
{isPaidEnterpriseFeaturesEnabled && (
<TabGroup className="mt-6" index={tabIndex} onIndexChange={setTabIndex}>
<TabList variant="line">
<Tab icon={FiGlobe}>Global</Tab>
@ -191,7 +193,7 @@ function Main() {
</TabGroup>
)}
{!EE_ENABLED && (
{!isPaidEnterpriseFeaturesEnabled && (
<div className="mt-6">
<GenericTokenRateLimitTable
fetchUrl={GLOBAL_TOKEN_FETCH_URL}
@ -206,7 +208,9 @@ function Main() {
setIsOpen={() => setModalIsOpen(false)}
setPopup={setPopup}
onSubmit={handleSubmit}
forSpecificScope={EE_ENABLED ? undefined : Scope.GLOBAL}
forSpecificScope={
isPaidEnterpriseFeaturesEnabled ? undefined : Scope.GLOBAL
}
/>
</div>
);

View File

@ -1,5 +1,5 @@
The DanswerAI Enterprise license (the “Enterprise License”)
Copyright (c) 2023 DanswerAI, Inc.
Copyright (c) 2023-present DanswerAI, Inc.
With regard to the Danswer Software:

View File

@ -1,11 +1,11 @@
import { EE_ENABLED } from "@/lib/constants";
import { SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED } from "@/lib/constants";
export default async function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
if (!EE_ENABLED) {
if (!SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED) {
return (
<div className="flex h-screen">
<div className="mx-auto my-auto text-lg font-bold text-red-500">

View File

@ -19,7 +19,7 @@ import {
getAuthTypeMetadataSS,
getCurrentUserSS,
} from "@/lib/userSS";
import { EE_ENABLED } from "@/lib/constants";
import { SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED } from "@/lib/constants";
import { redirect } from "next/navigation";
import {
FiActivity,
@ -194,7 +194,7 @@ export async function Layout({ children }: { children: React.ReactNode }) {
),
link: "/admin/users",
},
...(EE_ENABLED
...(SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED
? [
{
name: (
@ -227,7 +227,7 @@ export async function Layout({ children }: { children: React.ReactNode }) {
},
],
},
...(EE_ENABLED
...(SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED
? [
{
name: "Performance",
@ -275,7 +275,7 @@ export async function Layout({ children }: { children: React.ReactNode }) {
),
link: "/admin/settings",
},
...(EE_ENABLED
...(SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED
? [
{
name: (

View File

@ -1,3 +1,5 @@
"use client";
import React, { useState } from "react";
import { Formik, Form } from "formik";
import * as Yup from "yup";
@ -14,8 +16,8 @@ import { BooleanFormField, TextFormField } from "./Field";
import { createCredential, linkCredential } from "@/lib/credential";
import { useSWRConfig } from "swr";
import { Button, Divider } from "@tremor/react";
import { EE_ENABLED } from "@/lib/constants";
import IsPublicField from "./IsPublicField";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
const BASE_CONNECTOR_URL = "/api/manage/admin/connector";
@ -76,7 +78,6 @@ interface BaseProps<T extends Yup.AnyObject> {
// If specified, then we will create an empty credential and associate
// the connector with it. If credentialId is specified, then this will be ignored
shouldCreateEmptyCredentialForConnector?: boolean;
showNonPublicOption?: boolean;
}
type ConnectorFormProps<T extends Yup.AnyObject> = RequireAtLeastOne<
@ -98,12 +99,13 @@ export function ConnectorForm<T extends Yup.AnyObject>({
pruneFreq,
onSubmit,
shouldCreateEmptyCredentialForConnector,
// only show this option for EE, since groups are not supported in CE
showNonPublicOption = EE_ENABLED,
}: ConnectorFormProps<T>): JSX.Element {
const { mutate } = useSWRConfig();
const { popup, setPopup } = usePopup();
// only show this option for EE, since groups are not supported in CE
const showNonPublicOption = usePaidEnterpriseFeaturesEnabled();
const shouldHaveNameInput = credentialId !== undefined && !ccPairNameBuilder;
const ccPairNameInitialValue = shouldHaveNameInput

View File

@ -1,10 +1,13 @@
import { EnterpriseSettings, Settings } from "@/app/admin/settings/interfaces";
import { CUSTOM_ANALYTICS_ENABLED, EE_ENABLED } from "@/lib/constants";
import {
CUSTOM_ANALYTICS_ENABLED,
SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED,
} from "@/lib/constants";
import { fetchSS } from "@/lib/utilsSS";
export async function fetchSettingsSS() {
const tasks = [fetchSS("/settings")];
if (EE_ENABLED) {
if (SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED) {
tasks.push(fetchSS("/enterprise-settings"));
if (CUSTOM_ANALYTICS_ENABLED) {
tasks.push(fetchSS("/enterprise-settings/custom-analytics-script"));

View File

@ -0,0 +1,12 @@
"use client";
import { useContext } from "react";
import { SettingsContext } from "./SettingsProvider";
export function usePaidEnterpriseFeaturesEnabled() {
const combinedSettings = useContext(SettingsContext);
if (!combinedSettings) {
return null;
}
return combinedSettings.enterpriseSettings !== null;
}

View File

@ -25,14 +25,13 @@ export const HEADER_PADDING = `pt-[64px]`;
export const LOGOUT_DISABLED =
process.env.NEXT_PUBLIC_DISABLE_LOGOUT?.toLowerCase() === "true";
// NOTE: since this is a `NEXT_PUBLIC_` variable, it will be set at
// build-time
// TODO: consider moving this to an API call so that the api_server
// can be the single source of truth
export const EE_ENABLED =
process.env.NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES?.toLowerCase() === "true";
/* Enterprise-only settings */
// NOTE: this should ONLY be used on the server-side. If used client side,
// it will not be accurate (will always be false).
export const SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED =
process.env.ENABLE_PAID_EE_FEATURES?.toLowerCase() === "true";
// Enterprise-only settings
export const CUSTOM_ANALYTICS_ENABLED = process.env.CUSTOM_ANALYTICS_SECRET_KEY
? true
: false;

View File

@ -11,10 +11,10 @@ import { errorHandlingFetcher } from "./fetcher";
import { useState } from "react";
import { DateRangePickerValue } from "@tremor/react";
import { SourceMetadata } from "./search/interfaces";
import { EE_ENABLED } from "./constants";
import { destructureValue } from "./llm/utils";
import { ChatSession } from "@/app/chat/interfaces";
import { UsersResponse } from "./users/interfaces";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
const CREDENTIAL_URL = "/api/manage/admin/credential";
@ -192,8 +192,9 @@ export const useUserGroups = (): {
refreshUserGroups: () => void;
} => {
const swrResponse = useSWR<UserGroup[]>(USER_GROUP_URL, errorHandlingFetcher);
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
if (!EE_ENABLED) {
if (!isPaidEnterpriseFeaturesEnabled) {
return {
...{
data: [],

39
web/src/middleware.ts Normal file
View File

@ -0,0 +1,39 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED } from "./lib/constants";
const eePaths = [
"/admin/groups",
"/admin/api-key",
"/admin/performance/usage",
"/admin/performance/query-history",
"/admin/whitelabeling",
"/admin/performance/custom-analytics",
];
const eePathsForMatcher = eePaths.map((path) => `${path}/:path*`);
export async function middleware(request: NextRequest) {
if (SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED) {
const pathname = request.nextUrl.pathname;
// Check if the current path is in the eePaths list
if (eePaths.some((path) => pathname.startsWith(path))) {
// Add '/ee' to the beginning of the pathname
const newPathname = `/ee${pathname}`;
// Create a new URL with the modified pathname
const newUrl = new URL(newPathname, request.url);
// Rewrite to the new URL
return NextResponse.rewrite(newUrl);
}
}
// Continue with the response if no rewrite is needed
return NextResponse.next();
}
// Specify the paths that the middleware should run for
export const config = {
matcher: eePathsForMatcher,
};

View File

@ -1,13 +1,11 @@
var merge = require("lodash/merge");
const baseThemes = require("./tailwind-themes/tailwind.config.js");
const customThemes =
process.env.NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES &&
process.env.NEXT_PUBLIC_THEME
? require(
`./tailwind-themes/custom/${process.env.NEXT_PUBLIC_THEME}/tailwind.config.js`
)
: null;
const customThemes = process.env.NEXT_PUBLIC_THEME
? require(
`./tailwind-themes/custom/${process.env.NEXT_PUBLIC_THEME}/tailwind.config.js`
)
: null;
/** @type {import('tailwindcss').Config} */
module.exports = customThemes ? merge(baseThemes, customThemes) : baseThemes;