From 7ea554a7fd315dd6f91b9cbdc12c36fdf50ede9e Mon Sep 17 00:00:00 2001 From: wendrul <53628737+wendrul@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:13:27 +0200 Subject: [PATCH] feat: Full-text search on runs using tantivy and command palette for quick actions (#4046) * Add indexer crate and files * POC searcher incomplete schema only indexes at startup * POC search component frontend * Demo of the frontend element * add Results and Args as text * minimal functionality * Make jump to scripts by name also flows and apps * Add button on sidebar to open search * Update lock on indexer after merge * Make arrow key navigation compatible with scrol * Show empty result screen and log as a coming feat * Add summary to script searchable items * Catch `parts is undefined` error (uFuzzy) * Index refreshing using tokio interval * Fix JobLoader workspace being wrongly defined * Fix click outside * Add debouncing for completed run search * Binary mode working + job index tracker * Warning for no license + fix height scrollbars on content search * Make it compile without EE files * remove panic to use errors * Move global search * Cleanup UI, no more tab switcher but clear placeholders and actions * Add tantivy feature flag for windmill-api * Rework indexer mode * Mac compatibility for shortcut * Update test for new run_server * Prepare sqlx * Mac compatibility * Fix openapi yaml * Fix frontend * Frontend api fix * Update docker-compose.yml and caddyfile With the (by default deactivated) container and reverse proxy to use the windmill indexer * fix feature flag for tests * fix feature falg for running tests * fix feature flag for running tests * Make content search use search modal instead * Add tantivy feature to ee build steps * Remove old Content search * change volume location for indexer * Update dependencies * Prepare sqlx * Uncomment line on docker compose * Add line between input and results * Update ee repo ref --- .github/workflows/docker-image.yml | 4 +- Caddyfile | 1 + backend/.gitignore | 3 +- ...bfb3938cb9a440e9d53323a93ec9473417890.json | 22 + ...87589bf7c37d3557cd831787b9b16b3665e47.json | 20 + ...67910c7d050f514dfba76991b644b86b914b9.json | 23 + backend/Cargo.lock | 333 ++++++++- backend/Cargo.toml | 9 +- backend/ee-repo-ref.txt | 2 +- backend/src/main.rs | 47 +- backend/tests/worker.rs | 2 + backend/windmill-api/Cargo.toml | 3 +- backend/windmill-api/openapi.yaml | 42 ++ backend/windmill-api/src/indexer_ee.rs | 5 + backend/windmill-api/src/lib.rs | 9 + backend/windmill-common/src/utils.rs | 1 + backend/windmill-indexer/Cargo.toml | 26 + backend/windmill-indexer/src/indexer_ee.rs | 21 + backend/windmill-indexer/src/lib.rs | 1 + docker-compose.yml | 22 + .../src/lib/components/ContentSearch.svelte | 440 ------------ .../lib/components/ContentSearchInner.svelte | 379 ++++++++++ frontend/src/lib/components/Path.svelte | 11 +- .../src/lib/components/home/ItemsList.svelte | 7 +- .../search/GlobalSearchModal.svelte | 651 ++++++++++++++++++ .../components/search/QuickMenuItem.svelte | 87 +++ .../lib/components/sidebar/MenuButton.svelte | 12 + .../src/routes/(root)/(logged)/+layout.svelte | 42 +- 28 files changed, 1761 insertions(+), 464 deletions(-) create mode 100644 backend/.sqlx/query-8455e77a6a87bd0b8dbe55c854abfb3938cb9a440e9d53323a93ec9473417890.json create mode 100644 backend/.sqlx/query-919c9432f36a6212ba79b50fbf887589bf7c37d3557cd831787b9b16b3665e47.json create mode 100644 backend/.sqlx/query-f036e930beff03e8a3f1f91c32267910c7d050f514dfba76991b644b86b914b9.json create mode 100644 backend/windmill-api/src/indexer_ee.rs create mode 100644 backend/windmill-indexer/Cargo.toml create mode 100644 backend/windmill-indexer/src/indexer_ee.rs create mode 100644 backend/windmill-indexer/src/lib.rs delete mode 100644 frontend/src/lib/components/ContentSearch.svelte create mode 100644 frontend/src/lib/components/ContentSearchInner.svelte create mode 100644 frontend/src/lib/components/search/GlobalSearchModal.svelte create mode 100644 frontend/src/lib/components/search/QuickMenuItem.svelte diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 66a3b54f4d..ec65f9bb7f 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -135,7 +135,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee:dev ${{ steps.meta-ee-public.outputs.tags }} @@ -198,7 +198,7 @@ jobs: platforms: linux/amd64 push: true build-args: | - features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc + features=enterprise,enterprise_saml,stripe,embedding,parquet,prometheus,openidconnect,cloud,jemalloc,tantivy PYTHON_IMAGE=python:3.12.2-slim-bookworm tags: | ${{ steps.meta-ee-public-py312.outputs.tags }} diff --git a/Caddyfile b/Caddyfile index d455944bb4..8d3cc96265 100644 --- a/Caddyfile +++ b/Caddyfile @@ -2,6 +2,7 @@ bind {$ADDRESS} reverse_proxy /ws/* http://lsp:3001 # reverse_proxy /ws_mp/* http://multiplayer:3002 + # reverse_proxy /api/srch/* http://windmill_indexer:8001 reverse_proxy /* http://windmill_server:8000 # tls /certs/cert.pem /certs/key.pem } diff --git a/backend/.gitignore b/backend/.gitignore index 93b534e68c..7900ae5fc2 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -4,4 +4,5 @@ oauth.json oauth2.json windmill-api/openapi-deref.yaml tracing.folded -heaptrack* \ No newline at end of file +heaptrack* +index/ diff --git a/backend/.sqlx/query-8455e77a6a87bd0b8dbe55c854abfb3938cb9a440e9d53323a93ec9473417890.json b/backend/.sqlx/query-8455e77a6a87bd0b8dbe55c854abfb3938cb9a440e9d53323a93ec9473417890.json new file mode 100644 index 0000000000..2536c8afc9 --- /dev/null +++ b/backend/.sqlx/query-8455e77a6a87bd0b8dbe55c854abfb3938cb9a440e9d53323a93ec9473417890.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id FROM queue WHERE created_at <= $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Timestamptz" + ] + }, + "nullable": [ + false + ] + }, + "hash": "8455e77a6a87bd0b8dbe55c854abfb3938cb9a440e9d53323a93ec9473417890" +} diff --git a/backend/.sqlx/query-919c9432f36a6212ba79b50fbf887589bf7c37d3557cd831787b9b16b3665e47.json b/backend/.sqlx/query-919c9432f36a6212ba79b50fbf887589bf7c37d3557cd831787b9b16b3665e47.json new file mode 100644 index 0000000000..54ad46ae71 --- /dev/null +++ b/backend/.sqlx/query-919c9432f36a6212ba79b50fbf887589bf7c37d3557cd831787b9b16b3665e47.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT MAX(created_at) FROM completed_job", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "max", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "919c9432f36a6212ba79b50fbf887589bf7c37d3557cd831787b9b16b3665e47" +} diff --git a/backend/.sqlx/query-f036e930beff03e8a3f1f91c32267910c7d050f514dfba76991b644b86b914b9.json b/backend/.sqlx/query-f036e930beff03e8a3f1f91c32267910c7d050f514dfba76991b644b86b914b9.json new file mode 100644 index 0000000000..e8c48b6add --- /dev/null +++ b/backend/.sqlx/query-f036e930beff03e8a3f1f91c32267910c7d050f514dfba76991b644b86b914b9.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT CONCAT(coalesce(completed_job.logs, ''), coalesce(job_logs.logs, '')) as logs FROM completed_job\n LEFT JOIN job_logs ON job_logs.job_id = completed_job.id\n WHERE completed_job.id = $1 AND completed_job.workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "logs", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "f036e930beff03e8a3f1f91c32267910c7d050f514dfba76991b644b86b914b9" +} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 9639754793..b27f7d1e20 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -175,6 +175,12 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "archiver-rs" version = "0.5.1" @@ -1304,6 +1310,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bitpacking" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c1d3e2bfd8d06048a179f7b17afc3188effa10385e7b00dc65af6aae732ea92" +dependencies = [ + "crunchy", +] + [[package]] name = "bitvec" version = "1.0.1" @@ -1619,6 +1634,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "census" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" + [[package]] name = "cexpr" version = "0.6.0" @@ -2988,6 +3009,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dprint-swc-ext" version = "0.16.0" @@ -3209,6 +3236,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fastdivide" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59668941c55e5c186b8b58c391629af56774ec768f73c08bbcd56f09348eb00b" + [[package]] name = "fastrand" version = "1.9.0" @@ -3389,6 +3422,16 @@ dependencies = [ "syn 2.0.70", ] +[[package]] +name = "fs4" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" +dependencies = [ + "rustix 0.38.34", + "windows-sys 0.52.0", +] + [[package]] name = "fslock" version = "0.2.1" @@ -3676,6 +3719,20 @@ dependencies = [ "seq-macro", ] +[[package]] +name = "generator" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186014d53bc231d0090ef8d6f03e0920c54d85a5ed22f4f2f74315ec56cf83fb" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -4010,6 +4067,12 @@ dependencies = [ "triomphe", ] +[[package]] +name = "htmlescape" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" + [[package]] name = "http" version = "0.2.12" @@ -4244,7 +4307,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -4531,6 +4594,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "levenshtein_automata" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" + [[package]] name = "lexical-core" version = "0.8.5" @@ -4720,6 +4789,20 @@ dependencies = [ "prost-types", ] +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "pin-utils", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lru" version = "0.12.3" @@ -4929,6 +5012,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "measure_time" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbefd235b0aadd181626f281e1d684e116972988c14c264e42069d5e8a5775cc" +dependencies = [ + "instant", + "log", +] + [[package]] name = "memchr" version = "2.7.4" @@ -5035,6 +5128,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "murmurhash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" + [[package]] name = "mysql-common-derive" version = "0.31.1" @@ -5361,6 +5460,15 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oneshot" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071d1cf3298ad8e543dca18217d198cb6a3884443d204757b9624b935ef09fa0" +dependencies = [ + "loom", +] + [[package]] name = "onig" version = "6.4.0" @@ -5509,6 +5617,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "ownedbytes" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a059efb063b8f425b948e042e6b9bd85edfe60e913630ed727b23e2dfcc558" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "p256" version = "0.13.2" @@ -6881,6 +6998,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust-stemmers" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" +dependencies = [ + "serde", + "serde_derive", +] + [[package]] name = "rust_decimal" version = "1.35.0" @@ -7665,6 +7792,15 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" +dependencies = [ + "serde", +] + [[package]] name = "slab" version = "0.4.9" @@ -8644,6 +8780,147 @@ dependencies = [ "libc", ] +[[package]] +name = "tantivy" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8d0582f186c0a6d55655d24543f15e43607299425c5ad8352c242b914b31856" +dependencies = [ + "aho-corasick", + "arc-swap", + "base64 0.22.1", + "bitpacking", + "byteorder", + "census", + "crc32fast", + "crossbeam-channel", + "downcast-rs", + "fastdivide", + "fnv", + "fs4", + "htmlescape", + "itertools 0.12.1", + "levenshtein_automata", + "log", + "lru", + "lz4_flex", + "measure_time", + "memmap2", + "num_cpus", + "once_cell", + "oneshot", + "rayon", + "regex", + "rust-stemmers", + "rustc-hash", + "serde", + "serde_json", + "sketches-ddsketch", + "smallvec", + "tantivy-bitpacker", + "tantivy-columnar", + "tantivy-common", + "tantivy-fst", + "tantivy-query-grammar", + "tantivy-stacker", + "tantivy-tokenizer-api", + "tempfile", + "thiserror", + "time", + "uuid 1.10.0", + "winapi", +] + +[[package]] +name = "tantivy-bitpacker" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284899c2325d6832203ac6ff5891b297fc5239c3dc754c5bc1977855b23c10df" +dependencies = [ + "bitpacking", +] + +[[package]] +name = "tantivy-columnar" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12722224ffbe346c7fec3275c699e508fd0d4710e629e933d5736ec524a1f44e" +dependencies = [ + "downcast-rs", + "fastdivide", + "itertools 0.12.1", + "serde", + "tantivy-bitpacker", + "tantivy-common", + "tantivy-sstable", + "tantivy-stacker", +] + +[[package]] +name = "tantivy-common" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8019e3cabcfd20a1380b491e13ff42f57bb38bf97c3d5fa5c07e50816e0621f4" +dependencies = [ + "async-trait", + "byteorder", + "ownedbytes", + "serde", + "time", +] + +[[package]] +name = "tantivy-fst" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" +dependencies = [ + "byteorder", + "regex-syntax 0.8.4", + "utf8-ranges", +] + +[[package]] +name = "tantivy-query-grammar" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847434d4af57b32e309f4ab1b4f1707a6c566656264caa427ff4285c4d9d0b82" +dependencies = [ + "nom", +] + +[[package]] +name = "tantivy-sstable" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c69578242e8e9fc989119f522ba5b49a38ac20f576fc778035b96cc94f41f98e" +dependencies = [ + "tantivy-bitpacker", + "tantivy-common", + "tantivy-fst", + "zstd 0.13.2", +] + +[[package]] +name = "tantivy-stacker" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56d6ff5591fc332739b3ce7035b57995a3ce29a93ffd6012660e0949c956ea8" +dependencies = [ + "murmurhash32", + "rand_distr", + "tantivy-common", +] + +[[package]] +name = "tantivy-tokenizer-api" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0dcade25819a89cfe6f17d932c9cedff11989936bf6dd4f336d50392053b04" +dependencies = [ + "serde", +] + [[package]] name = "tap" version = "1.0.1" @@ -9696,6 +9973,12 @@ dependencies = [ "url", ] +[[package]] +name = "utf8-ranges" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" + [[package]] name = "utf8parse" version = "0.2.2" @@ -10077,6 +10360,7 @@ dependencies = [ "windmill-api-client", "windmill-common", "windmill-git-sync", + "windmill-indexer", "windmill-queue", "windmill-worker", ] @@ -10153,6 +10437,7 @@ dependencies = [ "windmill-audit", "windmill-common", "windmill-git-sync", + "windmill-indexer", "windmill-parser", "windmill-parser-py-imports", "windmill-parser-ts", @@ -10244,6 +10529,23 @@ dependencies = [ "windmill-queue", ] +[[package]] +name = "windmill-indexer" +version = "1.360.1" +dependencies = [ + "anyhow", + "chrono", + "futures", + "serde", + "serde_json", + "sqlx", + "tantivy", + "tokio", + "tracing", + "uuid 1.10.0", + "windmill-common", +] + [[package]] name = "windmill-parser" version = "1.361.1" @@ -10494,6 +10796,16 @@ dependencies = [ "zstd 0.12.4", ] +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -10503,6 +10815,25 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index e0f0384bcd..d016aa6397 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -13,6 +13,7 @@ members = [ "./windmill-common", "./windmill-audit", "./windmill-git-sync", + "./windmill-indexer", "./parsers/windmill-parser", "./parsers/windmill-parser-ts", "./parsers/windmill-parser-wasm", @@ -52,6 +53,7 @@ flow_testing = ["windmill-worker/flow_testing"] openidconnect = ["windmill-api/openidconnect"] cloud = ["windmill-queue/cloud", "windmill-worker/cloud"] jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"] +tantivy = ["windmill-indexer/tantivy"] [dependencies] anyhow.workspace = true @@ -62,6 +64,7 @@ windmill-common = { workspace = true, default-features = false } windmill-git-sync.workspace = true windmill-api = { workspace = true, default-features = false } windmill-worker.workspace = true +windmill-indexer.workspace = true futures.workspace = true tracing.workspace = true sqlx.workspace = true @@ -80,7 +83,8 @@ gethostname.workspace = true serde_json.workspace = true serde.workspace = true deno_core.workspace = true -pg-embed = {git = "https://github.com/faokunega/pg-embed", optional = true, default-features = false, features = ['rt_tokio']} +pg-embed = {git = "https://github.com/faokunega/pg-embed", optional = true, default-features = false, features = ['rt_tokio']} + [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemallocator = { optional = true, workspace = true } @@ -103,6 +107,7 @@ windmill-worker = { path = "./windmill-worker" } windmill-common = { path = "./windmill-common", default-features = false } windmill-audit = { path = "./windmill-audit" } windmill-git-sync = { path = "./windmill-git-sync" } +windmill-indexer = {path = "./windmill-indexer"} windmill-parser = { path = "./parsers/windmill-parser" } windmill-parser-ts = { path = "./parsers/windmill-parser-ts" } windmill-parser-py = { path = "./parsers/windmill-parser-py" } @@ -254,3 +259,5 @@ tikv-jemalloc-ctl = { version = "^0.5" } # 0.1.12 broken (nested dependency of swc_common) triomphe = "<0.1.12" + +tantivy = "0.22.0" diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index e638406882..2bc6bb8618 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -d414242f92cfaf9f364e9aeb321a511c3ad46fa7 \ No newline at end of file +49714fc57e3fc8e716f080ecf99fc71bc8e8083e diff --git a/backend/src/main.rs b/backend/src/main.rs index 1a2ed655a3..9fa2c48098 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -174,6 +174,15 @@ async fn windmill_main() -> anyhow::Result<()> { } #[cfg(feature = "enterprise")] Mode::Agent + } else if &x == "indexer" { + tracing::info!("Binary is in 'indexer' mode"); + #[cfg(not(feature = "tantivy"))] + { + panic!("Indexer mode requires the tantivy feature flag"); + } + + #[cfg(feature = "tantivy")] + Mode::Indexer } else { if &x != "standalone" { tracing::error!("mode not recognized, defaulting to standalone: {x}"); @@ -188,7 +197,7 @@ async fn windmill_main() -> anyhow::Result<()> { Mode::Standalone }); - let num_workers = if mode == Mode::Server { + let num_workers = if mode == Mode::Server || mode == Mode::Indexer { 0 } else { std::env::var("NUM_WORKERS") @@ -207,7 +216,7 @@ async fn windmill_main() -> anyhow::Result<()> { .ok() .and_then(|x| x.parse::().ok()) .unwrap_or(false) - && (mode == Mode::Server || mode == Mode::Standalone); + && (mode == Mode::Server || mode == Mode::Standalone || mode == Mode::Indexer); let server_bind_address: IpAddr = if server_mode { std::env::var("SERVER_BIND_ADDR") @@ -346,11 +355,36 @@ Windmill Community Edition {GIT_VERSION} .await .expect("could not create initial server dir"); + #[cfg(feature = "tantivy")] + let should_index_jobs = mode == Mode::Indexer || mode == Mode::Standalone; + + #[cfg(not(feature = "tantivy"))] + let should_index_jobs = false; + + let (index_reader, index_writer) = if should_index_jobs { + let (r, w) = windmill_indexer::indexer_ee::init_index()?; + (Some(r), Some(w)) + } else { + (None, None) + }; + + let indexer_rx = killpill_rx.resubscribe(); + let index_writer2 = index_writer.clone(); + let indexer_f = async { + if let Some(index_writer) = index_writer2 { + windmill_indexer::indexer_ee::run_indexer(db.clone(), index_writer, indexer_rx) + .await; + } + Ok(()) + }; + let server_f = async { if !is_agent { windmill_api::run_server( db.clone(), rsmq2, + index_reader, + index_writer, addr, server_killpill_rx, base_internal_tx, @@ -597,7 +631,14 @@ Windmill Community Edition {GIT_VERSION} schedule_key_renewal(&HTTP_CLIENT, &db).await; } - futures::try_join!(shutdown_signal, workers_f, monitor_f, server_f, metrics_f)?; + futures::try_join!( + shutdown_signal, + workers_f, + monitor_f, + server_f, + metrics_f, + indexer_f + )?; } else { tracing::info!("Nothing to do, exiting."); } diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index 68b3c3f061..063405a500 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -122,6 +122,8 @@ impl ApiServer { let task = tokio::task::spawn(windmill_api::run_server( db.clone(), None, + None, + None, addr, rx, port_tx, diff --git a/backend/windmill-api/Cargo.toml b/backend/windmill-api/Cargo.toml index ed2551dcda..9ea2d80f89 100644 --- a/backend/windmill-api/Cargo.toml +++ b/backend/windmill-api/Cargo.toml @@ -27,6 +27,7 @@ windmill-parser.workspace = true windmill-parser-py-imports.workspace = true windmill-parser-ts.workspace = true windmill-git-sync.workspace = true +windmill-indexer.workspace = true tokio.workspace = true anyhow.workspace = true argon2.workspace = true @@ -92,4 +93,4 @@ pin-project.workspace = true crc.workspace = true http.workspace = true async-stream.workspace = true -ulid.workspace = true \ No newline at end of file +ulid.workspace = true diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 74f54d6e3a..96f982ba10 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -8552,6 +8552,42 @@ paths: schema: $ref: "#/components/schemas/ExtendedJobs" + /srch/w/{workspace}/index/search/job: + get: + summary: Search through jobs with a string query + operationId: searchJobsIndex + tags: + - indexSearch + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: search_query + in: query + required: true + schema: + type: string + responses: + "200": + description: search results + content: + application/json: + schema: + type: object + properties: + query_parse_errors: + description: a list of the terms that couldn't be parsed (and thus ignored) + type: array + items: + type: object + properties: + dancer: + type: string + hits: + description: the jobs that matched the query + type: array + items: + $ref: "#/components/schemas/JobSearchHit" + + components: securitySchemes: bearerAuth: @@ -11085,3 +11121,9 @@ components: required: - jobs - obscured_jobs + + JobSearchHit: + type: object + properties: + dancer: + type: string diff --git a/backend/windmill-api/src/indexer_ee.rs b/backend/windmill-api/src/indexer_ee.rs new file mode 100644 index 0000000000..61a946bb84 --- /dev/null +++ b/backend/windmill-api/src/indexer_ee.rs @@ -0,0 +1,5 @@ +use axum::Router; + +pub fn workspaced_service() -> Router { + Router::new() +} diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index f258f5602f..212738c7e2 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -57,6 +57,7 @@ mod flows; mod folders; mod granular_acls; mod groups; +mod indexer_ee; mod inputs; mod integration; #[cfg(feature = "parquet")] @@ -143,6 +144,8 @@ pub async fn add_webhook_allowed_origin( pub async fn run_server( db: DB, rsmq: Option, + index_reader: Option, + index_writer: Option, addr: SocketAddr, mut rx: tokio::sync::broadcast::Receiver<()>, port_tx: tokio::sync::oneshot::Sender, @@ -177,6 +180,8 @@ pub async fn run_server( .layer(Extension(rsmq)) .layer(Extension(user_db)) .layer(Extension(auth_cache.clone())) + .layer(Extension(index_reader)) + .layer(Extension(index_writer)) .layer(CookieManagerLayer::new()) .layer(Extension(WebhookShared::new(rx.resubscribe(), db.clone()))) .layer(DefaultBodyLimit::max( @@ -266,6 +271,10 @@ pub async fn run_server( .route_layer(from_extractor::()) .route_layer(from_extractor::()) .nest("/jobs", jobs::global_root_service()) + .nest( + "/srch/w/:workspace_id/index", + indexer_ee::workspaced_service(), + ) .nest("/oidc", oidc_ee::global_service()) .nest( "/saml", diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index 82cf0b3fed..81031a7359 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -183,6 +183,7 @@ pub enum Mode { Agent, Server, Standalone, + Indexer, } pub async fn send_email( diff --git a/backend/windmill-indexer/Cargo.toml b/backend/windmill-indexer/Cargo.toml new file mode 100644 index 0000000000..2380dd4df2 --- /dev/null +++ b/backend/windmill-indexer/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "windmill-indexer" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[lib] +name = "windmill_indexer" +path = "src/lib.rs" + +[features] +default = [] +tantivy = ["dep:tantivy"] + +[dependencies] +windmill-common.workspace = true +tantivy = {workspace = true, optional = true} +tokio.workspace = true +sqlx.workspace = true +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true +tracing.workspace = true +chrono.workspace = true +uuid.workspace = true +futures.workspace = true diff --git a/backend/windmill-indexer/src/indexer_ee.rs b/backend/windmill-indexer/src/indexer_ee.rs new file mode 100644 index 0000000000..39b1770f97 --- /dev/null +++ b/backend/windmill-indexer/src/indexer_ee.rs @@ -0,0 +1,21 @@ +use sqlx::{Pool, Postgres}; +use windmill_common::error::Error; +use anyhow::anyhow; + +#[derive(Clone)] +pub struct IndexReader; + +#[derive(Clone)] +pub struct IndexWriter; + +pub fn init_index() -> Result<(IndexReader, IndexWriter), Error> { + Err(anyhow!("Cannot initialize index: not in EE").into()) +} + +pub async fn run_indexer( + _db: Pool, + mut _index_writer: IndexWriter, + mut _killpill_rx: tokio::sync::broadcast::Receiver<()>, +) { + tracing::error!("Cannot run indexer: not in EE"); +} diff --git a/backend/windmill-indexer/src/lib.rs b/backend/windmill-indexer/src/lib.rs new file mode 100644 index 0000000000..c1bf4eea0c --- /dev/null +++ b/backend/windmill-indexer/src/lib.rs @@ -0,0 +1 @@ +pub mod indexer_ee; diff --git a/docker-compose.yml b/docker-compose.yml index d5053ecd54..a32c949f0d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -109,6 +109,27 @@ services: # - /var/run/docker.sock:/var/run/docker.sock # - worker_dependency_cache:/tmp/windmill/cache + # The indexer powers full-text job and log search, an EE feature. + windmill_indexer: + image: ${WM_IMAGE} + pull_policy: always + deploy: + replicas: 0 # set to 1 to enable full-text job and log search + restart: unless-stopped + expose: + - 8001 + environment: + - PORT=8001 + - DATABASE_URL=${DATABASE_URL} + - MODE=indexer + #how often will new jobs be added to the index to be searched + - TANTIVY_REFRESH_INDEX_PERIOD__S=300 + depends_on: + db: + condition: service_healthy + volumes: + - windmill_index:/tmp/windmill/search + lsp: image: ghcr.io/windmill-labs/windmill-lsp:latest pull_policy: always @@ -147,4 +168,5 @@ volumes: db_data: null worker_dependency_cache: null worker_logs: null + windmill_index: null lsp_cache: null diff --git a/frontend/src/lib/components/ContentSearch.svelte b/frontend/src/lib/components/ContentSearch.svelte deleted file mode 100644 index 9ae8b19289..0000000000 --- a/frontend/src/lib/components/ContentSearch.svelte +++ /dev/null @@ -1,440 +0,0 @@ - - - { - return escape(s.content) - }} - bind:filteredItems={filteredScriptItems} -/> - - { - return escape(YAML.stringify(s.value)) - }} - bind:filteredItems={filteredResourceItems} -/> - - { - return escape(YAML.stringify(s.value, null, 4)) - }} - bind:filteredItems={filteredFlowItems} -/> - - { - return escape(YAML.stringify(s.value, null, 4)) - }} - bind:filteredItems={filteredAppItems} -/> - -{#if isOpen} - -
-
{ - isOpen = false - }} - > -
-
Search by content
-
- -
-
-
-
-
- - - - - - - -
-
- - - -
-
-
-
Searching among
{#if scripts}{scripts?.length}{:else} - - {/if}
- scripts, -
{#if resources}{resources?.length}{:else} - - {/if}
- resources, -
{#if flows}{flows?.length}{:else} - - {/if}
- flows, -
{#if apps}{apps?.length}{:else} - - {/if}
- apps
-
- -
- {#if !$enterpriseLicense} -
- - - Without EE, content search will only search among 10 scripts, 3 flows, 3 apps and 3 - resources. - -
- {/if} - - {#if search.trim().length > 0} -
- {#if (searchKind == 'all' || searchKind == 'scripts') && filteredScriptItems?.length > 0} - {#each filteredScriptItems.slice(0, showNbScripts) ?? [] as item} -
- -
-
{@html item.marked}
-
-
- -
-
-
-
- {/each} - {#if filteredScriptItems.length > showNbScripts} - { - showNbScripts += 30 - }} - > - ({showNbScripts} of {filteredScriptItems.length}) Show more scripts - - {/if} - {/if} - {#if (searchKind == 'all' || searchKind == 'resources') && filteredResourceItems?.length > 0} - {#each filteredResourceItems.slice(0, showNbResources) ?? [] as item} -
-
Resource: {item.path}
-
-
{@html item.marked}
-
-
- {/each} - {#if filteredResourceItems.length > showNbResources} - { - showNbResources += 30 - }} - > - ({showNbResources} of {filteredResourceItems.length}) Show more resources - - {/if} - {/if} - {#if (searchKind == 'all' || searchKind == 'flows') && filteredFlowItems?.length > 0} - {#each filteredFlowItems.slice(0, showNbFlows) ?? [] as item} -
- -
-
{@html item.marked}
-
-
- -
-
-
-
- {/each} - {#if filteredFlowItems.length > showNbFlows} - { - showNbScripts += 30 - }} - > - ({showNbFlows} of {filteredFlowItems.length}) Show more flows - - {/if} - {/if} - {#if (searchKind == 'all' || searchKind == 'apps') && filteredAppItems?.length > 0} - {#each filteredAppItems.slice(0, showNbApps) ?? [] as item} -
- -
-
{@html item.marked}
-
-
- {/each} - {#if filteredAppItems.length > showNbApps} - { - showNbApps += 30 - }} - > - ({showNbApps} of {filteredAppItems.length}) Show more apps - - {/if} - {/if} -
- {:else} -
-
-
Empty Search Filter
-
Start writing, search everywhere a path is referenced for instance
-
-
- {/if} -
-
-{/if} diff --git a/frontend/src/lib/components/ContentSearchInner.svelte b/frontend/src/lib/components/ContentSearchInner.svelte new file mode 100644 index 0000000000..dafddbb8a8 --- /dev/null +++ b/frontend/src/lib/components/ContentSearchInner.svelte @@ -0,0 +1,379 @@ + + + { + return escape(s.content) + }} + bind:filteredItems={filteredScriptItems} +/> + + { + return escape(YAML.stringify(s.value)) + }} + bind:filteredItems={filteredResourceItems} +/> + + { + return escape(YAML.stringify(s.value, null, 4)) + }} + bind:filteredItems={filteredFlowItems} +/> + + { + return escape(YAML.stringify(s.value, null, 4)) + }} + bind:filteredItems={filteredAppItems} +/> + +
+
+
+ + + + + + + +
+
+ + +
+
+
+
Searching among
{#if scripts}{scripts?.length}{:else} + + {/if}
+ scripts, +
{#if resources}{resources?.length}{:else} + + {/if}
+ resources, +
{#if flows}{flows?.length}{:else} + + {/if}
+ flows, +
{#if apps}{apps?.length}{:else} + + {/if}
+ apps
+
+ +
+ {#if !$enterpriseLicense} +
+ + + Without EE, content search will only search among 10 scripts, 3 flows, 3 apps and 3 + resources. + +
+ {/if} + + {#if search.trim().length > 0} +
+ {#if (searchKind == 'all' || searchKind == 'scripts') && filteredScriptItems?.length > 0} + {#each filteredScriptItems.slice(0, showNbScripts) ?? [] as item} +
+ +
+
{@html item.marked}
+
+
+ +
+
+
+
+ {/each} + {#if filteredScriptItems.length > showNbScripts} + { + showNbScripts += 30 + }} + > + ({showNbScripts} of {filteredScriptItems.length}) Show more scripts + + {/if} + {/if} + {#if (searchKind == 'all' || searchKind == 'resources') && filteredResourceItems?.length > 0} + {#each filteredResourceItems.slice(0, showNbResources) ?? [] as item} +
+
Resource: {item.path}
+
+
{@html item.marked}
+
+
+ {/each} + {#if filteredResourceItems.length > showNbResources} + { + showNbResources += 30 + }} + > + ({showNbResources} of {filteredResourceItems.length}) Show more resources + + {/if} + {/if} + {#if (searchKind == 'all' || searchKind == 'flows') && filteredFlowItems?.length > 0} + {#each filteredFlowItems.slice(0, showNbFlows) ?? [] as item} +
+ +
+
{@html item.marked}
+
+
+ +
+
+
+
+ {/each} + {#if filteredFlowItems.length > showNbFlows} + { + showNbScripts += 30 + }} + > + ({showNbFlows} of {filteredFlowItems.length}) Show more flows + + {/if} + {/if} + {#if (searchKind == 'all' || searchKind == 'apps') && filteredAppItems?.length > 0} + {#each filteredAppItems.slice(0, showNbApps) ?? [] as item} +
+ +
+
{@html item.marked}
+
+
+ {/each} + {#if filteredAppItems.length > showNbApps} + { + showNbApps += 30 + }} + > + ({showNbApps} of {filteredAppItems.length}) Show more apps + + {/if} + {/if} +
+ {:else} +
+
+
Empty Search Filter
+
Start writing, search everywhere a path is referenced for instance
+
+
+ {/if} +
+
diff --git a/frontend/src/lib/components/Path.svelte b/frontend/src/lib/components/Path.svelte index 53e5247412..1bcf12d70e 100644 --- a/frontend/src/lib/components/Path.svelte +++ b/frontend/src/lib/components/Path.svelte @@ -15,7 +15,7 @@ VariableService } from '$lib/gen' import { superadmin, userStore, workspaceStore } from '$lib/stores' - import { createEventDispatcher } from 'svelte' + import { createEventDispatcher, getContext } from 'svelte' import { writable } from 'svelte/store' import { Alert, Button, Drawer, DrawerContent } from './common' import Badge from './common/badge/Badge.svelte' @@ -26,7 +26,6 @@ import Required from './Required.svelte' import Tooltip from './Tooltip.svelte' import { Eye, Folder, Plus, SearchCode, User } from 'lucide-svelte' - import ContentSearch from './ContentSearch.svelte' type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app' | 'raw_app' let meta: Meta | undefined = undefined @@ -263,13 +262,9 @@ !dirty && (dirty = true) } - let contentSearch: ContentSearch + const openSearchWithPrefilledText: (t?: string) => void = getContext("openSearchWithPrefilledText") -{#if kind != 'app' && kind != 'schedule' && initialPath != '' && initialPath != undefined} - -{/if} - { - contentSearch?.open(initialPath) + openSearchWithPrefilledText("#") }} startIcon={{ icon: SearchCode }} > diff --git a/frontend/src/lib/components/home/ItemsList.svelte b/frontend/src/lib/components/home/ItemsList.svelte index 6e60e052ef..f4c72666f0 100644 --- a/frontend/src/lib/components/home/ItemsList.svelte +++ b/frontend/src/lib/components/home/ItemsList.svelte @@ -37,13 +37,13 @@ import { canWrite, getLocalSetting, storeLocalSetting } from '$lib/utils' import { page } from '$app/stores' import { setQuery } from '$lib/navigation' - import ContentSearch from '../ContentSearch.svelte' import Drawer from '../common/drawer/Drawer.svelte' import HighlightCode from '../HighlightCode.svelte' import DrawerContent from '../common/drawer/DrawerContent.svelte' import Item from './Item.svelte' import TreeViewRoot from './TreeViewRoot.svelte' import { Popup } from '../common' + import { getContext } from 'svelte' type TableItem = T & { canWrite: boolean @@ -289,7 +289,7 @@ $: storeLocalSetting(FILTER_USER_FOLDER_SETTING_NAME, filterUserFolders ? 'true' : undefined) $: storeLocalSetting(INCLUDE_WITHOUT_MAIN_SETTING_NAME, includeWithoutMain ? 'true' : undefined) - let contentSearch: ContentSearch + const openSearchWithPrefilledText: (t?: string) => void = getContext("openSearchWithPrefilledText") let viewCodeDrawer: Drawer let viewCodeTitle: string | undefined @@ -332,7 +332,6 @@ -
@@ -397,7 +396,7 @@
+ + + {/each} +
+ {#if selectedItem === undefined} + select a result to preview + {:else} +
+ +
+ {/if} + {:else} +
+
+
No runs found
+
There were no completed runs that match your query
+
Note that new runs might take a while to become searchable (by default + ~5min)
+ {#if !$enterpriseLicense} +
+ + + Full-text search on jobs is only available on EE. + + {/if} +
+
+ {/if} +
+ {/if} +
+
+
+
+
+{/if} + + diff --git a/frontend/src/lib/components/search/QuickMenuItem.svelte b/frontend/src/lib/components/search/QuickMenuItem.svelte new file mode 100644 index 0000000000..a7df0564a0 --- /dev/null +++ b/frontend/src/lib/components/search/QuickMenuItem.svelte @@ -0,0 +1,87 @@ + + +
dispatch('hover')} + class={`rounded-md w-full ${hovered ? 'bg-surface-hover' : ''}`} +> + {#if $$slots.itemReplacement} + + {:else} +
+
+ {#if icon} + + {:else if shortcutKey != undefined} +
+ + + {shortcutKey} + + +
+ {/if} +
+ {label} + {#if shortcutKey != undefined} +
+
+ + + {shortcutKey} + + +
+
+ {/if} +
+ {/if} +
diff --git a/frontend/src/lib/components/sidebar/MenuButton.svelte b/frontend/src/lib/components/sidebar/MenuButton.svelte index 5d533eae8b..35cd762332 100644 --- a/frontend/src/lib/components/sidebar/MenuButton.svelte +++ b/frontend/src/lib/components/sidebar/MenuButton.svelte @@ -1,17 +1,26 @@ {#if !disabled} diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index 8614337f49..e69058a806 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -11,7 +11,7 @@ UserService, WorkspaceService } from '$lib/gen' - import { classNames } from '$lib/utils' + import { classNames, getModifierKey } from '$lib/utils' import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte' import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte' import { @@ -37,14 +37,18 @@ import { SUPERADMIN_SETTINGS_HASH, USER_SETTINGS_HASH } from '$lib/components/sidebar/settings' import { isCloudHosted } from '$lib/cloud' import { syncTutorialsTodos } from '$lib/tutorialUtils' - import { ArrowLeft } from 'lucide-svelte' + import { ArrowLeft, Search } from 'lucide-svelte' import { getUserExt } from '$lib/user' import { workspacedOpenai } from '$lib/components/copilot/lib' import { twMerge } from 'tailwind-merge' import OperatorMenu from '$lib/components/sidebar/OperatorMenu.svelte' + import GlobalSearchModal from '$lib/components/search/GlobalSearchModal.svelte' + import MenuButton from '$lib/components/sidebar/MenuButton.svelte' + import { setContext } from 'svelte' OpenAPI.WITH_CREDENTIALS = true let menuOpen = false + let globalSearchModal: GlobalSearchModal | undefined = undefined let isCollapsed = false let userSettings: UserSettings let superadminSettings: SuperadminSettings @@ -244,6 +248,12 @@ $: if (isCollapsed && $userStore?.operator) { isCollapsed = false } + + function openSearchModal(text?: string): void { + globalSearchModal?.openSearchWithPrefilledText(text) + } + + setContext("openSearchWithPrefilledText", openSearchModal) @@ -258,6 +268,7 @@ {:else if $userStore} + {#if $superadmin} {/if} @@ -328,6 +339,15 @@
+ openSearchModal()} + isCollapsed={false} + icon={Search} + label="Search" + class="!text-xs" + shortcut={`${getModifierKey()}k`} + />
@@ -367,6 +387,15 @@
+ openSearchModal()} + {isCollapsed} + icon={Search} + label="Search" + class="!text-xs" + shortcut={`${getModifierKey()}k`} + />
@@ -446,6 +475,15 @@
+ openSearchModal()} + {isCollapsed} + icon={Search} + label="Search" + class="!text-xs" + shortcut={`${getModifierKey()}k`} + />