diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index be7d248f12..e344609472 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -3070,6 +3070,10 @@ paths: in: query schema: type: string + - name: get_usages + in: query + schema: + type: boolean - $ref: "#/components/parameters/Page" - $ref: "#/components/parameters/PerPage" responses: @@ -14261,6 +14265,10 @@ components: expires_at: type: string format: date-time + usages: + type: array + items: + $ref: "#/components/schemas/AssetUsage" required: - workspace_id - path diff --git a/backend/windmill-api/src/resources.rs b/backend/windmill-api/src/resources.rs index e42699d039..cb47f5731e 100644 --- a/backend/windmill-api/src/resources.rs +++ b/backend/windmill-api/src/resources.rs @@ -122,7 +122,7 @@ pub struct ListableResource { pub is_expired: Option, pub refresh_error: Option, pub account: Option, - pub usages: Option>, // AssetUsage[] | null + pub usages: Option>, // AssetUsage[] | null } #[derive(Deserialize)] diff --git a/backend/windmill-api/src/variables.rs b/backend/windmill-api/src/variables.rs index 73aba91229..1f740bc579 100644 --- a/backend/windmill-api/src/variables.rs +++ b/backend/windmill-api/src/variables.rs @@ -7,7 +7,9 @@ */ use crate::{ - db::{ApiAuthed, DB}, users::{maybe_refresh_folders, require_owner_of_path}, webhook_util::{WebhookMessage, WebhookShared} + db::{ApiAuthed, DB}, + users::{maybe_refresh_folders, require_owner_of_path}, + webhook_util::{WebhookMessage, WebhookShared}, }; use axum::{ @@ -21,7 +23,10 @@ use serde_json::Value; use windmill_audit::audit_oss::{audit_log, AuditAuthorable}; use windmill_audit::ActionKind; use windmill_common::{ - db::UserDB, error::{Error, JsonResult, Result}, utils::{not_found_if_none, paginate, Pagination, StripPath}, variables::{ + db::UserDB, + error::{Error, JsonResult, Result}, + utils::{not_found_if_none, paginate, Pagination, StripPath}, + variables::{ build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable, }, worker::CLOUD_HOSTED, @@ -80,6 +85,7 @@ async fn list_contextual_variables( #[derive(Deserialize)] struct ListVariableQuery { path_start: Option, + get_usages: Option, } async fn list_variables( @@ -99,7 +105,13 @@ async fn list_variables( account.refresh_error, resource.path IS NOT NULL as is_linked, account.refresh_token != '' as is_refreshed, - variable.expires_at + variable.expires_at, + CASE WHEN $6 IS TRUE THEN + (SELECT ARRAY_AGG(jsonb_build_object( + 'access_type', asset.usage_access_type, 'path', asset.usage_path, 'kind', asset.usage_kind)) + FROM asset + WHERE asset.path = variable.path AND asset.workspace_id = $1 AND asset.kind = 'variable') + ELSE NULL END as usages from variable LEFT JOIN account ON variable.account = account.id AND account.workspace_id = $1 LEFT JOIN resource ON resource.path = variable.path AND resource.workspace_id = $1 @@ -114,6 +126,7 @@ async fn list_variables( .bind(&lq.path_start.unwrap_or_default()) .bind(per_page as i32) .bind(offset as i32) + .bind(lq.get_usages.unwrap_or(false)) .fetch_all(&mut *tx) .await?; diff --git a/backend/windmill-common/src/assets.rs b/backend/windmill-common/src/assets.rs index a23f4c4094..6a72f9e12b 100644 --- a/backend/windmill-common/src/assets.rs +++ b/backend/windmill-common/src/assets.rs @@ -39,7 +39,7 @@ pub struct Asset { pub struct AssetUsage { pub path: String, pub kind: AssetUsageKind, - pub access_type: AssetUsageAccessType, + pub access_type: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Hash)] diff --git a/backend/windmill-common/src/variables.rs b/backend/windmill-common/src/variables.rs index 1dfa1438d8..60c417ffec 100644 --- a/backend/windmill-common/src/variables.rs +++ b/backend/windmill-common/src/variables.rs @@ -43,6 +43,7 @@ pub struct ListableVariable { pub refresh_error: Option, pub is_linked: Option, pub expires_at: Option>, + pub usages: Option>, // AssetUsage[] | null } #[derive(Serialize, Deserialize, sqlx::FromRow)] diff --git a/frontend/src/lib/components/assets/ResourceListPage.svelte b/frontend/src/lib/components/assets/ResourceListPage.svelte index 9fafcd0dbb..ebcf715152 100644 --- a/frontend/src/lib/components/assets/ResourceListPage.svelte +++ b/frontend/src/lib/components/assets/ResourceListPage.svelte @@ -811,11 +811,11 @@ -
+
{ diff --git a/frontend/src/lib/components/assets/VariablesListPage.svelte b/frontend/src/lib/components/assets/VariablesListPage.svelte index b3c0869afc..7fcdfd6f0a 100644 --- a/frontend/src/lib/components/assets/VariablesListPage.svelte +++ b/frontend/src/lib/components/assets/VariablesListPage.svelte @@ -17,11 +17,16 @@ import TableSimple from '$lib/components/TableSimple.svelte' import Tooltip from '$lib/components/Tooltip.svelte' import VariableEditor from '$lib/components/VariableEditor.svelte' - import type { ContextualVariable, ListableVariable, WorkspaceDeployUISettings } from '$lib/gen' + import type { + AssetUsage, + ContextualVariable, + ListableVariable, + WorkspaceDeployUISettings + } from '$lib/gen' import { OauthService, VariableService, WorkspaceService } from '$lib/gen' import { enterpriseLicense, userStore, workspaceStore, userWorkspaces } from '$lib/stores' import { sendUserToast } from '$lib/toast' - import { canWrite, isOwner, truncate } from '$lib/utils' + import { canWrite, isOwner, pluralize, truncate } from '$lib/utils' import { isDeployable, ALL_DEPLOYABLE } from '$lib/utils_deployable' import { Plus, @@ -37,6 +42,13 @@ Circle } from 'lucide-svelte' import { untrack } from 'svelte' + import { twMerge } from 'tailwind-merge' + + type Props = { + onOpenUsages: (usages: AssetUsage[]) => void + } + + let { onOpenUsages }: Props = $props() type ListableVariableW = ListableVariable & { canWrite: boolean } @@ -76,7 +88,9 @@ // If relative, the dropdown is positioned relative to its button async function loadVariables(): Promise { - variables = (await VariableService.listVariable({ workspace: $workspaceStore! })).map((x) => { + variables = ( + await VariableService.listVariable({ workspace: $workspaceStore!, getUsages: true }) + ).map((x) => { return { canWrite: canWrite(x.path, x.extra_perms!, $userStore) && x.workspace_id == $workspaceStore, ...x @@ -226,7 +240,7 @@ - {#each filteredItems as { path, value, is_secret, description, extra_perms, canWrite, account, is_refreshed, is_expired, refresh_error, is_linked, marked }} + {#each filteredItems as { path, value, is_secret, description, extra_perms, canWrite, account, is_refreshed, is_expired, refresh_error, is_linked, marked, usages }} @@ -268,9 +282,20 @@ {truncate(description ?? '', 50)} - +
- 0 usages + { + if (usages?.length) onOpenUsages(usages) + }} + > + {pluralize(usages?.length ?? 0, 'usage')} + {#if is_linked} diff --git a/frontend/src/routes/(root)/(logged)/assets/+page.svelte b/frontend/src/routes/(root)/(logged)/assets/+page.svelte index 29b225c12f..669d0c2321 100644 --- a/frontend/src/routes/(root)/(logged)/assets/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/assets/+page.svelte @@ -4,9 +4,7 @@ import { DrawerContent, Tab, Tabs } from '$lib/components/common' import Drawer from '$lib/components/common/drawer/Drawer.svelte' import RowIcon from '$lib/components/common/table/RowIcon.svelte' - import DbManagerDrawer from '$lib/components/DBManagerDrawer.svelte' import PageHeader from '$lib/components/PageHeader.svelte' - import S3FilePicker from '$lib/components/S3FilePicker.svelte' import { type AssetUsageAccessType, type AssetUsageKind } from '$lib/gen' import { userStore, workspaceStore, userWorkspaces } from '$lib/stores' import { @@ -29,9 +27,6 @@ }[] } | undefined = $state() - let s3FilePicker: S3FilePicker | undefined = $state() - let dbManagerDrawer: DbManagerDrawer | undefined = $state() - let selectedTab: 'resources' | 'variables' | 's3objects' = $state('resources') let resourceListPage: ResourceListPage | undefined = $state() @@ -103,7 +98,10 @@ onOpenUsages={(usages) => (usagesDrawerData = { usages })} /> {:else if selectedTab === 'variables'} - + (usagesDrawerData = { usages })} + /> {:else if selectedTab === 's3objects'} {/if} @@ -143,6 +141,3 @@ - - -