diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index a04b5844ea..e0d990384a 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -8ffae1f43b31dc8136714fa612d22b6301773e27 \ No newline at end of file +9b3339730eb4bb0b564c7c56ac546f33fb3d8905 \ No newline at end of file diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 7e89e6dd0e..4dc1d252c4 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -16973,9 +16973,9 @@ paths: description: count of log lines that matched the query per hostname type: object - /srch/index/delete/{idx_name}: + /indexer/delete/{idx_name}: delete: - summary: Restart container and delete the index to recreate it. + summary: Clear an index and restart the indexer. operationId: clearIndex tags: - indexSearch @@ -16990,12 +16990,102 @@ paths: - ServiceLogIndex responses: "200": - description: idx to be deleted and container restarting + description: idx to be deleted and indexer restarting content: text/plain: schema: type: string + /indexer/storage: + get: + summary: Get index storage sizes (disk and S3). + operationId: getIndexStorageSizes + tags: + - indexSearch + responses: + "200": + description: storage sizes for each index + content: + application/json: + schema: + type: object + properties: + job_index: + type: object + properties: + disk_size_bytes: + type: integer + nullable: true + s3_size_bytes: + type: integer + nullable: true + service_log_index: + type: object + properties: + disk_size_bytes: + type: integer + nullable: true + s3_size_bytes: + type: integer + nullable: true + + /indexer/status: + get: + summary: Get indexer status including liveness and storage sizes. + operationId: getIndexerStatus + tags: + - indexSearch + responses: + "200": + description: indexer status for each index + content: + application/json: + schema: + type: object + properties: + job_indexer: + type: object + properties: + is_alive: + type: boolean + last_locked_at: + type: string + format: date-time + nullable: true + owner: + type: string + nullable: true + storage: + type: object + properties: + disk_size_bytes: + type: integer + nullable: true + s3_size_bytes: + type: integer + nullable: true + log_indexer: + type: object + properties: + is_alive: + type: boolean + last_locked_at: + type: string + format: date-time + nullable: true + owner: + type: string + nullable: true + storage: + type: object + properties: + disk_size_bytes: + type: integer + nullable: true + s3_size_bytes: + type: integer + nullable: true + /w/{workspace}/assets/list: get: summary: List all assets in the workspace with cursor pagination diff --git a/backend/windmill-api/src/indexer_oss.rs b/backend/windmill-api/src/indexer_oss.rs index eee87acdcb..fd87c10fe2 100644 --- a/backend/windmill-api/src/indexer_oss.rs +++ b/backend/windmill-api/src/indexer_oss.rs @@ -14,3 +14,8 @@ pub fn workspaced_service() -> Router { pub fn global_service() -> Router { Router::new() } + +#[cfg(not(feature = "private"))] +pub fn management_service() -> Router { + Router::new() +} diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 76fe0224ed..9b713cad0a 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -58,10 +58,7 @@ use windmill_common::db::UserDB; use windmill_common::worker::CLOUD_HOSTED; #[allow(unused_imports)] pub(crate) use windmill_common::BASE_URL; -use windmill_common::{ - utils::GIT_VERSION, - INSTANCE_NAME, -}; +use windmill_common::{utils::GIT_VERSION, INSTANCE_NAME}; use crate::scim_oss::has_scim_token; use windmill_common::error::AppError; @@ -550,6 +547,7 @@ pub async fn run_server( .nest("/embeddings", embeddings::global_service()) .nest("/ai", ai::global_service()) .nest("/inkeep", inkeep_oss::global_service()) + .nest("/indexer", indexer_oss::management_service()) .nest("/mcp/w/:workspace_id/list_tools", mcp_list_tools_service) .nest("/health/detailed", health::detailed_service()) .route_layer(from_extractor::()) @@ -612,8 +610,10 @@ pub async fn run_server( if let Some(agent_workers_job_completed_tx) = agent_workers_job_completed_tx.clone() { - windmill_api_agent_workers::global_service(agent_workers_job_completed_tx) - .layer(Extension(agent_cache.clone())) + windmill_api_agent_workers::global_service( + agent_workers_job_completed_tx, + ) + .layer(Extension(agent_cache.clone())) } else { Router::new() } @@ -785,7 +785,10 @@ pub async fn run_server( }, ) // JWKS endpoint for HashiCorp Vault JWT authentication (must be outside /api prefix) - .route("/.well-known/jwks.json", get(windmill_api_settings::get_jwks)) + .route( + "/.well-known/jwks.json", + get(windmill_api_settings::get_jwks), + ) .fallback(static_assets::static_handler) .layer(middleware_stack); diff --git a/frontend/src/lib/components/instanceSettings/IndexerMemorySettings.svelte b/frontend/src/lib/components/instanceSettings/IndexerMemorySettings.svelte index 4044c141fc..5a9dfb496a 100644 --- a/frontend/src/lib/components/instanceSettings/IndexerMemorySettings.svelte +++ b/frontend/src/lib/components/instanceSettings/IndexerMemorySettings.svelte @@ -2,11 +2,14 @@ import { Button } from '$lib/components/common' import ConfirmationModal from '../common/confirmationModal/ConfirmationModal.svelte' import { IndexSearchService } from '$lib/gen' + import type { GetIndexerStatusResponse } from '$lib/gen' import { sendUserToast } from '$lib/toast' + import { displaySize } from '$lib/utils' import Tooltip from '../Tooltip.svelte' import IntegerInput from '../IntegerInput.svelte' import InputError from '../InputError.svelte' import Label from '../Label.svelte' + import { Loader2, RefreshCw } from 'lucide-svelte' import type { Writable } from 'svelte/store' interface Props { @@ -19,6 +22,35 @@ let clearJobsIndexModalOpen = $state(false) let clearServiceLogsIndexModalOpen = $state(false) + + let status: GetIndexerStatusResponse | undefined = $state(undefined) + let statusLoading = $state(true) + let statusError = $state(false) + + function formatTimeAgo(isoDate: string): string { + const diffMs = Date.now() - new Date(isoDate).getTime() + const diffSecs = Math.floor(diffMs / 1000) + if (diffSecs < 60) return `${diffSecs}s ago` + const diffMins = Math.floor(diffSecs / 60) + if (diffMins < 60) return `${diffMins}m ago` + const diffHours = Math.floor(diffMins / 60) + return `${diffHours}h ago` + } + + async function loadStatus() { + statusLoading = true + statusError = false + try { + status = await IndexSearchService.getIndexerStatus() + } catch (e) { + status = undefined + statusError = true + } finally { + statusLoading = false + } + } + + loadStatus()
@@ -52,30 +84,101 @@ />
+ +