diff --git a/server/setup/scriptsSqlite/1.13.0.ts b/server/setup/scriptsSqlite/1.13.0.ts index c4b49495f..a67b0fa14 100644 --- a/server/setup/scriptsSqlite/1.13.0.ts +++ b/server/setup/scriptsSqlite/1.13.0.ts @@ -271,6 +271,7 @@ export default async function migration() { `ALTER TABLE 'sites' DROP COLUMN 'remoteSubnets';` ).run(); +<<<<<<< Updated upstream // get all of the siteResources and set the aliasAddress to 100.96.128.x starting at .8 const siteResourcesForAlias = db .prepare( @@ -278,6 +279,15 @@ export default async function migration() { ) .all() as { siteResourceId: number; +======= + // Associate clients with site resources based on their previous site access + // Get all client-site associations from the renamed clientSitesAssociationsCache table + const clientSiteAssociations = db + .prepare(`SELECT clientId, siteId FROM 'clientSitesAssociationsCache'`) + .all() as { + clientId: number; + siteId: number; +>>>>>>> Stashed changes }[]; const updateAliasAddress = db.prepare( @@ -335,6 +345,7 @@ export default async function migration() { `INSERT INTO 'clientSiteResources' ('clientId', 'siteResourceId') VALUES (?, ?)` ); +<<<<<<< Updated upstream // create a clientSiteResourcesAssociationsCache entry for each existing association as well const insertClientSiteResourceCache = db.prepare( `INSERT INTO 'clientSiteResourcesAssociationsCache' ('clientId', 'siteResourceId') VALUES (?, ?)` @@ -345,6 +356,13 @@ export default async function migration() { const siteResources = getSiteResources.all( association.siteId ) as { +======= + // For each client-site association, find all site resources for that site + for (const association of clientSiteAssociations) { + const siteResources = db + .prepare(`SELECT siteResourceId FROM 'siteResources' WHERE siteId = ?`) + .all(association.siteId) as { +>>>>>>> Stashed changes siteResourceId: number; }[]; @@ -352,15 +370,25 @@ export default async function migration() { for (const siteResource of siteResources) { insertClientSiteResource.run( association.clientId, +<<<<<<< Updated upstream siteResource.siteResourceId ); insertClientSiteResourceCache.run( association.clientId, +======= +>>>>>>> Stashed changes siteResource.siteResourceId ); } } + // Get all clients for niceId population (used later) + const clients = db + .prepare(`SELECT clientId FROM 'clients'`) + .all() as { + clientId: number; + }[]; + // Associate existing site resources with their org's admin role const siteResourcesWithOrg = db .prepare(`SELECT siteResourceId, orgId FROM 'siteResources'`) diff --git a/src/components/resource-launcher/LauncherGroupList.tsx b/src/components/resource-launcher/LauncherGroupList.tsx index a631aff6c..bb3418f8d 100644 --- a/src/components/resource-launcher/LauncherGroupList.tsx +++ b/src/components/resource-launcher/LauncherGroupList.tsx @@ -26,7 +26,7 @@ type LauncherGroupListProps = { }; resourcesByGroupKey: Record; onClearFilters?: () => void; - onResourceSelect: (resource: LauncherResource) => void; + onResourceSelect?: (resource: LauncherResource) => void; }; function hasActiveLauncherFilters(config: LauncherViewConfig): boolean { diff --git a/src/components/resource-launcher/LauncherGroupSection.tsx b/src/components/resource-launcher/LauncherGroupSection.tsx index 1d4881835..49b648032 100644 --- a/src/components/resource-launcher/LauncherGroupSection.tsx +++ b/src/components/resource-launcher/LauncherGroupSection.tsx @@ -40,7 +40,7 @@ type LauncherGroupSectionProps = { pageSize: number; }; defaultOpen?: boolean; - onResourceSelect: (resource: LauncherResource) => void; + onResourceSelect?: (resource: LauncherResource) => void; }; export function LauncherGroupSection({ diff --git a/src/components/resource-launcher/LauncherResourceCard.tsx b/src/components/resource-launcher/LauncherResourceCard.tsx index 71e9cbe8b..ac891e84b 100644 --- a/src/components/resource-launcher/LauncherResourceCard.tsx +++ b/src/components/resource-launcher/LauncherResourceCard.tsx @@ -10,7 +10,7 @@ import { getLauncherResourceSelectProps } from "./useLauncherResourceAction"; type LauncherResourceCardProps = { resource: LauncherResource; showLabels: boolean; - onSelect: () => void; + onSelect?: () => void; }; export function LauncherResourceCard({ @@ -19,18 +19,20 @@ export function LauncherResourceCard({ onSelect }: LauncherResourceCardProps) { const hasIcon = Boolean(resource.iconUrl); - const clickProps = getLauncherResourceSelectProps(onSelect); + const clickProps = onSelect + ? getLauncherResourceSelectProps(onSelect) + : null; return (
void; + onResourceSelect?: (resource: LauncherResource) => void; }; export function LauncherResourceGrid({ @@ -21,7 +21,11 @@ export function LauncherResourceGrid({ key={resource.launcherResourceKey} resource={resource} showLabels={showLabels} - onSelect={() => onResourceSelect(resource)} + onSelect={ + onResourceSelect + ? () => onResourceSelect(resource) + : undefined + } /> ))}
diff --git a/src/components/resource-launcher/LauncherResourceList.tsx b/src/components/resource-launcher/LauncherResourceList.tsx index e64e2b7b0..555d112a3 100644 --- a/src/components/resource-launcher/LauncherResourceList.tsx +++ b/src/components/resource-launcher/LauncherResourceList.tsx @@ -6,7 +6,7 @@ import { LauncherResourceRow } from "./LauncherResourceRow"; type LauncherResourceListProps = { resources: LauncherResource[]; showLabels: boolean; - onResourceSelect: (resource: LauncherResource) => void; + onResourceSelect?: (resource: LauncherResource) => void; }; export function LauncherResourceList({ @@ -23,7 +23,11 @@ export function LauncherResourceList({ resource={resource} showLabels={showLabels} isLast={index === resources.length - 1} - onSelect={() => onResourceSelect(resource)} + onSelect={ + onResourceSelect + ? () => onResourceSelect(resource) + : undefined + } /> ))}
diff --git a/src/components/resource-launcher/LauncherResourceRow.tsx b/src/components/resource-launcher/LauncherResourceRow.tsx index 34558de51..eca882c56 100644 --- a/src/components/resource-launcher/LauncherResourceRow.tsx +++ b/src/components/resource-launcher/LauncherResourceRow.tsx @@ -11,7 +11,7 @@ type LauncherResourceRowProps = { resource: LauncherResource; showLabels: boolean; isLast?: boolean; - onSelect: () => void; + onSelect?: () => void; }; export function LauncherResourceRow({ @@ -21,19 +21,21 @@ export function LauncherResourceRow({ onSelect }: LauncherResourceRowProps) { const hasTags = showLabels && resource.labels.length > 0; - const clickProps = getLauncherResourceSelectProps(onSelect); + const clickProps = onSelect + ? getLauncherResourceSelectProps(onSelect) + : null; return (
+
{viewOptions.map((option) => { const isSelected = activeViewId === option.value; return ( diff --git a/src/components/resource-launcher/ResourceLauncher.tsx b/src/components/resource-launcher/ResourceLauncher.tsx index 10b9df847..6503de169 100644 --- a/src/components/resource-launcher/ResourceLauncher.tsx +++ b/src/components/resource-launcher/ResourceLauncher.tsx @@ -32,7 +32,6 @@ import { useToast } from "@app/hooks/useToast"; import { useEnvContext } from "@app/hooks/useEnvContext"; import type { LauncherGroup, - LauncherResource, LauncherViewConfig, LauncherViewRecord } from "@server/routers/launcher/types"; @@ -51,12 +50,13 @@ import { import { useDebouncedCallback } from "use-debounce"; import type { Selectedsite } from "@app/components/site-selector"; import type { SelectedLabel } from "@app/components/labels-selector"; +import { useMediaQuery } from "@app/hooks/useMediaQuery"; +import { cn } from "@app/lib/cn"; import { LauncherFilterPopover } from "./LauncherFilterPopover"; import { LauncherGroupList } from "./LauncherGroupList"; import { LauncherRefreshButton } from "./LauncherRefreshButton"; import { LauncherSettingsMenu } from "./LauncherSettingsMenu"; import { LauncherSortButton } from "./LauncherSortButton"; -import { LauncherResourcePanel } from "./LauncherResourcePanel"; import { LauncherSaveViewMenu, LauncherViewTabs } from "./LauncherViewTabs"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; @@ -98,11 +98,11 @@ export default function ResourceLauncher({ const [searchInputResetKey, setSearchInputResetKey] = useState(0); const [saveDialogOpen, setSaveDialogOpen] = useState(false); - const [selectedResource, setSelectedResource] = - useState(null); const [newViewName, setNewViewName] = useState(""); const [saveOrgWide, setSaveOrgWide] = useState(false); + const isDesktop = useMediaQuery("(min-width: 768px)"); + const configRef = useRef(config); configRef.current = config; const searchInputRef = useRef(config.query); @@ -392,6 +392,90 @@ export default function ResourceLauncher({ }); }; + const savedViewTabs = views.map((view) => ({ + viewId: view.viewId, + name: view.name + })); + + const renderToolbarSearch = (searchClassName: string) => ( +
+ + { + const value = event.currentTarget.value; + searchInputRef.current = value; + debouncedNavigateSearch(activeViewIdRef.current, value); + }} + placeholder={t("resourceLauncherSearchPlaceholder")} + className="pl-8" + type="search" + /> +
+ ); + + const renderToolbarActions = () => ( + <> + + + applyConfigPatch({ + siteIds: sites.map((site) => site.siteId) + }) + } + onLabelsChange={(labels) => + applyConfigPatch({ + labelIds: labels.map((label) => label.labelId) + }) + } + /> + + applyConfigPatch({ + order: config.order === "asc" ? "desc" : "asc" + }) + } + /> + { + if (!isDefaultView) { + deleteViewMutation.mutate(activeViewId); + } + }} + /> + + + ); + + const renderToolbarViews = () => ( + + ); + return (
-
-
-
-
- - { - const value = event.currentTarget.value; - searchInputRef.current = value; - debouncedNavigateSearch( - activeViewIdRef.current, - value - ); - }} - placeholder={t( - "resourceLauncherSearchPlaceholder" - )} - className="pl-8" - type="search" - /> -
- ({ - viewId: view.viewId, - name: view.name - }))} - onSelectView={selectView} - /> + {isDesktop ? ( +
+ {renderToolbarSearch("w-64")} +
+ {renderToolbarViews()}
-
- - - applyConfigPatch({ - siteIds: sites.map((site) => site.siteId) - }) - } - onLabelsChange={(labels) => - applyConfigPatch({ - labelIds: labels.map( - (label) => label.labelId - ) - }) - } - /> - - applyConfigPatch({ - order: - config.order === "asc" ? "desc" : "asc" - }) - } - /> - { - if (!isDefaultView) { - deleteViewMutation.mutate(activeViewId); - } - }} - /> - +
+ {renderToolbarActions()}
-
+ ) : ( +
+
+ {renderToolbarActions()} +
+ {renderToolbarSearch("w-full")} +
+ {renderToolbarViews()} +
+
+ )} - - { - if (!open) { - setSelectedResource(null); - } - }} - resource={selectedResource} - orgId={orgId} - isAdmin={isAdmin} />