mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-02 10:34:55 +00:00
show sites and labels for non admins
This commit is contained in:
@@ -468,6 +468,18 @@ authenticated.get(
|
||||
launcher.listLauncherResources
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/launcher/sites",
|
||||
verifyOrgAccess,
|
||||
launcher.listLauncherSites
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/launcher/labels",
|
||||
verifyOrgAccess,
|
||||
launcher.listLauncherLabels
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/launcher/views",
|
||||
verifyOrgAccess,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export * from "./types";
|
||||
export { listLauncherGroups } from "./listLauncherGroups";
|
||||
export { listLauncherResources } from "./listLauncherResources";
|
||||
export { listLauncherSites } from "./listLauncherSites";
|
||||
export { listLauncherLabels } from "./listLauncherLabels";
|
||||
export { listLauncherViews } from "./listLauncherViews";
|
||||
export { createLauncherView } from "./createLauncherView";
|
||||
export { updateLauncherView } from "./updateLauncherView";
|
||||
|
||||
@@ -39,10 +39,12 @@ import {
|
||||
import {
|
||||
LAUNCHER_NO_SITE_GROUP_KEY,
|
||||
LAUNCHER_UNLABELED_GROUP_KEY,
|
||||
type LauncherFilterListQuery,
|
||||
type LauncherGroup,
|
||||
type LauncherLabel,
|
||||
type LauncherListQuery,
|
||||
type LauncherResource,
|
||||
type LauncherSiteInfo,
|
||||
parseIdListParam
|
||||
} from "./types";
|
||||
|
||||
@@ -1138,3 +1140,261 @@ export async function listLauncherResourcesForUser(
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
function buildSiteNameSearchCondition(query: string) {
|
||||
if (!query.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const pattern = searchPattern(query.toLowerCase());
|
||||
return or(
|
||||
like(sql`LOWER(${sites.name})`, pattern),
|
||||
like(sql`LOWER(${sites.niceId})`, pattern)
|
||||
);
|
||||
}
|
||||
|
||||
function buildLabelNameSearchCondition(query: string) {
|
||||
if (!query.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
const pattern = searchPattern(query.toLowerCase());
|
||||
return like(sql`LOWER(${labels.name})`, pattern);
|
||||
}
|
||||
|
||||
async function collectAccessibleSites(
|
||||
orgId: string,
|
||||
accessible: AccessibleIds,
|
||||
siteNameSearch?: ReturnType<typeof buildSiteNameSearchCondition>
|
||||
): Promise<Map<number, SiteGroupRow>> {
|
||||
const siteCountMap = new Map<number, SiteGroupRow>();
|
||||
|
||||
if (accessible.resourceIds.length > 0) {
|
||||
const publicConditions = [
|
||||
inArray(resources.resourceId, accessible.resourceIds),
|
||||
eq(resources.orgId, orgId),
|
||||
eq(resources.enabled, true)
|
||||
];
|
||||
if (siteNameSearch) {
|
||||
publicConditions.push(siteNameSearch);
|
||||
}
|
||||
|
||||
const publicRows = await db
|
||||
.select({
|
||||
siteId: sites.siteId,
|
||||
name: sites.name,
|
||||
type: sites.type,
|
||||
online: sites.online,
|
||||
itemCount: countDistinct(resources.resourceId)
|
||||
})
|
||||
.from(targets)
|
||||
.innerJoin(resources, eq(targets.resourceId, resources.resourceId))
|
||||
.innerJoin(sites, eq(targets.siteId, sites.siteId))
|
||||
.where(and(...publicConditions))
|
||||
.groupBy(sites.siteId, sites.name, sites.type, sites.online);
|
||||
|
||||
for (const row of publicRows) {
|
||||
const existing = siteCountMap.get(row.siteId);
|
||||
if (existing) {
|
||||
existing.itemCount += Number(row.itemCount);
|
||||
} else {
|
||||
siteCountMap.set(row.siteId, {
|
||||
siteId: row.siteId,
|
||||
name: row.name,
|
||||
type: row.type,
|
||||
online: row.online,
|
||||
itemCount: Number(row.itemCount)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (accessible.siteResourceIds.length > 0) {
|
||||
const siteConditions = [
|
||||
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
||||
eq(siteResources.orgId, orgId),
|
||||
eq(siteResources.enabled, true)
|
||||
];
|
||||
if (siteNameSearch) {
|
||||
siteConditions.push(siteNameSearch);
|
||||
}
|
||||
|
||||
const siteRows = await db
|
||||
.select({
|
||||
siteId: sites.siteId,
|
||||
name: sites.name,
|
||||
type: sites.type,
|
||||
online: sites.online,
|
||||
itemCount: countDistinct(siteResources.siteResourceId)
|
||||
})
|
||||
.from(siteResources)
|
||||
.innerJoin(
|
||||
siteNetworks,
|
||||
eq(siteResources.networkId, siteNetworks.networkId)
|
||||
)
|
||||
.innerJoin(sites, eq(siteNetworks.siteId, sites.siteId))
|
||||
.where(and(...siteConditions))
|
||||
.groupBy(sites.siteId, sites.name, sites.type, sites.online);
|
||||
|
||||
for (const row of siteRows) {
|
||||
const existing = siteCountMap.get(row.siteId);
|
||||
if (existing) {
|
||||
existing.itemCount += Number(row.itemCount);
|
||||
} else {
|
||||
siteCountMap.set(row.siteId, {
|
||||
siteId: row.siteId,
|
||||
name: row.name,
|
||||
type: row.type,
|
||||
online: row.online,
|
||||
itemCount: Number(row.itemCount)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return siteCountMap;
|
||||
}
|
||||
|
||||
async function collectAccessibleLabels(
|
||||
orgId: string,
|
||||
accessible: AccessibleIds,
|
||||
labelNameSearch?: ReturnType<typeof buildLabelNameSearchCondition>
|
||||
): Promise<Map<number, LauncherLabel>> {
|
||||
const labelMap = new Map<number, LauncherLabel>();
|
||||
|
||||
if (!(await labelsEnabled(orgId))) {
|
||||
return labelMap;
|
||||
}
|
||||
|
||||
if (accessible.resourceIds.length > 0) {
|
||||
const publicConditions = [
|
||||
inArray(resources.resourceId, accessible.resourceIds),
|
||||
eq(resources.orgId, orgId),
|
||||
eq(resources.enabled, true),
|
||||
eq(labels.orgId, orgId)
|
||||
];
|
||||
if (labelNameSearch) {
|
||||
publicConditions.push(labelNameSearch);
|
||||
}
|
||||
|
||||
const labeledPublic = await db
|
||||
.select({
|
||||
labelId: labels.labelId,
|
||||
name: labels.name,
|
||||
color: labels.color
|
||||
})
|
||||
.from(resourceLabels)
|
||||
.innerJoin(labels, eq(resourceLabels.labelId, labels.labelId))
|
||||
.innerJoin(
|
||||
resources,
|
||||
eq(resourceLabels.resourceId, resources.resourceId)
|
||||
)
|
||||
.where(and(...publicConditions))
|
||||
.groupBy(labels.labelId, labels.name, labels.color);
|
||||
|
||||
for (const row of labeledPublic) {
|
||||
labelMap.set(row.labelId, {
|
||||
labelId: row.labelId,
|
||||
name: row.name,
|
||||
color: row.color
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (accessible.siteResourceIds.length > 0) {
|
||||
const siteConditions = [
|
||||
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
|
||||
eq(siteResources.orgId, orgId),
|
||||
eq(siteResources.enabled, true),
|
||||
eq(labels.orgId, orgId)
|
||||
];
|
||||
if (labelNameSearch) {
|
||||
siteConditions.push(labelNameSearch);
|
||||
}
|
||||
|
||||
const labeledSite = await db
|
||||
.select({
|
||||
labelId: labels.labelId,
|
||||
name: labels.name,
|
||||
color: labels.color
|
||||
})
|
||||
.from(siteResourceLabels)
|
||||
.innerJoin(labels, eq(siteResourceLabels.labelId, labels.labelId))
|
||||
.innerJoin(
|
||||
siteResources,
|
||||
eq(
|
||||
siteResourceLabels.siteResourceId,
|
||||
siteResources.siteResourceId
|
||||
)
|
||||
)
|
||||
.where(and(...siteConditions))
|
||||
.groupBy(labels.labelId, labels.name, labels.color);
|
||||
|
||||
for (const row of labeledSite) {
|
||||
labelMap.set(row.labelId, {
|
||||
labelId: row.labelId,
|
||||
name: row.name,
|
||||
color: row.color
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return labelMap;
|
||||
}
|
||||
|
||||
export async function listAccessibleLauncherSitesForUser(
|
||||
orgId: string,
|
||||
userId: string,
|
||||
userRoleIds: number[],
|
||||
query: LauncherFilterListQuery
|
||||
): Promise<{ sites: LauncherSiteInfo[]; total: number }> {
|
||||
const accessible = await resolveAccessibleIds(orgId, userId, userRoleIds);
|
||||
const siteNameSearch = buildSiteNameSearchCondition(query.query);
|
||||
const siteCountMap = await collectAccessibleSites(
|
||||
orgId,
|
||||
accessible,
|
||||
siteNameSearch
|
||||
);
|
||||
|
||||
const sites: LauncherSiteInfo[] = Array.from(siteCountMap.values())
|
||||
.map((row) => ({
|
||||
siteId: row.siteId,
|
||||
name: row.name,
|
||||
type: row.type,
|
||||
online: row.online
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
a.name.localeCompare(b.name, undefined, { sensitivity: "base" })
|
||||
);
|
||||
|
||||
const total = sites.length;
|
||||
const offset = (query.page - 1) * query.pageSize;
|
||||
return {
|
||||
sites: sites.slice(offset, offset + query.pageSize),
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
export async function listAccessibleLauncherLabelsForUser(
|
||||
orgId: string,
|
||||
userId: string,
|
||||
userRoleIds: number[],
|
||||
query: LauncherFilterListQuery
|
||||
): Promise<{ labels: LauncherLabel[]; total: number }> {
|
||||
const accessible = await resolveAccessibleIds(orgId, userId, userRoleIds);
|
||||
const labelNameSearch = buildLabelNameSearchCondition(query.query);
|
||||
const labelMap = await collectAccessibleLabels(
|
||||
orgId,
|
||||
accessible,
|
||||
labelNameSearch
|
||||
);
|
||||
|
||||
const labelsList = Array.from(labelMap.values()).sort((a, b) =>
|
||||
a.name.localeCompare(b.name, undefined, { sensitivity: "base" })
|
||||
);
|
||||
|
||||
const total = labelsList.length;
|
||||
const offset = (query.page - 1) * query.pageSize;
|
||||
return {
|
||||
labels: labelsList.slice(offset, offset + query.pageSize),
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
67
server/routers/launcher/listLauncherLabels.ts
Normal file
67
server/routers/launcher/listLauncherLabels.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { response } from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { fromZodError } from "zod-validation-error";
|
||||
import { listAccessibleLauncherLabelsForUser } from "./launcherResourceAccess";
|
||||
import { launcherFilterListQuerySchema } from "./types";
|
||||
|
||||
export async function listLauncherLabels(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const orgId = req.userOrgId;
|
||||
const userId = req.user!.userId;
|
||||
|
||||
if (!orgId) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID")
|
||||
);
|
||||
}
|
||||
|
||||
const parsed = launcherFilterListQuerySchema.safeParse(req.query);
|
||||
if (!parsed.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromZodError(parsed.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { labels, total } = await listAccessibleLauncherLabelsForUser(
|
||||
orgId,
|
||||
userId,
|
||||
req.userOrgRoleIds ?? [],
|
||||
parsed.data
|
||||
);
|
||||
|
||||
return response(res, {
|
||||
data: {
|
||||
labels,
|
||||
pagination: {
|
||||
total,
|
||||
page: parsed.data.page,
|
||||
pageSize: parsed.data.pageSize
|
||||
}
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Launcher labels retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
if (createHttpError.isHttpError(error)) {
|
||||
return next(error);
|
||||
}
|
||||
console.error("Error listing launcher labels:", error);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Internal server error"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
67
server/routers/launcher/listLauncherSites.ts
Normal file
67
server/routers/launcher/listLauncherSites.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { response } from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import createHttpError from "http-errors";
|
||||
import { fromZodError } from "zod-validation-error";
|
||||
import { listAccessibleLauncherSitesForUser } from "./launcherResourceAccess";
|
||||
import { launcherFilterListQuerySchema } from "./types";
|
||||
|
||||
export async function listLauncherSites(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const orgId = req.userOrgId;
|
||||
const userId = req.user!.userId;
|
||||
|
||||
if (!orgId) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID")
|
||||
);
|
||||
}
|
||||
|
||||
const parsed = launcherFilterListQuerySchema.safeParse(req.query);
|
||||
if (!parsed.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromZodError(parsed.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { sites, total } = await listAccessibleLauncherSitesForUser(
|
||||
orgId,
|
||||
userId,
|
||||
req.userOrgRoleIds ?? [],
|
||||
parsed.data
|
||||
);
|
||||
|
||||
return response(res, {
|
||||
data: {
|
||||
sites,
|
||||
pagination: {
|
||||
total,
|
||||
page: parsed.data.page,
|
||||
pageSize: parsed.data.pageSize
|
||||
}
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Launcher sites retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
if (createHttpError.isHttpError(error)) {
|
||||
return next(error);
|
||||
}
|
||||
console.error("Error listing launcher sites:", error);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Internal server error"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,40 @@ export type ListLauncherViewsResponse = {
|
||||
views: LauncherViewRecord[];
|
||||
};
|
||||
|
||||
export const launcherFilterListQuerySchema = z.strictObject({
|
||||
pageSize: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.catch(500)
|
||||
.default(500),
|
||||
page: z.coerce.number().int().min(1).optional().catch(1).default(1),
|
||||
query: z.string().optional().default("")
|
||||
});
|
||||
|
||||
export type LauncherFilterListQuery = z.infer<
|
||||
typeof launcherFilterListQuerySchema
|
||||
>;
|
||||
|
||||
export type ListLauncherSitesResponse = {
|
||||
sites: LauncherSiteInfo[];
|
||||
pagination: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type ListLauncherLabelsResponse = {
|
||||
labels: LauncherLabel[];
|
||||
pagination: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
};
|
||||
};
|
||||
|
||||
export const launcherListQuerySchema = z.strictObject({
|
||||
pageSize: z.coerce
|
||||
.number()
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
CommandItem,
|
||||
CommandList
|
||||
} from "@app/components/ui/command";
|
||||
import { orgQueries } from "@app/lib/queries";
|
||||
import { launcherQueries, orgQueries } from "@app/lib/queries";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
@@ -27,6 +27,7 @@ type LabelsFilterSelectorProps = {
|
||||
onToggle: (label: LabelFilterOption) => void;
|
||||
onClear?: () => void;
|
||||
showClear?: boolean;
|
||||
scope?: "org" | "launcher";
|
||||
};
|
||||
|
||||
export function LabelsFilterSelector({
|
||||
@@ -34,19 +35,33 @@ export function LabelsFilterSelector({
|
||||
isSelected,
|
||||
onToggle,
|
||||
onClear,
|
||||
showClear = false
|
||||
showClear = false,
|
||||
scope = "org"
|
||||
}: LabelsFilterSelectorProps) {
|
||||
const t = useTranslations();
|
||||
const [labelSearchQuery, setlabelsSearchQuery] = useState("");
|
||||
const [debouncedQuery] = useDebounce(labelSearchQuery, 150);
|
||||
|
||||
const { data: labels = [] } = useQuery(
|
||||
orgQueries.labels({
|
||||
const orgLabelsQuery = useQuery({
|
||||
...orgQueries.labels({
|
||||
orgId,
|
||||
query: debouncedQuery,
|
||||
perPage: 500
|
||||
})
|
||||
);
|
||||
}),
|
||||
enabled: scope === "org"
|
||||
});
|
||||
const launcherLabelsQuery = useQuery({
|
||||
...launcherQueries.labels({
|
||||
orgId,
|
||||
query: debouncedQuery,
|
||||
perPage: 500
|
||||
}),
|
||||
enabled: scope === "launcher"
|
||||
});
|
||||
const labels =
|
||||
scope === "launcher"
|
||||
? (launcherLabelsQuery.data ?? [])
|
||||
: (orgLabelsQuery.data ?? []);
|
||||
|
||||
return (
|
||||
<Command shouldFilter={false}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { orgQueries } from "@app/lib/queries";
|
||||
import { launcherQueries, orgQueries } from "@app/lib/queries";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
@@ -19,6 +19,7 @@ export type MultiSitesSelectorProps = {
|
||||
selectedSites: Selectedsite[];
|
||||
onSelectionChange: (sites: Selectedsite[]) => void;
|
||||
filterTypes?: string[];
|
||||
scope?: "org" | "launcher";
|
||||
};
|
||||
|
||||
export function formatMultiSitesSelectorLabel(
|
||||
@@ -40,19 +41,33 @@ export function MultiSitesSelector({
|
||||
orgId,
|
||||
selectedSites,
|
||||
onSelectionChange,
|
||||
filterTypes
|
||||
filterTypes,
|
||||
scope = "org"
|
||||
}: MultiSitesSelectorProps) {
|
||||
const t = useTranslations();
|
||||
const [siteSearchQuery, setSiteSearchQuery] = useState("");
|
||||
const [debouncedQuery] = useDebounce(siteSearchQuery, 150);
|
||||
|
||||
const { data: sites = [] } = useQuery(
|
||||
orgQueries.sites({
|
||||
const orgSitesQuery = useQuery({
|
||||
...orgQueries.sites({
|
||||
orgId,
|
||||
query: debouncedQuery,
|
||||
perPage: 10
|
||||
})
|
||||
);
|
||||
}),
|
||||
enabled: scope === "org"
|
||||
});
|
||||
const launcherSitesQuery = useQuery({
|
||||
...launcherQueries.sites({
|
||||
orgId,
|
||||
query: debouncedQuery,
|
||||
perPage: 500
|
||||
}),
|
||||
enabled: scope === "launcher"
|
||||
});
|
||||
const sites =
|
||||
scope === "launcher"
|
||||
? (launcherSitesQuery.data ?? [])
|
||||
: (orgSitesQuery.data ?? []);
|
||||
|
||||
const sitesShown = useMemo(() => {
|
||||
const base = filterTypes
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
PopoverTrigger
|
||||
} from "@app/components/ui/popover";
|
||||
import { cn } from "@app/lib/cn";
|
||||
import { orgQueries } from "@app/lib/queries";
|
||||
import { launcherQueries } from "@app/lib/queries";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ChevronsUpDown, Funnel } from "lucide-react";
|
||||
@@ -44,12 +44,37 @@ export function LauncherFilterPopover({
|
||||
const [labelsOpen, setLabelsOpen] = useState(false);
|
||||
|
||||
const { data: labels = [] } = useQuery(
|
||||
orgQueries.labels({
|
||||
launcherQueries.labels({
|
||||
orgId,
|
||||
perPage: 500
|
||||
})
|
||||
);
|
||||
|
||||
const { data: sites = [] } = useQuery(
|
||||
launcherQueries.sites({
|
||||
orgId,
|
||||
perPage: 500
|
||||
})
|
||||
);
|
||||
|
||||
const resolvedSelectedSites: Selectedsite[] = useMemo(
|
||||
() =>
|
||||
selectedSites.map((selected) => {
|
||||
const found = sites.find(
|
||||
(site) => site.siteId === selected.siteId
|
||||
);
|
||||
return found
|
||||
? {
|
||||
siteId: found.siteId,
|
||||
name: found.name,
|
||||
type: found.type,
|
||||
online: found.online
|
||||
}
|
||||
: selected;
|
||||
}),
|
||||
[sites, selectedSites]
|
||||
);
|
||||
|
||||
const selectedLabelIds = useMemo(
|
||||
() => new Set(selectedLabels.map((label) => label.labelId)),
|
||||
[selectedLabels]
|
||||
@@ -98,7 +123,7 @@ export function LauncherFilterPopover({
|
||||
>
|
||||
<span className="truncate text-left">
|
||||
{formatMultiSitesSelectorLabel(
|
||||
selectedSites,
|
||||
resolvedSelectedSites,
|
||||
t
|
||||
)}
|
||||
</span>
|
||||
@@ -111,8 +136,9 @@ export function LauncherFilterPopover({
|
||||
>
|
||||
<MultiSitesSelector
|
||||
orgId={orgId}
|
||||
selectedSites={selectedSites}
|
||||
selectedSites={resolvedSelectedSites}
|
||||
onSelectionChange={onSitesChange}
|
||||
scope="launcher"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -145,6 +171,7 @@ export function LauncherFilterPopover({
|
||||
>
|
||||
<LabelsFilterSelector
|
||||
orgId={orgId}
|
||||
scope="launcher"
|
||||
isSelected={(label) =>
|
||||
selectedLabelIds.has(label.labelId)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,9 @@ import type { ListResourcePoliciesResponse } from "@server/routers/resource/type
|
||||
import type { GetResourcePolicyResponse } from "@server/routers/policy";
|
||||
import type {
|
||||
ListLauncherGroupsResponse,
|
||||
ListLauncherLabelsResponse,
|
||||
ListLauncherResourcesResponse,
|
||||
ListLauncherSitesResponse,
|
||||
ListLauncherViewsResponse,
|
||||
LauncherListQuery,
|
||||
LauncherViewConfig
|
||||
@@ -1190,6 +1192,72 @@ export const launcherQueries = {
|
||||
return res.data.data.views;
|
||||
}
|
||||
}),
|
||||
sites: ({
|
||||
orgId,
|
||||
query,
|
||||
perPage = 500
|
||||
}: {
|
||||
orgId: string;
|
||||
query?: string;
|
||||
perPage?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: [
|
||||
"ORG",
|
||||
orgId,
|
||||
"LAUNCHER",
|
||||
"SITES",
|
||||
{ query, perPage }
|
||||
] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
const sp = new URLSearchParams({
|
||||
pageSize: perPage.toString()
|
||||
});
|
||||
|
||||
if (query?.trim()) {
|
||||
sp.set("query", query);
|
||||
}
|
||||
|
||||
const res = await meta!.api.get<
|
||||
AxiosResponse<ListLauncherSitesResponse>
|
||||
>(`/org/${orgId}/launcher/sites?${sp.toString()}`, { signal });
|
||||
return res.data.data.sites;
|
||||
}
|
||||
}),
|
||||
labels: ({
|
||||
orgId,
|
||||
query,
|
||||
perPage = 500
|
||||
}: {
|
||||
orgId: string;
|
||||
query?: string;
|
||||
perPage?: number;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: [
|
||||
"ORG",
|
||||
orgId,
|
||||
"LAUNCHER",
|
||||
"LABELS",
|
||||
{ query, perPage }
|
||||
] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
const sp = new URLSearchParams({
|
||||
pageSize: perPage.toString()
|
||||
});
|
||||
|
||||
if (query?.trim()) {
|
||||
sp.set("query", query);
|
||||
}
|
||||
|
||||
const res = await meta!.api.get<
|
||||
AxiosResponse<ListLauncherLabelsResponse>
|
||||
>(`/org/${orgId}/launcher/labels?${sp.toString()}`, {
|
||||
signal
|
||||
});
|
||||
return res.data.data.labels;
|
||||
}
|
||||
}),
|
||||
groups: (orgId: string, filters: LauncherQueryFilters) =>
|
||||
infiniteQueryOptions({
|
||||
queryKey: ["ORG", orgId, "LAUNCHER", "GROUPS", filters] as const,
|
||||
|
||||
Reference in New Issue
Block a user