Skip to content

Commit

Permalink
Web UI integration (qdrant#2009)
Browse files Browse the repository at this point in the history
* wip: integrate UI web interface

* fmt

* Api key exclusion rule + fixes

* upd welcome url

* rollback api key in config
  • Loading branch information
generall committed Jun 19, 2023
1 parent dacbde5 commit 16ddb98
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
/consensus_test_logs
/config/local
/tls
/static
*.log
.qdrant-initialized
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ COPY --from=xx / /
# so, please, don't reorder them without prior consideration. 🥲

RUN apt-get update \
&& apt-get install -y clang lld cmake protobuf-compiler \
&& apt-get install -y clang lld cmake protobuf-compiler jq \
&& rustup component add rustfmt

# `ARG`/`ENV` pair is a workaround for `docker build` backward-compatibility.
Expand Down Expand Up @@ -89,6 +89,9 @@ RUN PATH="$PATH:/opt/mold/bin" \
&& mv target/$(xx-cargo --print-target-triple)/$PROFILE_DIR/qdrant /qdrant/qdrant


# Download and extract web UI
RUN mkdir /static ; STATIC_DIR='/static' ./tools/sync-web-ui.sh

FROM debian:11-slim AS qdrant

RUN apt-get update \
Expand All @@ -102,6 +105,7 @@ RUN mkdir -p ${APP}
COPY --from=builder /qdrant/qdrant ${APP}/qdrant
COPY --from=builder /qdrant/config ${APP}/config
COPY --from=builder /qdrant/tools/entrypoint.sh ${APP}/entrypoint.sh
COPY --from=builder /static ${APP}/static

WORKDIR ${APP}

Expand Down
1 change: 1 addition & 0 deletions config/development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ service:
http_port: 6333
# Uncomment to enable gRPC:
#grpc_port: 6334
#api_key: your_secret_api_key_here


storage:
Expand Down
14 changes: 13 additions & 1 deletion src/actix/api_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ use futures_util::future::LocalBoxFuture;

pub struct ApiKey {
api_key: String,
skip_prefixes: Vec<String>,
}

impl ApiKey {
pub fn new(api_key: &str) -> Self {
pub fn new(api_key: &str, skip_prefixes: Vec<String>) -> Self {
Self {
api_key: api_key.to_string(),
skip_prefixes,
}
}
}
Expand All @@ -33,13 +35,15 @@ where
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(ApiKeyMiddleware {
api_key: self.api_key.clone(),
skip_prefixes: self.skip_prefixes.clone(),
service,
}))
}
}

pub struct ApiKeyMiddleware<S> {
api_key: String,
skip_prefixes: Vec<String>,
service: S,
}

Expand All @@ -56,6 +60,14 @@ where
forward_ready!(service);

