From 544ba8f50d4d8f8aed9fa49cadba0864851afec0 Mon Sep 17 00:00:00 2001 From: Weves Date: Fri, 19 May 2023 12:20:04 -0700 Subject: [PATCH] initial health check --- backend/Dockerfile | 3 +- backend/Dockerfile.background | 3 +- backend/danswer/main.py | 2 ++ backend/danswer/server/admin.py | 11 ++----- backend/danswer/server/health.py | 10 ++++++ backend/danswer/server/models.py | 13 +++++++- deployment/docker-compose.dev.yml | 5 +-- deployment/docker-compose.prod.yml | 3 +- web/Dockerfile.dev | 32 +++++++++++++++++++ web/{Dockerfile => Dockerfile.prod} | 0 web/src/app/admin/connectors/github/page.tsx | 4 +++ .../google-drive/auth/callback/route.ts | 2 +- .../admin/connectors/google-drive/page.tsx | 12 +++++-- web/src/app/admin/connectors/slack/page.tsx | 8 +++-- web/src/app/admin/connectors/web/page.tsx | 4 +++ web/src/app/admin/indexing/status/page.tsx | 4 +++ web/src/app/auth/google/callback/route.ts | 2 +- web/src/app/page.tsx | 4 +++ web/src/components/health/healthcheck.tsx | 24 ++++++++++++++ web/src/lib/userSS.ts | 9 +----- web/src/lib/utilsSS.ts | 8 +++++ 21 files changed, 131 insertions(+), 32 deletions(-) create mode 100644 backend/danswer/server/health.py create mode 100644 web/Dockerfile.dev rename web/{Dockerfile => Dockerfile.prod} (100%) create mode 100644 web/src/components/health/healthcheck.tsx create mode 100644 web/src/lib/utilsSS.ts diff --git a/backend/Dockerfile b/backend/Dockerfile index d87325f7dbfa..ece2915082c7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,7 +1,8 @@ FROM python:3.11-slim-bullseye RUN apt-get update \ - && apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler libprotobuf-dev libgoogle-perftools-dev libpq-dev build-essential \ + && apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler \ + libprotobuf-dev libgoogle-perftools-dev libpq-dev build-essential curl \ && rm -rf /var/lib/apt/lists/* COPY ./requirements/default.txt /tmp/requirements.txt diff --git a/backend/Dockerfile.background b/backend/Dockerfile.background index 4b54c8827926..ebd28293aae4 100644 --- a/backend/Dockerfile.background +++ b/backend/Dockerfile.background @@ -1,7 +1,8 @@ FROM python:3.11-slim-bullseye RUN apt-get update \ - && apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler libprotobuf-dev libgoogle-perftools-dev libpq-dev build-essential cron \ + && apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler \ + libprotobuf-dev libgoogle-perftools-dev libpq-dev build-essential cron curl \ && rm -rf /var/lib/apt/lists/* COPY ./requirements/default.txt /tmp/requirements.txt diff --git a/backend/danswer/main.py b/backend/danswer/main.py index 3d4694327060..b361b155f69c 100644 --- a/backend/danswer/main.py +++ b/backend/danswer/main.py @@ -13,6 +13,7 @@ from danswer.configs.app_configs import WEB_DOMAIN from danswer.datastores.qdrant.indexing import list_collections from danswer.server.admin import router as admin_router from danswer.server.event_loading import router as event_processing_router +from danswer.server.health import router as health_router from danswer.server.search_backend import router as backend_router from danswer.utils.logging import setup_logger from fastapi import FastAPI @@ -39,6 +40,7 @@ def get_application() -> FastAPI: application.include_router(backend_router) application.include_router(event_processing_router) application.include_router(admin_router) + application.include_router(health_router) application.include_router( fastapi_users.get_auth_router(auth_backend), diff --git a/backend/danswer/server/admin.py b/backend/danswer/server/admin.py index 4248ed40274d..ebb5c2bae621 100644 --- a/backend/danswer/server/admin.py +++ b/backend/danswer/server/admin.py @@ -21,7 +21,9 @@ from danswer.dynamic_configs.interface import ConfigNotFoundError from danswer.server.models import AuthStatus from danswer.server.models import AuthUrl from danswer.server.models import GDriveCallback +from danswer.server.models import IndexAttemptRequest from danswer.server.models import IndexAttemptSnapshot +from danswer.server.models import ListIndexAttemptsResponse from danswer.utils.logging import setup_logger from fastapi import APIRouter from fastapi import Depends @@ -69,11 +71,6 @@ def modify_slack_config( update_slack_config(slack_config) -class IndexAttemptRequest(BaseModel): - input_type: InputType = InputType.PULL - connector_specific_config: dict[str, Any] - - @router.post("/connectors/{source}/index-attempt", status_code=201) def index( source: DocumentSource, @@ -100,10 +97,6 @@ def index( ) -class ListIndexAttemptsResponse(BaseModel): - index_attempts: list[IndexAttemptSnapshot] - - @router.get("/connectors/{source}/index-attempt") def list_index_attempts( source: DocumentSource, diff --git a/backend/danswer/server/health.py b/backend/danswer/server/health.py new file mode 100644 index 000000000000..a9a652783ffc --- /dev/null +++ b/backend/danswer/server/health.py @@ -0,0 +1,10 @@ +from danswer.server.models import HealthCheckResponse +from fastapi import APIRouter + + +router = APIRouter() + + +@router.get("/health") +def healthcheck() -> HealthCheckResponse: + return {"status": "ok"} diff --git a/backend/danswer/server/models.py b/backend/danswer/server/models.py index 3df71b42b7a6..05724bb7309e 100644 --- a/backend/danswer/server/models.py +++ b/backend/danswer/server/models.py @@ -1,12 +1,18 @@ from datetime import datetime from typing import Any +from typing import Literal from danswer.configs.constants import DocumentSource +from danswer.connectors.models import InputType from danswer.datastores.interfaces import DatastoreFilter from danswer.db.models import IndexingStatus from pydantic import BaseModel +class HealthCheckResponse(BaseModel): + status: Literal["ok"] + + class AuthStatus(BaseModel): authenticated: bool @@ -51,6 +57,11 @@ class UserByEmail(BaseModel): user_email: str +class IndexAttemptRequest(BaseModel): + input_type: InputType = InputType.PULL + connector_specific_config: dict[str, Any] + + class IndexAttemptSnapshot(BaseModel): connector_specific_config: dict[str, Any] status: IndexingStatus @@ -60,5 +71,5 @@ class IndexAttemptSnapshot(BaseModel): docs_indexed: int -class ListWebsiteIndexAttemptsResponse(BaseModel): +class ListIndexAttemptsResponse(BaseModel): index_attempts: list[IndexAttemptSnapshot] diff --git a/deployment/docker-compose.dev.yml b/deployment/docker-compose.dev.yml index 472817a5c4d1..01192fbcb11c 100644 --- a/deployment/docker-compose.dev.yml +++ b/deployment/docker-compose.dev.yml @@ -35,7 +35,7 @@ services: web_server: build: context: ../web - dockerfile: Dockerfile + dockerfile: Dockerfile.dev depends_on: - api_server restart: always @@ -43,7 +43,6 @@ services: - .env environment: - INTERNAL_URL=http://api_server:8080 - - NODE_ENV=development ports: - "3000:3000" relational_db: @@ -59,6 +58,8 @@ services: vector_db: image: qdrant/qdrant:v1.1.3 restart: always + ports: + - "6333:6333" volumes: - qdrant_volume:/qdrant/storage volumes: diff --git a/deployment/docker-compose.prod.yml b/deployment/docker-compose.prod.yml index 45c1978cc423..28561f3fcbb2 100644 --- a/deployment/docker-compose.prod.yml +++ b/deployment/docker-compose.prod.yml @@ -33,7 +33,7 @@ services: web_server: build: context: ../web - dockerfile: Dockerfile + dockerfile: Dockerfile.prod depends_on: - api_server restart: always @@ -41,7 +41,6 @@ services: - .env environment: - INTERNAL_URL=http://api_server:8080 - - NODE_ENV=production relational_db: image: postgres:15.2-alpine restart: always diff --git a/web/Dockerfile.dev b/web/Dockerfile.dev new file mode 100644 index 000000000000..0bcabc117e42 --- /dev/null +++ b/web/Dockerfile.dev @@ -0,0 +1,32 @@ +FROM node:18-alpine + +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ + # Allow install without lockfile, so example works even without Node.js installed locally + else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && yarn install; \ + fi + +COPY src ./src +COPY public ./public +COPY next.config.js . +COPY tsconfig.json . + +# Next.js collects completely anonymous telemetry data about general usage. Learn more here: https://nextjs.org/telemetry +# Uncomment the following line to disable telemetry at run time +ENV NEXT_TELEMETRY_DISABLED 1 + +# Note: Don't expose ports here, Compose will handle that for us + +# Start Next.js in development mode based on the preferred package manager +CMD \ + if [ -f yarn.lock ]; then yarn dev; \ + elif [ -f package-lock.json ]; then npm run dev; \ + elif [ -f pnpm-lock.yaml ]; then pnpm dev; \ + else yarn dev; \ + fi \ No newline at end of file diff --git a/web/Dockerfile b/web/Dockerfile.prod similarity index 100% rename from web/Dockerfile rename to web/Dockerfile.prod diff --git a/web/src/app/admin/connectors/github/page.tsx b/web/src/app/admin/connectors/github/page.tsx index 8ea2586303ec..9212405eeb9d 100644 --- a/web/src/app/admin/connectors/github/page.tsx +++ b/web/src/app/admin/connectors/github/page.tsx @@ -4,10 +4,14 @@ import * as Yup from "yup"; import { IndexForm } from "@/components/admin/connectors/Form"; import { GithubIcon } from "@/components/icons/icons"; import { TextFormField } from "@/components/admin/connectors/Field"; +import { HealthCheckBanner } from "@/components/health/healthcheck"; export default function Page() { return (
+
+ +

Github PRs

diff --git a/web/src/app/admin/connectors/google-drive/auth/callback/route.ts b/web/src/app/admin/connectors/google-drive/auth/callback/route.ts index 637eeac704d8..1d2ba2c65b15 100644 --- a/web/src/app/admin/connectors/google-drive/auth/callback/route.ts +++ b/web/src/app/admin/connectors/google-drive/auth/callback/route.ts @@ -1,5 +1,5 @@ import { getDomain } from "@/lib/redirectSS"; -import { buildUrl } from "@/lib/userSS"; +import { buildUrl } from "@/lib/utilsSS"; import { NextRequest, NextResponse } from "next/server"; export const GET = async (request: NextRequest) => { diff --git a/web/src/app/admin/connectors/google-drive/page.tsx b/web/src/app/admin/connectors/google-drive/page.tsx index 9414db71225b..f7e0ffaf0594 100644 --- a/web/src/app/admin/connectors/google-drive/page.tsx +++ b/web/src/app/admin/connectors/google-drive/page.tsx @@ -16,6 +16,7 @@ import { LoadingAnimation } from "@/components/Loading"; import { useRouter } from "next/navigation"; import { Popup } from "@/components/admin/connectors/Popup"; import { useState } from "react"; +import { HealthCheckBanner } from "@/components/health/healthcheck"; export default function Page() { const router = useRouter(); @@ -43,9 +44,14 @@ export default function Page() { } | null>(null); const header = ( -
- -

Google Drive

+
+
+ +
+
+ +

Google Drive

+
); diff --git a/web/src/app/admin/connectors/slack/page.tsx b/web/src/app/admin/connectors/slack/page.tsx index 4f4a6724b10d..2040cd4a364b 100644 --- a/web/src/app/admin/connectors/slack/page.tsx +++ b/web/src/app/admin/connectors/slack/page.tsx @@ -7,6 +7,7 @@ import { SlackConfig } from "../../../../components/admin/connectors/types"; import { LoadingAnimation } from "@/components/Loading"; import { InitialSetupForm } from "./InitialSetupForm"; import { useRouter } from "next/navigation"; +import { HealthCheckBanner } from "@/components/health/healthcheck"; const MainSection = () => { // TODO: add back in once this is ready @@ -29,9 +30,7 @@ const MainSection = () => {
); } else if (error || !data) { - return ( -
{`Error loading Slack config - ${error}`}
- ); + return
{`Error loading Slack config - ${error}`}
; } return ( @@ -62,6 +61,9 @@ const MainSection = () => { export default function Page() { return (
+
+ +

Slack

diff --git a/web/src/app/admin/connectors/web/page.tsx b/web/src/app/admin/connectors/web/page.tsx index 7866c6910152..4770e7f3505b 100644 --- a/web/src/app/admin/connectors/web/page.tsx +++ b/web/src/app/admin/connectors/web/page.tsx @@ -15,6 +15,7 @@ import { import { IndexForm } from "@/components/admin/connectors/Form"; import { TextFormField } from "@/components/admin/connectors/Field"; import { useRouter } from "next/navigation"; +import { HealthCheckBanner } from "@/components/health/healthcheck"; const COLUMNS = [ { header: "Base URL", key: "url" }, @@ -54,6 +55,9 @@ export default function Web() { return (
+
+ +

Web

diff --git a/web/src/app/admin/indexing/status/page.tsx b/web/src/app/admin/indexing/status/page.tsx index 3c8cd3fc317e..d0e7caf5b4b0 100644 --- a/web/src/app/admin/indexing/status/page.tsx +++ b/web/src/app/admin/indexing/status/page.tsx @@ -16,6 +16,7 @@ import { CheckCircle, XCircle } from "@phosphor-icons/react"; import { submitIndexRequest } from "@/components/admin/connectors/Form"; import { useState } from "react"; import { Popup } from "@/components/admin/connectors/Popup"; +import { HealthCheckBanner } from "@/components/health/healthcheck"; const getModifiedSource = (indexAttempt: IndexAttempt) => { return indexAttempt.source === "web" @@ -63,6 +64,9 @@ export default function Status() { return (
{popup && } +
+ +

Indexing Status

diff --git a/web/src/app/auth/google/callback/route.ts b/web/src/app/auth/google/callback/route.ts index b69345bb52c6..a554d89e62cd 100644 --- a/web/src/app/auth/google/callback/route.ts +++ b/web/src/app/auth/google/callback/route.ts @@ -1,5 +1,5 @@ import { getDomain } from "@/lib/redirectSS"; -import { buildUrl } from "@/lib/userSS"; +import { buildUrl } from "@/lib/utilsSS"; import { NextRequest, NextResponse } from "next/server"; export const GET = async (request: NextRequest) => { diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index c396a3a22473..5569ac4f3c37 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -3,6 +3,7 @@ import { Header } from "@/components/Header"; import { getCurrentUserSS } from "@/lib/userSS"; import { redirect } from "next/navigation"; import { DISABLE_AUTH } from "@/lib/constants"; +import { HealthCheckBanner } from "@/components/health/healthcheck"; export default async function Home() { let user = null; @@ -15,6 +16,9 @@ export default async function Home() { return ( <>
+
+ +
diff --git a/web/src/components/health/healthcheck.tsx b/web/src/components/health/healthcheck.tsx new file mode 100644 index 000000000000..9dba756e2bf7 --- /dev/null +++ b/web/src/components/health/healthcheck.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { fetcher } from "@/lib/fetcher"; +import useSWR from "swr"; + +export const HealthCheckBanner = () => { + const { error } = useSWR("/api/health", fetcher); + if (!error) { + return null; + } + + return ( +
+

The backend is currently unavailable.

+ +

+ If this is your initial setup or you just updated your Danswer + deployment, this is likely because the backend is still starting up. + Give it a minute or two, and then refresh the page. If that does not + work, make sure the backend is setup and/or contact an administrator. +

+
+ ); +}; diff --git a/web/src/lib/userSS.ts b/web/src/lib/userSS.ts index 628cb5a2dbc0..2006ebe51bfe 100644 --- a/web/src/lib/userSS.ts +++ b/web/src/lib/userSS.ts @@ -1,13 +1,6 @@ import { cookies } from "next/headers"; import { User } from "./types"; -import { INTERNAL_URL } from "./constants"; - -export const buildUrl = (path: string) => { - if (path.startsWith("/")) { - return `${INTERNAL_URL}${path}`; - } - return `${INTERNAL_URL}/${path}`; -}; +import { buildUrl } from "./utilsSS"; export const getGoogleOAuthUrlSS = async (): Promise => { const res = await fetch(buildUrl("/auth/google/authorize")); diff --git a/web/src/lib/utilsSS.ts b/web/src/lib/utilsSS.ts new file mode 100644 index 000000000000..d75b0d3fdf67 --- /dev/null +++ b/web/src/lib/utilsSS.ts @@ -0,0 +1,8 @@ +import { INTERNAL_URL } from "./constants"; + +export const buildUrl = (path: string) => { + if (path.startsWith("/")) { + return `${INTERNAL_URL}${path}`; + } + return `${INTERNAL_URL}/${path}`; +};