initial health check

This commit is contained in:
Weves
2023-05-19 12:20:04 -07:00
committed by Chris Weaver
parent 16dd429826
commit 544ba8f50d
21 changed files with 131 additions and 32 deletions

View File

@@ -1,7 +1,8 @@
FROM python:3.11-slim-bullseye FROM python:3.11-slim-bullseye
RUN apt-get update \ 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/* && rm -rf /var/lib/apt/lists/*
COPY ./requirements/default.txt /tmp/requirements.txt COPY ./requirements/default.txt /tmp/requirements.txt

View File

@@ -1,7 +1,8 @@
FROM python:3.11-slim-bullseye FROM python:3.11-slim-bullseye
RUN apt-get update \ 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/* && rm -rf /var/lib/apt/lists/*
COPY ./requirements/default.txt /tmp/requirements.txt COPY ./requirements/default.txt /tmp/requirements.txt

View File

@@ -13,6 +13,7 @@ from danswer.configs.app_configs import WEB_DOMAIN
from danswer.datastores.qdrant.indexing import list_collections from danswer.datastores.qdrant.indexing import list_collections
from danswer.server.admin import router as admin_router from danswer.server.admin import router as admin_router
from danswer.server.event_loading import router as event_processing_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.server.search_backend import router as backend_router
from danswer.utils.logging import setup_logger from danswer.utils.logging import setup_logger
from fastapi import FastAPI from fastapi import FastAPI
@@ -39,6 +40,7 @@ def get_application() -> FastAPI:
application.include_router(backend_router) application.include_router(backend_router)
application.include_router(event_processing_router) application.include_router(event_processing_router)
application.include_router(admin_router) application.include_router(admin_router)
application.include_router(health_router)
application.include_router( application.include_router(
fastapi_users.get_auth_router(auth_backend), fastapi_users.get_auth_router(auth_backend),

View File

@@ -21,7 +21,9 @@ from danswer.dynamic_configs.interface import ConfigNotFoundError
from danswer.server.models import AuthStatus from danswer.server.models import AuthStatus
from danswer.server.models import AuthUrl from danswer.server.models import AuthUrl
from danswer.server.models import GDriveCallback from danswer.server.models import GDriveCallback
from danswer.server.models import IndexAttemptRequest
from danswer.server.models import IndexAttemptSnapshot from danswer.server.models import IndexAttemptSnapshot
from danswer.server.models import ListIndexAttemptsResponse
from danswer.utils.logging import setup_logger from danswer.utils.logging import setup_logger
from fastapi import APIRouter from fastapi import APIRouter
from fastapi import Depends from fastapi import Depends
@@ -69,11 +71,6 @@ def modify_slack_config(
update_slack_config(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) @router.post("/connectors/{source}/index-attempt", status_code=201)
def index( def index(
source: DocumentSource, source: DocumentSource,
@@ -100,10 +97,6 @@ def index(
) )
class ListIndexAttemptsResponse(BaseModel):
index_attempts: list[IndexAttemptSnapshot]
@router.get("/connectors/{source}/index-attempt") @router.get("/connectors/{source}/index-attempt")
def list_index_attempts( def list_index_attempts(
source: DocumentSource, source: DocumentSource,

View File

@@ -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"}

View File

@@ -1,12 +1,18 @@
from datetime import datetime from datetime import datetime
from typing import Any from typing import Any
from typing import Literal
from danswer.configs.constants import DocumentSource from danswer.configs.constants import DocumentSource
from danswer.connectors.models import InputType
from danswer.datastores.interfaces import DatastoreFilter from danswer.datastores.interfaces import DatastoreFilter
from danswer.db.models import IndexingStatus from danswer.db.models import IndexingStatus
from pydantic import BaseModel from pydantic import BaseModel
class HealthCheckResponse(BaseModel):
status: Literal["ok"]
class AuthStatus(BaseModel): class AuthStatus(BaseModel):
authenticated: bool authenticated: bool
@@ -51,6 +57,11 @@ class UserByEmail(BaseModel):
user_email: str user_email: str
class IndexAttemptRequest(BaseModel):
input_type: InputType = InputType.PULL
connector_specific_config: dict[str, Any]
class IndexAttemptSnapshot(BaseModel): class IndexAttemptSnapshot(BaseModel):
connector_specific_config: dict[str, Any] connector_specific_config: dict[str, Any]
status: IndexingStatus status: IndexingStatus
@@ -60,5 +71,5 @@ class IndexAttemptSnapshot(BaseModel):
docs_indexed: int docs_indexed: int
class ListWebsiteIndexAttemptsResponse(BaseModel): class ListIndexAttemptsResponse(BaseModel):
index_attempts: list[IndexAttemptSnapshot] index_attempts: list[IndexAttemptSnapshot]

View File

@@ -35,7 +35,7 @@ services:
web_server: web_server:
build: build:
context: ../web context: ../web
dockerfile: Dockerfile dockerfile: Dockerfile.dev
depends_on: depends_on:
- api_server - api_server
restart: always restart: always
@@ -43,7 +43,6 @@ services:
- .env - .env
environment: environment:
- INTERNAL_URL=http://api_server:8080 - INTERNAL_URL=http://api_server:8080
- NODE_ENV=development
ports: ports:
- "3000:3000" - "3000:3000"
relational_db: relational_db:
@@ -59,6 +58,8 @@ services:
vector_db: vector_db:
image: qdrant/qdrant:v1.1.3 image: qdrant/qdrant:v1.1.3
restart: always restart: always
ports:
- "6333:6333"
volumes: volumes:
- qdrant_volume:/qdrant/storage - qdrant_volume:/qdrant/storage
volumes: volumes:

View File

@@ -33,7 +33,7 @@ services:
web_server: web_server:
build: build:
context: ../web context: ../web
dockerfile: Dockerfile dockerfile: Dockerfile.prod
depends_on: depends_on:
- api_server - api_server
restart: always restart: always
@@ -41,7 +41,6 @@ services:
- .env - .env
environment: environment:
- INTERNAL_URL=http://api_server:8080 - INTERNAL_URL=http://api_server:8080
- NODE_ENV=production
relational_db: relational_db:
image: postgres:15.2-alpine image: postgres:15.2-alpine
restart: always restart: always

32
web/Dockerfile.dev Normal file
View File

@@ -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

View File

@@ -4,10 +4,14 @@ import * as Yup from "yup";
import { IndexForm } from "@/components/admin/connectors/Form"; import { IndexForm } from "@/components/admin/connectors/Form";
import { GithubIcon } from "@/components/icons/icons"; import { GithubIcon } from "@/components/icons/icons";
import { TextFormField } from "@/components/admin/connectors/Field"; import { TextFormField } from "@/components/admin/connectors/Field";
import { HealthCheckBanner } from "@/components/health/healthcheck";
export default function Page() { export default function Page() {
return ( return (
<div className="mx-auto"> <div className="mx-auto">
<div className="mb-4">
<HealthCheckBanner />
</div>
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex"> <div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
<GithubIcon size="32" /> <GithubIcon size="32" />
<h1 className="text-3xl font-bold pl-2">Github PRs</h1> <h1 className="text-3xl font-bold pl-2">Github PRs</h1>

View File

@@ -1,5 +1,5 @@
import { getDomain } from "@/lib/redirectSS"; import { getDomain } from "@/lib/redirectSS";
import { buildUrl } from "@/lib/userSS"; import { buildUrl } from "@/lib/utilsSS";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
export const GET = async (request: NextRequest) => { export const GET = async (request: NextRequest) => {

View File

@@ -16,6 +16,7 @@ import { LoadingAnimation } from "@/components/Loading";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Popup } from "@/components/admin/connectors/Popup"; import { Popup } from "@/components/admin/connectors/Popup";
import { useState } from "react"; import { useState } from "react";
import { HealthCheckBanner } from "@/components/health/healthcheck";
export default function Page() { export default function Page() {
const router = useRouter(); const router = useRouter();
@@ -43,9 +44,14 @@ export default function Page() {
} | null>(null); } | null>(null);
const header = ( const header = (
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex"> <div>
<GoogleDriveIcon size="32" /> <div className="mb-4">
<h1 className="text-3xl font-bold pl-2">Google Drive</h1> <HealthCheckBanner />
</div>
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
<GoogleDriveIcon size="32" />
<h1 className="text-3xl font-bold pl-2">Google Drive</h1>
</div>
</div> </div>
); );

View File

@@ -7,6 +7,7 @@ import { SlackConfig } from "../../../../components/admin/connectors/types";
import { LoadingAnimation } from "@/components/Loading"; import { LoadingAnimation } from "@/components/Loading";
import { InitialSetupForm } from "./InitialSetupForm"; import { InitialSetupForm } from "./InitialSetupForm";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { HealthCheckBanner } from "@/components/health/healthcheck";
const MainSection = () => { const MainSection = () => {
// TODO: add back in once this is ready // TODO: add back in once this is ready
@@ -29,9 +30,7 @@ const MainSection = () => {
</div> </div>
); );
} else if (error || !data) { } else if (error || !data) {
return ( return <div>{`Error loading Slack config - ${error}`}</div>;
<div className="mt-16">{`Error loading Slack config - ${error}`}</div>
);
} }
return ( return (
@@ -62,6 +61,9 @@ const MainSection = () => {
export default function Page() { export default function Page() {
return ( return (
<div className="mx-auto"> <div className="mx-auto">
<div className="mb-4">
<HealthCheckBanner />
</div>
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex"> <div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
<SlackIcon size="32" /> <SlackIcon size="32" />
<h1 className="text-3xl font-bold pl-2">Slack</h1> <h1 className="text-3xl font-bold pl-2">Slack</h1>

View File

@@ -15,6 +15,7 @@ import {
import { IndexForm } from "@/components/admin/connectors/Form"; import { IndexForm } from "@/components/admin/connectors/Form";
import { TextFormField } from "@/components/admin/connectors/Field"; import { TextFormField } from "@/components/admin/connectors/Field";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { HealthCheckBanner } from "@/components/health/healthcheck";
const COLUMNS = [ const COLUMNS = [
{ header: "Base URL", key: "url" }, { header: "Base URL", key: "url" },
@@ -54,6 +55,9 @@ export default function Web() {
return ( return (
<div className="mx-auto"> <div className="mx-auto">
<div className="mb-4">
<HealthCheckBanner />
</div>
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex"> <div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
<GlobeIcon size="32" /> <GlobeIcon size="32" />
<h1 className="text-3xl font-bold pl-2">Web</h1> <h1 className="text-3xl font-bold pl-2">Web</h1>

View File

@@ -16,6 +16,7 @@ import { CheckCircle, XCircle } from "@phosphor-icons/react";
import { submitIndexRequest } from "@/components/admin/connectors/Form"; import { submitIndexRequest } from "@/components/admin/connectors/Form";
import { useState } from "react"; import { useState } from "react";
import { Popup } from "@/components/admin/connectors/Popup"; import { Popup } from "@/components/admin/connectors/Popup";
import { HealthCheckBanner } from "@/components/health/healthcheck";
const getModifiedSource = (indexAttempt: IndexAttempt) => { const getModifiedSource = (indexAttempt: IndexAttempt) => {
return indexAttempt.source === "web" return indexAttempt.source === "web"
@@ -63,6 +64,9 @@ export default function Status() {
return ( return (
<div className="mx-auto"> <div className="mx-auto">
{popup && <Popup message={popup.message} type={popup.type} />} {popup && <Popup message={popup.message} type={popup.type} />}
<div className="mb-4">
<HealthCheckBanner />
</div>
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex"> <div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
<NotebookIcon size="32" /> <NotebookIcon size="32" />
<h1 className="text-3xl font-bold pl-2">Indexing Status</h1> <h1 className="text-3xl font-bold pl-2">Indexing Status</h1>

View File

@@ -1,5 +1,5 @@
import { getDomain } from "@/lib/redirectSS"; import { getDomain } from "@/lib/redirectSS";
import { buildUrl } from "@/lib/userSS"; import { buildUrl } from "@/lib/utilsSS";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
export const GET = async (request: NextRequest) => { export const GET = async (request: NextRequest) => {

View File

@@ -3,6 +3,7 @@ import { Header } from "@/components/Header";
import { getCurrentUserSS } from "@/lib/userSS"; import { getCurrentUserSS } from "@/lib/userSS";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { DISABLE_AUTH } from "@/lib/constants"; import { DISABLE_AUTH } from "@/lib/constants";
import { HealthCheckBanner } from "@/components/health/healthcheck";
export default async function Home() { export default async function Home() {
let user = null; let user = null;
@@ -15,6 +16,9 @@ export default async function Home() {
return ( return (
<> <>
<Header user={user} /> <Header user={user} />
<div className="m-3">
<HealthCheckBanner />
</div>
<div className="px-24 pt-10 flex flex-col items-center min-h-screen bg-gray-900 text-gray-100"> <div className="px-24 pt-10 flex flex-col items-center min-h-screen bg-gray-900 text-gray-100">
<div className="max-w-[800px] w-full"> <div className="max-w-[800px] w-full">
<SearchSection /> <SearchSection />

View File

@@ -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 (
<div className="text-xs mx-auto bg-gradient-to-r from-red-900 to-red-700 p-2 rounded-sm border-hidden text-gray-300">
<p className="font-bold pb-1">The backend is currently unavailable.</p>
<p className="px-1">
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.
</p>
</div>
);
};

View File

@@ -1,13 +1,6 @@
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { User } from "./types"; import { User } from "./types";
import { INTERNAL_URL } from "./constants"; import { buildUrl } from "./utilsSS";
export const buildUrl = (path: string) => {
if (path.startsWith("/")) {
return `${INTERNAL_URL}${path}`;
}
return `${INTERNAL_URL}/${path}`;
};
export const getGoogleOAuthUrlSS = async (): Promise<string> => { export const getGoogleOAuthUrlSS = async (): Promise<string> => {
const res = await fetch(buildUrl("/auth/google/authorize")); const res = await fetch(buildUrl("/auth/google/authorize"));

8
web/src/lib/utilsSS.ts Normal file
View File

@@ -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}`;
};