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

View File

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

View File

@ -25,8 +25,8 @@ jobs:
- name: Pull, Tag and Push Web Server Image - name: Pull, Tag and Push Web Server Image
run: | 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 - name: Pull, Tag and Push API Server Image
run: | 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 # 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 # 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: To run the backend API server, navigate back to `danswer/backend` and run:
```bash ```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):_ _For Windows (for compatibility with both PowerShell and Command Prompt):_
```bash ```bash
powershell -Command " powershell -Command "
$env:AUTH_TYPE='disabled' $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: 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. * 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. * 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 * Websites
* And more ... * 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 ## 💡 Contributing
Looking to contribute? Please check out the [Contribution Guide](CONTRIBUTING.md) for more details. 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 FROM python:3.11.7-slim-bookworm
LABEL com.danswer.maintainer="founders@danswer.ai" LABEL com.danswer.maintainer="founders@danswer.ai"
LABEL com.danswer.description="This image is the Enterprise Edition (Paid Edition) backend of \ LABEL com.danswer.description="This image is the web/frontend container of Danswer which \
Danswer. If you do not have a contract or agreement with DanswerAI, you are not permitted to use \ contains code for both the Community and Enterprise editions of Danswer. If you do not \
this container outside of personal development or testing purposes. Please reach out to \ have a contract or agreement with DanswerAI, you are not permitted to use the Enterprise \
founders@danswer.ai for more information. You can access the MIT version of Danswer at \ Edition features outside of personal development or testing purposes. Please reach out to \
https://github.com/danswer-ai/danswer" founders@danswer.ai for more information. Please visit https://github.com/danswer-ai/danswer"
# Default DANSWER_VERSION, typically overriden during builds by GitHub Actions. # Default DANSWER_VERSION, typically overriden during builds by GitHub Actions.
ARG DANSWER_VERSION=0.3-dev ARG DANSWER_VERSION=0.3-dev
@ -18,20 +18,33 @@ RUN echo "DANSWER_VERSION: ${DANSWER_VERSION}"
# curl included just for users' convenience # curl included just for users' convenience
# zip for Vespa step futher down # zip for Vespa step futher down
# ca-certificates for HTTPS # ca-certificates for HTTPS
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y cmake curl zip ca-certificates libgnutls30=3.7.9-2+deb12u2 \ apt-get install -y \
libblkid1=2.38.1-5+deb12u1 libmount1=2.38.1-5+deb12u1 libsmartcols1=2.38.1-5+deb12u1 \ cmake \
libuuid1=2.38.1-5+deb12u1 && \ 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/* && \ rm -rf /var/lib/apt/lists/* && \
apt-get clean apt-get clean
# Install Python dependencies # Install Python dependencies
# Remove py which is pulled in by retry, py is not needed and is a CVE # Remove py which is pulled in by retry, py is not needed and is a CVE
COPY ./requirements/default.txt /tmp/requirements.txt 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 && \ 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 ln -s /usr/local/bin/supervisord /usr/bin/supervisord
# Cleanup for CVEs and size reduction # 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 # 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 is part of the base Python Debian image but not needed for Danswer functionality
# perl-base could only be removed with --allow-remove-essential # perl-base could only be removed with --allow-remove-essential
RUN apt-get remove -y --allow-remove-essential perl-base xserver-common xvfb cmake \ RUN apt-get update && \
libldap-2.5-0 libldap-2.5-0 && \ 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 && \ apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
rm /usr/local/lib/python3.11/site-packages/tornado/test/test.key rm -f /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
# Pre-downloading models for setups with limited egress # Pre-downloading models for setups with limited egress
RUN python -c "from transformers import AutoTokenizer; AutoTokenizer.from_pretrained('intfloat/e5-base-v2')" RUN python -c "from transformers import AutoTokenizer; AutoTokenizer.from_pretrained('intfloat/e5-base-v2')"
@ -69,7 +81,7 @@ WORKDIR /app
# Enterprise Version Files # Enterprise Version Files
COPY ./ee /app/ee 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 # Set up application files
COPY ./danswer /app/danswer 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.search.search_nlp_models import warm_up_encoders
from danswer.utils.logger import setup_logger from danswer.utils.logger import setup_logger
from danswer.utils.variable_functionality import global_version 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 INDEXING_MODEL_SERVER_HOST
from shared_configs.configs import LOG_LEVEL from shared_configs.configs import LOG_LEVEL
from shared_configs.configs import MODEL_SERVER_PORT 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: def update__main() -> None:
set_is_ee_based_on_env_variable()
logger.info("Starting Indexing Loop") logger.info("Starting Indexing Loop")
update_loop() update_loop()

View File

@ -279,3 +279,15 @@ TOKEN_BUDGET_GLOBALLY_ENABLED = (
CUSTOM_ANSWER_VALIDITY_CONDITIONS = json.loads( CUSTOM_ANSWER_VALIDITY_CONDITIONS = json.loads(
os.environ.get("CUSTOM_ANSWER_VALIDITY_CONDITIONS", "[]") 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 optional_telemetry
from danswer.utils.telemetry import RecordType from danswer.utils.telemetry import RecordType
from danswer.utils.variable_functionality import fetch_versioned_implementation 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 ENABLE_RERANKING_REAL_TIME_FLOW
from shared_configs.configs import MODEL_SERVER_HOST from shared_configs.configs import MODEL_SERVER_HOST
from shared_configs.configs import MODEL_SERVER_PORT from shared_configs.configs import MODEL_SERVER_PORT
@ -369,11 +371,18 @@ def get_application() -> FastAPI:
return application 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__": if __name__ == "__main__":
logger.info( logger.info(
f"Starting Danswer Backend version {__version__} on http://{APP_HOST}:{str(APP_PORT)}/" 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) 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), _: User = Depends(current_admin_user),
db_session: Session = Depends(get_session), db_session: Session = Depends(get_session),
) -> None: ) -> 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 connector_id = connector_credential_pair_identifier.connector_id
credential_id = connector_credential_pair_identifier.credential_id credential_id = connector_credential_pair_identifier.credential_id

View File

@ -3,9 +3,9 @@ import importlib
from typing import Any from typing import Any
from typing import TypeVar from typing import TypeVar
from danswer.configs.app_configs import ENTERPRISE_EDITION_ENABLED
from danswer.utils.logger import setup_logger from danswer.utils.logger import setup_logger
logger = setup_logger() logger = setup_logger()
@ -23,6 +23,12 @@ class DanswerVersion:
global_version = 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) @functools.lru_cache(maxsize=128)
def fetch_versioned_implementation(module: str, attribute: str) -> Any: def fetch_versioned_implementation(module: str, attribute: str) -> Any:
logger.debug("Fetching versioned implementation for %s.%s", module, attribute) 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”) The DanswerAI Enterprise license (the “Enterprise License”)
Copyright (c) 2023 DanswerAI, Inc. Copyright (c) 2023-present DanswerAI, Inc.
With regard to the Danswer Software: With regard to the Danswer Software:

View File

@ -2,7 +2,7 @@ from datetime import timedelta
from sqlalchemy.orm import Session 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.configs.app_configs import JOB_TIMEOUT
from danswer.db.engine import get_sqlalchemy_engine from danswer.db.engine import get_sqlalchemy_engine
from danswer.db.tasks import check_live_task_not_timed_out 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 fastapi import FastAPI
from httpx_oauth.clients.openid import OpenID from httpx_oauth.clients.openid import OpenID
from danswer.auth.users import auth_backend from danswer.auth.users import auth_backend
from danswer.auth.users import fastapi_users 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 AUTH_TYPE
from danswer.configs.app_configs import OAUTH_CLIENT_ID from danswer.configs.app_configs import OAUTH_CLIENT_ID
from danswer.configs.app_configs import OAUTH_CLIENT_SECRET from danswer.configs.app_configs import OAUTH_CLIENT_SECRET
from danswer.configs.app_configs import USER_AUTH_SECRET from danswer.configs.app_configs import USER_AUTH_SECRET
from danswer.configs.app_configs import WEB_DOMAIN from danswer.configs.app_configs import WEB_DOMAIN
from danswer.configs.constants import AuthType 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.main import include_router_with_global_prefix_prepended
from danswer.utils.logger import setup_logger from danswer.utils.logger import setup_logger
from danswer.utils.variable_functionality import global_version from danswer.utils.variable_functionality import global_version
@ -45,14 +42,14 @@ from ee.danswer.utils.encryption import test_encryption
logger = setup_logger() 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 that happens at import time is not guaranteed to be running ee-version
# Anything after the server startup will be running ee version # Anything after the server startup will be running ee version
global_version.set_ee() global_version.set_ee()
test_encryption() test_encryption()
application = get_application() application = get_application_base()
if AUTH_TYPE == AuthType.OIDC: if AUTH_TYPE == AuthType.OIDC:
include_router_with_global_prefix_prepended( include_router_with_global_prefix_prepended(
@ -108,13 +105,3 @@ def get_ee_application() -> FastAPI:
seed_db() seed_db()
return application 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 # relatively compute-light (e.g. they tend to just make a bunch of requests to
# Vespa / Postgres) # Vespa / Postgres)
[program:celery_worker] [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=/var/log/celery_worker_supervisor.log
stdout_logfile_maxbytes=52428800 stdout_logfile_maxbytes=52428800
redirect_stderr=true redirect_stderr=true
@ -33,7 +33,7 @@ autorestart=true
# Job scheduler for periodic tasks # Job scheduler for periodic tasks
[program:celery_beat] [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=/var/log/celery_beat_supervisor.log
stdout_logfile_maxbytes=52428800 stdout_logfile_maxbytes=52428800
redirect_stderr=true redirect_stderr=true

View File

@ -1,14 +1,14 @@
version: '3' version: '3'
services: services:
api_server: api_server:
image: danswer/danswer-ee-backend:latest image: danswer/danswer-backend:latest
build: build:
context: ../../backend context: ../../backend
dockerfile: Dockerfile dockerfile: Dockerfile
command: > command: >
/bin/sh -c "alembic upgrade head && /bin/sh -c "alembic upgrade head &&
echo \"Starting Danswer Api Server\" && 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: depends_on:
- relational_db - relational_db
- index - index
@ -86,7 +86,9 @@ services:
# (time spent on finding the right docs + time spent fetching summaries from disk) # (time spent on finding the right docs + time spent fetching summaries from disk)
- LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-} - LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-}
- LOG_ENDPOINT_LATENCY=${LOG_ENDPOINT_LATENCY:-} - LOG_ENDPOINT_LATENCY=${LOG_ENDPOINT_LATENCY:-}
# Enterprise Edition only # Enterprise Edition only
- ENABLE_PAID_EE_FEATURES=${ENABLE_PAID_EE_FEATURES:-false}
- API_KEY_HASH_ROUNDS=${API_KEY_HASH_ROUNDS:-} - API_KEY_HASH_ROUNDS=${API_KEY_HASH_ROUNDS:-}
# Seeding configuration # Seeding configuration
- ENV_SEED_CONFIGURATION=${ENV_SEED_CONFIGURATION:-} - ENV_SEED_CONFIGURATION=${ENV_SEED_CONFIGURATION:-}
@ -100,11 +102,11 @@ services:
background: background:
image: danswer/danswer-ee-backend:latest image: danswer/danswer-backend:latest
build: build:
context: ../../backend context: ../../backend
dockerfile: Dockerfile 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: depends_on:
- relational_db - relational_db
- index - index
@ -187,6 +189,9 @@ services:
- LOG_LEVEL=${LOG_LEVEL:-info} # Set to debug to get more fine-grained logs - 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_ALL_MODEL_INTERACTIONS=${LOG_ALL_MODEL_INTERACTIONS:-} # Log all of the prompts to the LLM
- LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-} - LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-}
# Enterprise Edition stuff
- ENABLE_PAID_EE_FEATURES=${ENABLE_PAID_EE_FEATURES:-false}
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
logging: logging:
@ -197,7 +202,7 @@ services:
web_server: web_server:
image: danswer/danswer-ee-web-server:latest image: danswer/danswer-web-server:latest
build: build:
context: ../../web context: ../../web
dockerfile: Dockerfile dockerfile: Dockerfile
@ -207,8 +212,9 @@ services:
- NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS:-} - 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_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-} - NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-}
# Enterprise Edition only
- NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-} - 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. # 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} - NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED=${NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED:-false}
depends_on: depends_on:
@ -219,6 +225,9 @@ services:
- WEB_DOMAIN=${WEB_DOMAIN:-} - WEB_DOMAIN=${WEB_DOMAIN:-}
- THEME_IS_DARK=${THEME_IS_DARK:-} - THEME_IS_DARK=${THEME_IS_DARK:-}
# Enterprise Edition only
- ENABLE_PAID_EE_FEATURES=${ENABLE_PAID_EE_FEATURES:-false}
inference_model_server: inference_model_server:
image: danswer/danswer-model-server:latest 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 # 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) # (time spent on finding the right docs + time spent fetching summaries from disk)
- LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-} - 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: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
logging: logging:
@ -177,6 +181,10 @@ services:
- LOG_LEVEL=${LOG_LEVEL:-info} # Set to debug to get more fine-grained logs - 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_ALL_MODEL_INTERACTIONS=${LOG_ALL_MODEL_INTERACTIONS:-} # Log all of the prompts to the LLM
- LOG_VESPA_TIMING_INFORMATION=${LOG_VESPA_TIMING_INFORMATION:-} - 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: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
logging: logging:
@ -197,12 +205,17 @@ services:
- NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_POSITIVE_PREDEFINED_FEEDBACK_OPTIONS:-} - 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_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS:-}
- NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-} - NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-}
- NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-}
depends_on: depends_on:
- api_server - api_server
restart: always restart: always
environment: environment:
- INTERNAL_URL=http://api_server:8080 - INTERNAL_URL=http://api_server:8080
- WEB_DOMAIN=${WEB_DOMAIN:-} - 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: inference_model_server:

View File

@ -1,14 +1,14 @@
version: '3' version: '3'
services: services:
api_server: api_server:
image: danswer/danswer-ee-backend:latest image: danswer/danswer-backend:latest
build: build:
context: ../../backend context: ../../backend
dockerfile: Dockerfile dockerfile: Dockerfile
command: > command: >
/bin/sh -c "alembic upgrade head && /bin/sh -c "alembic upgrade head &&
echo \"Starting Danswer Api Server\" && 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: depends_on:
- relational_db - relational_db
- index - index
@ -31,11 +31,11 @@ services:
background: background:
image: danswer/danswer-ee-backend:latest image: danswer/danswer-backend:latest
build: build:
context: ../../backend context: ../../backend
dockerfile: Dockerfile 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: depends_on:
- relational_db - relational_db
- index - index
@ -60,7 +60,7 @@ services:
web_server: web_server:
image: danswer/danswer-ee-web-server:latest image: danswer/danswer-web-server:latest
build: build:
context: ../../web context: ../../web
dockerfile: Dockerfile dockerfile: Dockerfile
@ -71,7 +71,6 @@ services:
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_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_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-}
- NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-} - NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-}
- NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES=${NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES:-true}
depends_on: depends_on:
- api_server - api_server
restart: always restart: always

View File

@ -1,14 +1,14 @@
version: '3' version: '3'
services: services:
api_server: api_server:
image: danswer/danswer-ee-backend:latest image: danswer/danswer-backend:latest
build: build:
context: ../../backend context: ../../backend
dockerfile: Dockerfile dockerfile: Dockerfile
command: > command: >
/bin/sh -c "alembic upgrade head && /bin/sh -c "alembic upgrade head &&
echo \"Starting Danswer Api Server\" && 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: depends_on:
- relational_db - relational_db
- index - index
@ -31,11 +31,11 @@ services:
background: background:
image: danswer/danswer-ee-backend:latest image: danswer/danswer-backend:latest
build: build:
context: ../../backend context: ../../backend
dockerfile: Dockerfile 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: depends_on:
- relational_db - relational_db
- index - index
@ -59,7 +59,7 @@ services:
max-file: "6" max-file: "6"
web_server: web_server:
image: danswer/danswer-ee-web-server:latest image: danswer/danswer-web-server:latest
build: build:
context: ../../web context: ../../web
dockerfile: Dockerfile dockerfile: Dockerfile
@ -70,7 +70,6 @@ services:
- NEXT_PUBLIC_NEGATIVE_PREDEFINED_FEEDBACK_OPTIONS=${NEXT_PUBLIC_NEGATIVE_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_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT:-}
- NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-} - NEXT_PUBLIC_THEME=${NEXT_PUBLIC_THEME:-}
- NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES=${NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES:-true}
depends_on: depends_on:
- api_server - api_server
restart: always restart: always

View File

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

View File

@ -14,9 +14,9 @@ spec:
spec: spec:
containers: containers:
- name: background - name: background
image: danswer/danswer-ee-backend:latest image: danswer/danswer-backend:latest
imagePullPolicy: IfNotPresent 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 some extra values since this is shared between services
# There are no conflicts though, extra env variables are simply ignored # There are no conflicts though, extra env variables are simply ignored
envFrom: envFrom:

View File

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

View File

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

View File

@ -9,64 +9,17 @@ const nextConfig = {
output: "standalone", output: "standalone",
swcMinify: true, swcMinify: true,
rewrites: async () => { 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 // In production, something else (nginx in the one box setup) should take
// care of this rewrite. TODO (chris): better support setups where // care of this rewrite. TODO (chris): better support setups where
// web_server and api_server are on different machines. // 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 [ return [
{ {
source: "/api/:path*", source: "/api/:path*",
destination: "http://127.0.0.1:8080/:path*", // Proxy to Backend destination: "http://127.0.0.1:8080/:path*", // Proxy to Backend
}, },
].concat(eeRedirects); ];
}, },
redirects: async () => { redirects: async () => {
// In production, something else (nginx in the one box setup) should take // In production, something else (nginx in the one box setup) should take

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
import { NotebookIcon } from "@/components/icons/icons"; import { NotebookIcon } from "@/components/icons/icons";
import { getWebVersion, getBackendVersion } from "@/lib/version"; import { getWebVersion, getBackendVersion } from "@/lib/version";
import { EE_ENABLED } from "@/lib/constants";
const Page = async () => { const Page = async () => {
let web_version: string | null = null; let web_version: string | null = null;
@ -22,9 +21,6 @@ const Page = async () => {
</div> </div>
<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"> <div className="flex mb-2">
<p className="my-auto mr-1">Backend Version: </p> <p className="my-auto mr-1">Backend Version: </p>
<p className="text-base my-auto text-slate-400 italic"> <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 { mutate } from "swr";
import { usePopup } from "@/components/admin/connectors/Popup"; import { usePopup } from "@/components/admin/connectors/Popup";
import { CreateRateLimitModal } from "./CreateRateLimitModal"; 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 BASE_URL = "/api/admin/token-rate-limits";
const GLOBAL_TOKEN_FETCH_URL = `${BASE_URL}/global`; const GLOBAL_TOKEN_FETCH_URL = `${BASE_URL}/global`;
@ -69,6 +69,8 @@ function Main() {
const [modalIsOpen, setModalIsOpen] = useState(false); const [modalIsOpen, setModalIsOpen] = useState(false);
const { popup, setPopup } = usePopup(); const { popup, setPopup } = usePopup();
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
const updateTable = (target_scope: Scope) => { const updateTable = (target_scope: Scope) => {
if (target_scope === Scope.GLOBAL) { if (target_scope === Scope.GLOBAL) {
mutate(GLOBAL_TOKEN_FETCH_URL); mutate(GLOBAL_TOKEN_FETCH_URL);
@ -120,7 +122,7 @@ function Main() {
token spend. token spend.
</Text> </Text>
</li> </li>
{EE_ENABLED && ( {isPaidEnterpriseFeaturesEnabled && (
<> <>
<li> <li>
<Text> <Text>
@ -150,7 +152,7 @@ function Main() {
Create a Token Rate Limit Create a Token Rate Limit
</Button> </Button>
{EE_ENABLED && ( {isPaidEnterpriseFeaturesEnabled && (
<TabGroup className="mt-6" index={tabIndex} onIndexChange={setTabIndex}> <TabGroup className="mt-6" index={tabIndex} onIndexChange={setTabIndex}>
<TabList variant="line"> <TabList variant="line">
<Tab icon={FiGlobe}>Global</Tab> <Tab icon={FiGlobe}>Global</Tab>
@ -191,7 +193,7 @@ function Main() {
</TabGroup> </TabGroup>
)} )}
{!EE_ENABLED && ( {!isPaidEnterpriseFeaturesEnabled && (
<div className="mt-6"> <div className="mt-6">
<GenericTokenRateLimitTable <GenericTokenRateLimitTable
fetchUrl={GLOBAL_TOKEN_FETCH_URL} fetchUrl={GLOBAL_TOKEN_FETCH_URL}
@ -206,7 +208,9 @@ function Main() {
setIsOpen={() => setModalIsOpen(false)} setIsOpen={() => setModalIsOpen(false)}
setPopup={setPopup} setPopup={setPopup}
onSubmit={handleSubmit} onSubmit={handleSubmit}
forSpecificScope={EE_ENABLED ? undefined : Scope.GLOBAL} forSpecificScope={
isPaidEnterpriseFeaturesEnabled ? undefined : Scope.GLOBAL
}
/> />
</div> </div>
); );

View File

@ -1,5 +1,5 @@
The DanswerAI Enterprise license (the “Enterprise License”) The DanswerAI Enterprise license (the “Enterprise License”)
Copyright (c) 2023 DanswerAI, Inc. Copyright (c) 2023-present DanswerAI, Inc.
With regard to the Danswer Software: 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({ export default async function AdminLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
if (!EE_ENABLED) { if (!SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED) {
return ( return (
<div className="flex h-screen"> <div className="flex h-screen">
<div className="mx-auto my-auto text-lg font-bold text-red-500"> <div className="mx-auto my-auto text-lg font-bold text-red-500">

View File

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

View File

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

View File

@ -1,10 +1,13 @@
import { EnterpriseSettings, Settings } from "@/app/admin/settings/interfaces"; 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"; import { fetchSS } from "@/lib/utilsSS";
export async function fetchSettingsSS() { export async function fetchSettingsSS() {
const tasks = [fetchSS("/settings")]; const tasks = [fetchSS("/settings")];
if (EE_ENABLED) { if (SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED) {
tasks.push(fetchSS("/enterprise-settings")); tasks.push(fetchSS("/enterprise-settings"));
if (CUSTOM_ANALYTICS_ENABLED) { if (CUSTOM_ANALYTICS_ENABLED) {
tasks.push(fetchSS("/enterprise-settings/custom-analytics-script")); 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 = export const LOGOUT_DISABLED =
process.env.NEXT_PUBLIC_DISABLE_LOGOUT?.toLowerCase() === "true"; process.env.NEXT_PUBLIC_DISABLE_LOGOUT?.toLowerCase() === "true";
// NOTE: since this is a `NEXT_PUBLIC_` variable, it will be set at /* Enterprise-only settings */
// build-time
// TODO: consider moving this to an API call so that the api_server // NOTE: this should ONLY be used on the server-side. If used client side,
// can be the single source of truth // it will not be accurate (will always be false).
export const EE_ENABLED = export const SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED =
process.env.NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES?.toLowerCase() === "true"; process.env.ENABLE_PAID_EE_FEATURES?.toLowerCase() === "true";
// Enterprise-only settings
export const CUSTOM_ANALYTICS_ENABLED = process.env.CUSTOM_ANALYTICS_SECRET_KEY export const CUSTOM_ANALYTICS_ENABLED = process.env.CUSTOM_ANALYTICS_SECRET_KEY
? true ? true
: false; : false;