fn call(&self, req: ServiceRequest) -> Self::Future {
if self
.skip_prefixes
.iter()
.any(|prefix| req.path().starts_with(prefix))
{
return Box::pin(self.service.call(req));
}

if let Some(key) = req.headers().get("api-key") {
if let Ok(key) = key.to_str() {
if constant_time_eq(self.api_key.as_bytes(), key.as_bytes()) {
Expand Down
32 changes: 29 additions & 3 deletions src/actix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ use crate::actix::api_key::ApiKey;
use crate::common::telemetry::TelemetryCollector;
use crate::settings::{max_web_workers, Settings};

const DEFAULT_STATIC_DIR: &str = "./static";
const WEB_UI_PATH: &str = "/dashboard";

#[get("/")]
pub async fn index() -> impl Responder {
HttpResponse::Ok().json(VersionInfo::default())
Expand All @@ -52,6 +55,19 @@ pub fn init(
.clone();
let telemetry_collector_data = web::Data::from(telemetry_collector);
let api_key = settings.service.api_key.clone();
let static_folder = settings
.service
.static_content_dir
.clone()
.unwrap_or(DEFAULT_STATIC_DIR.to_string());

let web_ui_enabled = settings.service.enable_static_content.unwrap_or(true);
let skip_api_key_prefixes = if web_ui_enabled {
vec![WEB_UI_PATH.to_string()]
} else {
vec![]
};

let mut server = HttpServer::new(move || {
let cors = Cors::default()
.allow_any_origin()
Expand All @@ -65,13 +81,16 @@ pub fn init(
.limit(settings.service.max_request_size_mb * 1024 * 1024)
.error_handler(|err, rec| validation_error_handler("JSON body", err, rec));

App::new()
let mut app = App::new()
.wrap(Compress::default()) // Reads the `Accept-Encoding` header to negotiate which compression codec to use.
// api_key middleware
// note: the last call to `wrap()` or `wrap_fn()` is executed first
.wrap(Condition::new(
api_key.is_some(),
ApiKey::new(&api_key.clone().unwrap_or_default()),
ApiKey::new(
&api_key.clone().unwrap_or_default(),
skip_api_key_prefixes.clone(),
),
))
.wrap(Condition::new(settings.service.enable_cors, cors))
.wrap(Logger::default().exclude("/")) // Avoid logging healthcheck requests
Expand All @@ -97,7 +116,14 @@ pub fn init(
.service(get_point)
.service(get_points)
.service(scroll_points)
.service(count_points)
.service(count_points);

if web_ui_enabled {
app = app.service(
actix_files::Files::new(WEB_UI_PATH, &static_folder).index_file("index.html"),
)
}
app
})
.workers(max_web_workers(&settings));

Expand Down
13 changes: 9 additions & 4 deletions src/greeting.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::env;

use api::grpc::models::VersionInfo;
use atty::Stream;
use colored::{Color, ColoredString, Colorize};

use crate::settings::Settings;

fn paint(text: &str, true_color: bool) -> ColoredString {
if true_color {
text.bold().truecolor(184, 20, 56)
Expand All @@ -14,7 +15,7 @@ fn paint(text: &str, true_color: bool) -> ColoredString {

/// Prints welcome message
#[rustfmt::skip]
pub fn welcome() {
pub fn welcome(settings: &Settings) {
if !atty::is(Stream::Stdout) {
colored::control::set_override(false);
}
Expand All @@ -35,7 +36,11 @@ pub fn welcome() {
println!("{}", paint(r#" \__, |\__,_|_| \__,_|_| |_|\__| "#, true_color));
println!("{}", paint(r#" |_| "#, true_color));
println!();
let ui_link = format!("https://ui.qdrant.tech/?v=v{}", VersionInfo::default().minor_version());
let ui_link = format!(
"http{}://localhost:{}/dashboard",
if settings.service.enable_tls { "s" } else { "" },
settings.service.http_port
);

println!("{} {}",
"Access web UI at".truecolor(134, 186, 144),
Expand All @@ -49,6 +54,6 @@ mod tests {

#[test]
fn test_welcome() {
welcome()
welcome(&Settings::new(None).unwrap());
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ fn main() -> anyhow::Result<()> {

segment::madvise::set_global(settings.storage.mmap_advice);

welcome();
welcome(&settings);

if let Some(recovery_warning) = &settings.storage.recovery_mode {
log::warn!("Qdrant is loaded in recovery mode: {}", recovery_warning);
Expand Down
9 changes: 9 additions & 0 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ pub struct ServiceConfig {
#[serde(default)]
pub verify_https_client_certificate: bool,
pub api_key: Option<String>,
/// Directory where static files are served from.
/// For example, the Web-UI should be placed here.
#[serde(default)]
pub static_content_dir: Option<String>,

/// If serving of the static content is enabled.
/// This includes the Web-UI. True by default.
#[serde(default)]
pub enable_static_content: Option<bool>,
}

#[derive(Debug, Deserialize, Clone, Default, Validate)]
Expand Down
20 changes: 20 additions & 0 deletions tools/sync-web-ui.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash

set -euo pipefail

STATIC_DIR=${STATIC_DIR:-"./static"}
OPENAPI_FILE=${OPENAPI_DIR:-"./docs/redoc/master/openapi.json"}

# Download `dist.zip` from the latest release of https://github.com/qdrant/qdrant-web-ui and unzip given folder

# Get latest dist.zip, assume jq is installed
DOWNLOAD_LINK=$(curl --silent "https://api.github.com/repos/qdrant/qdrant-web-ui/releases/latest" | jq -r '.assets[] | select(.name=="dist-qdrant.zip") | .browser_download_url')

wget -O dist-qdrant.zip $DOWNLOAD_LINK

rm -rf "${STATIC_DIR}/"*
unzip -o dist-qdrant.zip -d "${STATIC_DIR}"
cp -r "${STATIC_DIR}/dist/"* "${STATIC_DIR}"
rm -rf "${STATIC_DIR}/dist"

cp "${OPENAPI_FILE}" "${STATIC_DIR}/openapi.json"

0 comments on commit 16ddb98

Please sign in to comment.