diff --git a/frontend/src/pages/Events/List/hooks/useColumnDefinitions.tsx b/frontend/src/pages/Events/List/hooks/useColumnDefinitions.tsx index d6e5b846e..bc9a07aa0 100644 --- a/frontend/src/pages/Events/List/hooks/useColumnDefinitions.tsx +++ b/frontend/src/pages/Events/List/hooks/useColumnDefinitions.tsx @@ -138,6 +138,19 @@ export const useColumnsDefinitions = () => { ); + case 'secret': + return ( +
+ Secret{' '} + {target.project_name && ( + + {target.project_name} + + )} + /{target.name} +
+ ); + default: return '---'; } diff --git a/frontend/src/pages/Events/List/hooks/useFilters.ts b/frontend/src/pages/Events/List/hooks/useFilters.ts index d463770b3..e6a97c71e 100644 --- a/frontend/src/pages/Events/List/hooks/useFilters.ts +++ b/frontend/src/pages/Events/List/hooks/useFilters.ts @@ -19,6 +19,7 @@ type RequestParamsKeys = keyof Pick< | 'target_jobs' | 'target_volumes' | 'target_gateways' + | 'target_secrets' | 'within_projects' | 'within_fleets' | 'within_runs' @@ -35,6 +36,7 @@ const filterKeys: Record = { TARGET_JOBS: 'target_jobs', TARGET_VOLUMES: 'target_volumes', TARGET_GATEWAYS: 'target_gateways', + TARGET_SECRETS: 'target_secrets', WITHIN_PROJECTS: 'within_projects', WITHIN_FLEETS: 'within_fleets', WITHIN_RUNS: 'within_runs', @@ -53,6 +55,7 @@ const multipleChoiseKeys: RequestParamsKeys[] = [ 'target_jobs', 'target_volumes', 'target_gateways', + 'target_secrets', 'within_projects', 'within_fleets', 'within_runs', @@ -69,6 +72,7 @@ const targetTypes = [ { label: 'Job', value: 'job' }, { label: 'Volume', value: 'volume' }, { label: 'Gateway', value: 'gateway' }, + { label: 'Secret', value: 'secret' }, ]; export const useFilters = () => { @@ -171,6 +175,11 @@ export const useFilters = () => { operators: ['='], propertyLabel: 'Target gateways', }, + { + key: filterKeys.TARGET_SECRETS, + operators: ['='], + propertyLabel: 'Target secrets', + }, { key: filterKeys.WITHIN_PROJECTS, diff --git a/frontend/src/types/event.d.ts b/frontend/src/types/event.d.ts index 618ea6673..dd0147fe1 100644 --- a/frontend/src/types/event.d.ts +++ b/frontend/src/types/event.d.ts @@ -1,4 +1,4 @@ -declare type TEventTargetType = 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job' | 'volume' | 'gateway'; +declare type TEventTargetType = 'project' | 'user' | 'fleet' | 'instance' | 'run' | 'job' | 'volume' | 'gateway' | 'secret'; declare type TEventListRequestParams = Omit & { prev_recorded_at?: string; @@ -10,6 +10,7 @@ declare type TEventListRequestParams = Omit EventListFilters: " Update the server to 0.20.7 or higher or remove --target-gateway." ) filters.target_gateways.append(id) + elif args.target_secrets: + filters.target_secrets = [ + api.client.secrets.get(api.project, name=name).id for name in args.target_secrets + ] if args.within_fleets: filters.within_fleets = [ diff --git a/src/dstack/_internal/cli/services/events.py b/src/dstack/_internal/cli/services/events.py index 11f764bd1..0f0eb0f4b 100644 --- a/src/dstack/_internal/cli/services/events.py +++ b/src/dstack/_internal/cli/services/events.py @@ -18,6 +18,7 @@ class EventListFilters: target_runs: Optional[list[uuid.UUID]] = None target_volumes: Optional[list[uuid.UUID]] = None target_gateways: Optional[list[uuid.UUID]] = None + target_secrets: Optional[list[uuid.UUID]] = None within_projects: Optional[list[uuid.UUID]] = None within_fleets: Optional[list[uuid.UUID]] = None within_runs: Optional[list[uuid.UUID]] = None diff --git a/src/dstack/_internal/core/models/events.py b/src/dstack/_internal/core/models/events.py index 289c4fc67..f2efb80d0 100644 --- a/src/dstack/_internal/core/models/events.py +++ b/src/dstack/_internal/core/models/events.py @@ -18,6 +18,7 @@ class EventTargetType(str, Enum): JOB = "job" VOLUME = "volume" GATEWAY = "gateway" + SECRET = "secret" class EventTarget(CoreModel): diff --git a/src/dstack/_internal/server/routers/events.py b/src/dstack/_internal/server/routers/events.py index 4250eb4d7..036a8b2be 100644 --- a/src/dstack/_internal/server/routers/events.py +++ b/src/dstack/_internal/server/routers/events.py @@ -46,6 +46,7 @@ async def list_events( target_jobs=body.target_jobs, target_volumes=body.target_volumes, target_gateways=body.target_gateways, + target_secrets=body.target_secrets, within_projects=body.within_projects, within_fleets=body.within_fleets, within_runs=body.within_runs, diff --git a/src/dstack/_internal/server/schemas/events.py b/src/dstack/_internal/server/schemas/events.py index 30f7fe324..3899b1f39 100644 --- a/src/dstack/_internal/server/schemas/events.py +++ b/src/dstack/_internal/server/schemas/events.py @@ -102,6 +102,17 @@ class ListEventsRequest(CoreModel): max_items=MAX_FILTER_ITEMS, ), ] = None + target_secrets: Annotated[ + Optional[list[uuid.UUID]], + Field( + description=( + "List of secret IDs." + " The response will only include events that target the specified secrets" + ), + min_items=MIN_FILTER_ITEMS, + max_items=MAX_FILTER_ITEMS, + ), + ] = None within_projects: Annotated[ Optional[list[uuid.UUID]], Field( diff --git a/src/dstack/_internal/server/services/events.py b/src/dstack/_internal/server/services/events.py index c6d35a457..d46b43e20 100644 --- a/src/dstack/_internal/server/services/events.py +++ b/src/dstack/_internal/server/services/events.py @@ -20,6 +20,7 @@ MemberModel, ProjectModel, RunModel, + SecretModel, UserModel, VolumeModel, ) @@ -93,6 +94,7 @@ def from_model( JobModel, ProjectModel, RunModel, + SecretModel, UserModel, VolumeModel, ], @@ -139,6 +141,13 @@ def from_model( id=model.id, name=model.run_name, ) + if isinstance(model, SecretModel): + return Target( + type=EventTargetType.SECRET, + project_id=model.project_id or model.project.id, + id=model.id, + name=model.name, + ) if isinstance(model, UserModel): return Target( type=EventTargetType.USER, @@ -232,6 +241,7 @@ async def list_events( target_jobs: Optional[list[uuid.UUID]], target_volumes: Optional[list[uuid.UUID]], target_gateways: Optional[list[uuid.UUID]], + target_secrets: Optional[list[uuid.UUID]], within_projects: Optional[list[uuid.UUID]], within_fleets: Optional[list[uuid.UUID]], within_runs: Optional[list[uuid.UUID]], @@ -315,6 +325,13 @@ async def list_events( EventTargetModel.entity_id.in_(target_gateways), ) ) + if target_secrets is not None: + target_filters.append( + and_( + EventTargetModel.entity_type == EventTargetType.SECRET, + EventTargetModel.entity_id.in_(target_secrets), + ) + ) if within_projects is not None: target_filters.append(EventTargetModel.entity_project_id.in_(within_projects)) if within_fleets is not None: diff --git a/src/dstack/api/server/_events.py b/src/dstack/api/server/_events.py index d403fb242..5aff9c9d2 100644 --- a/src/dstack/api/server/_events.py +++ b/src/dstack/api/server/_events.py @@ -31,6 +31,7 @@ def list( # NOTE: New parameters go here. Avoid positional parameters, they can break compatibility. target_volumes: Optional[list[UUID]] = None, target_gateways: Optional[list[UUID]] = None, + target_secrets: Optional[list[UUID]] = None, ) -> list[Event]: if prev_recorded_at is not None: # Time zones other than UTC are misinterpreted by the server: @@ -45,6 +46,7 @@ def list( target_jobs=target_jobs, target_volumes=target_volumes, target_gateways=target_gateways, + target_secrets=target_secrets, within_projects=within_projects, within_fleets=within_fleets, within_runs=within_runs,