View File

@ -11,10 +11,10 @@ import { errorHandlingFetcher } from "./fetcher";
import { useState } from "react"; import { useState } from "react";
import { DateRangePickerValue } from "@tremor/react"; import { DateRangePickerValue } from "@tremor/react";
import { SourceMetadata } from "./search/interfaces"; import { SourceMetadata } from "./search/interfaces";
import { EE_ENABLED } from "./constants";
import { destructureValue } from "./llm/utils"; import { destructureValue } from "./llm/utils";
import { ChatSession } from "@/app/chat/interfaces"; import { ChatSession } from "@/app/chat/interfaces";
import { UsersResponse } from "./users/interfaces"; import { UsersResponse } from "./users/interfaces";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
const CREDENTIAL_URL = "/api/manage/admin/credential"; const CREDENTIAL_URL = "/api/manage/admin/credential";
@ -192,8 +192,9 @@ export const useUserGroups = (): {
refreshUserGroups: () => void; refreshUserGroups: () => void;
} => { } => {
const swrResponse = useSWR<UserGroup[]>(USER_GROUP_URL, errorHandlingFetcher); const swrResponse = useSWR<UserGroup[]>(USER_GROUP_URL, errorHandlingFetcher);
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
if (!EE_ENABLED) { if (!isPaidEnterpriseFeaturesEnabled) {
return { return {
...{ ...{
data: [], 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"); var merge = require("lodash/merge");
const baseThemes = require("./tailwind-themes/tailwind.config.js"); const baseThemes = require("./tailwind-themes/tailwind.config.js");
const customThemes = const customThemes = process.env.NEXT_PUBLIC_THEME
process.env.NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES && ? require(
process.env.NEXT_PUBLIC_THEME `./tailwind-themes/custom/${process.env.NEXT_PUBLIC_THEME}/tailwind.config.js`
? require( )
`./tailwind-themes/custom/${process.env.NEXT_PUBLIC_THEME}/tailwind.config.js` : null;
)
: null;
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = customThemes ? merge(baseThemes, customThemes) : baseThemes; module.exports = customThemes ? merge(baseThemes, customThemes) : baseThemes;