): JSX.Element => {
const isTeamsSection = entityType === "teams";
const displayType = isTeamsSection ? "fleets" : entityType;
@@ -415,6 +418,11 @@ const SelectTargets = ({
return (
<>
{entityType && {capitalize(displayType)}
}
+ {isTeamsSection && !!disabledIds?.size && (
+
+ Results limited to fleets you can access.
+
+ )}
{isSearchEnabled && (
<>
t.id === entity.id)}
onClick={handleButtonSelect}
+ disabled={disabledIds?.has(entity.id)}
/>
);
})}
@@ -531,31 +540,46 @@ const SelectTargets = ({
const resultsTableConfig = generateTableHeaders();
const selectedHostsTableConfig = generateTableHeaders(handleRowRemove);
- // Filter out observer teams that break live query/policy API
- const filterTeamObserverTeams = () => {
- // API blocks live policy if a team level user is able to select the team they are an observer on
- if (isLivePolicy) {
- return (
- teams?.filter(
- (team) =>
- !permissions.isTeamObserver(currentUser, team.id) ||
- permissions.isTeamObserverPlus(currentUser, team.id)
- ) || []
- );
+ const shouldDisableForObserver = (teamId: number): boolean => {
+ if (isLivePolicy) return true;
+ if (!isObserverCanRunQuery) return true;
+ if (queryTeamId != null && queryTeamId !== teamId) return true;
+ return false;
+ };
+
+ const getDisabledTeamIds = (): Set => {
+ const disabled = new Set();
+
+ const isGlobalPlainObserver = currentUser?.global_role === "observer";
+
+ if (isGlobalPlainObserver) {
+ // Global plain observers have the same restrictions as team-level
+ // observers but applied to ALL teams/fleets (including "Unassigned")
+ const allTeamIds = [...(teams?.map((t) => t.id) || []), 0]; // 0 = "Unassigned"
+ allTeamIds.forEach((teamId) => {
+ if (shouldDisableForObserver(teamId)) {
+ disabled.add(teamId);
+ }
+ });
+ return disabled;
}
- // API blocks live query if a team level user is able to select the team they are an observer on
- // AND the query does not have observer can run enabled
- return (
- teams?.filter(
- (team) =>
- !permissions.isTeamObserver(currentUser, team.id) ||
- permissions.isTeamObserverPlus(currentUser, team.id) ||
- isObserverCanRunQuery
- ) || []
- );
+ // Team/fleet-level plain observer logic
+ teams?.forEach((team) => {
+ const isPlainObserver =
+ permissions.isTeamObserver(currentUser, team.id) &&
+ !permissions.isTeamObserverPlus(currentUser, team.id);
+ if (!isPlainObserver) return;
+
+ if (shouldDisableForObserver(team.id)) {
+ disabled.add(team.id);
+ }
+ });
+ return disabled;
};
+ const disabledTeamIds = getDisabledTeamIds();
+
if (isLoadingLabels || isLoadingTeams) {
return ;
}
@@ -570,11 +594,12 @@ const SelectTargets = ({
renderTargetEntitySection("Platforms", labels.platforms)}
{!!teams?.length &&
(isOnGlobalTeam
- ? renderTargetEntitySection("teams", [
- { id: 0, name: "Unassigned" },
- ...teams,
- ])
- : renderTargetEntitySection("teams", filterTeamObserverTeams()))}
+ ? renderTargetEntitySection(
+ "teams",
+ [{ id: 0, name: "Unassigned" }, ...teams],
+ disabledTeamIds
+ )
+ : renderTargetEntitySection("teams", teams, disabledTeamIds))}
{!!labels?.other?.length &&
renderTargetEntitySection("labels", labels.other)}
diff --git a/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tsx b/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tsx
index 96490f4544..0eb3a8d59a 100644
--- a/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tsx
+++ b/frontend/components/LiveQuery/TargetChipSelector/TargetChipSelector.tsx
@@ -16,6 +16,7 @@ interface ITargetChipSelectorProps {
onClick: (
value: ISelectLabel | ISelectTeam
) => React.MouseEventHandler;
+ disabled?: boolean;
}
const isBuiltInLabel = (
@@ -28,6 +29,7 @@ const TargetChipSelector = ({
entity,
isSelected,
onClick,
+ disabled,
}: ITargetChipSelectorProps): JSX.Element => {
const displayText = (): string => {
if (isBuiltInLabel(entity)) {
@@ -44,6 +46,7 @@ const TargetChipSelector = ({