mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-02 10:34:55 +00:00
fix toolbar responsiveness and disable side panel
This commit is contained in:
@@ -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'`)
|
||||
|
||||
@@ -26,7 +26,7 @@ type LauncherGroupListProps = {
|
||||
};
|
||||
resourcesByGroupKey: Record<string, LauncherGroupResources>;
|
||||
onClearFilters?: () => void;
|
||||
onResourceSelect: (resource: LauncherResource) => void;
|
||||
onResourceSelect?: (resource: LauncherResource) => void;
|
||||
};
|
||||
|
||||
function hasActiveLauncherFilters(config: LauncherViewConfig): boolean {
|
||||
|
||||
@@ -40,7 +40,7 @@ type LauncherGroupSectionProps = {
|
||||
pageSize: number;
|
||||
};
|
||||
defaultOpen?: boolean;
|
||||
onResourceSelect: (resource: LauncherResource) => void;
|
||||
onResourceSelect?: (resource: LauncherResource) => void;
|
||||
};
|
||||
|
||||
export function LauncherGroupSection({
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
className={cn(
|
||||
"flex min-w-0 flex-col gap-2.5 overflow-hidden rounded-xl border border-border bg-background p-4",
|
||||
clickProps.className
|
||||
clickProps?.className
|
||||
)}
|
||||
onClick={clickProps.onClick}
|
||||
onKeyDown={clickProps.onKeyDown}
|
||||
role={clickProps.role}
|
||||
tabIndex={clickProps.tabIndex}
|
||||
onClick={clickProps?.onClick}
|
||||
onKeyDown={clickProps?.onKeyDown}
|
||||
role={clickProps?.role}
|
||||
tabIndex={clickProps?.tabIndex}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
@@ -6,7 +6,7 @@ import { LauncherResourceCard } from "./LauncherResourceCard";
|
||||
type LauncherResourceGridProps = {
|
||||
resources: LauncherResource[];
|
||||
showLabels: boolean;
|
||||
onResourceSelect: (resource: LauncherResource) => 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
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-2.5 p-4 max-md:min-w-max max-md:whitespace-nowrap",
|
||||
isLast ? undefined : "border-b border-border",
|
||||
clickProps.className
|
||||
clickProps?.className
|
||||
)}
|
||||
onClick={clickProps.onClick}
|
||||
onKeyDown={clickProps.onKeyDown}
|
||||
role={clickProps.role}
|
||||
tabIndex={clickProps.tabIndex}
|
||||
onClick={clickProps?.onClick}
|
||||
onKeyDown={clickProps?.onKeyDown}
|
||||
role={clickProps?.role}
|
||||
tabIndex={clickProps?.tabIndex}
|
||||
>
|
||||
<LauncherResourceIcon
|
||||
iconUrl={resource.iconUrl}
|
||||
|
||||
@@ -37,7 +37,7 @@ export function LauncherViewTabs({
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 overflow-x-auto max-w-full shrink min-w-0">
|
||||
<div className="flex w-max items-center gap-2">
|
||||
{viewOptions.map((option) => {
|
||||
const isSelected = activeViewId === option.value;
|
||||
return (
|
||||
|
||||
@@ -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<LauncherResource | null>(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) => (
|
||||
<div className={cn("relative shrink-0", searchClassName)}>
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||
<Input
|
||||
key={`${activeViewId}-${searchInputResetKey}`}
|
||||
defaultValue={config.query}
|
||||
onChange={(event) => {
|
||||
const value = event.currentTarget.value;
|
||||
searchInputRef.current = value;
|
||||
debouncedNavigateSearch(activeViewIdRef.current, value);
|
||||
}}
|
||||
placeholder={t("resourceLauncherSearchPlaceholder")}
|
||||
className="pl-8"
|
||||
type="search"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderToolbarActions = () => (
|
||||
<>
|
||||
<LauncherSaveViewMenu
|
||||
isDefaultView={isDefaultView}
|
||||
isAdmin={isAdmin}
|
||||
isOrgWideView={isOrgWideView}
|
||||
hasUnsavedChanges={hasUnsavedChanges}
|
||||
onSaveToCurrent={handleSaveToCurrent}
|
||||
onSaveAsNew={handleSaveAsNew}
|
||||
onSaveForEveryone={handleSaveForEveryone}
|
||||
onMakePersonal={handleMakePersonal}
|
||||
onResetView={handleResetView}
|
||||
/>
|
||||
<LauncherFilterPopover
|
||||
orgId={orgId}
|
||||
selectedSites={selectedSites}
|
||||
selectedLabels={selectedLabels}
|
||||
onSitesChange={(sites) =>
|
||||
applyConfigPatch({
|
||||
siteIds: sites.map((site) => site.siteId)
|
||||
})
|
||||
}
|
||||
onLabelsChange={(labels) =>
|
||||
applyConfigPatch({
|
||||
labelIds: labels.map((label) => label.labelId)
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LauncherSortButton
|
||||
order={config.order}
|
||||
onToggle={() =>
|
||||
applyConfigPatch({
|
||||
order: config.order === "asc" ? "desc" : "asc"
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LauncherSettingsMenu
|
||||
config={config}
|
||||
isDefaultView={isDefaultView}
|
||||
onConfigChange={applyConfigPatch}
|
||||
onDeleteView={() => {
|
||||
if (!isDefaultView) {
|
||||
deleteViewMutation.mutate(activeViewId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<LauncherRefreshButton
|
||||
onRefresh={refreshData}
|
||||
isRefreshing={isRefreshing || isNavigating}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const renderToolbarViews = () => (
|
||||
<LauncherViewTabs
|
||||
activeViewId={activeViewId}
|
||||
savedViews={savedViewTabs}
|
||||
onSelectView={selectView}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col" aria-busy={isNavigating}>
|
||||
<SettingsSectionTitle
|
||||
@@ -399,93 +483,27 @@ export default function ResourceLauncher({
|
||||
description={t("resourceLauncherDescription")}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col gap-3 mb-6">
|
||||
<div className="flex flex-row items-center gap-3 justify-between max-md:flex-col max-md:items-stretch">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-3 min-w-0 flex-1 max-md:order-2">
|
||||
<div className="relative w-full sm:max-w-sm shrink-0">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||
<Input
|
||||
key={`${activeViewId}-${searchInputResetKey}`}
|
||||
defaultValue={config.query}
|
||||
onChange={(event) => {
|
||||
const value = event.currentTarget.value;
|
||||
searchInputRef.current = value;
|
||||
debouncedNavigateSearch(
|
||||
activeViewIdRef.current,
|
||||
value
|
||||
);
|
||||
}}
|
||||
placeholder={t(
|
||||
"resourceLauncherSearchPlaceholder"
|
||||
)}
|
||||
className="pl-8"
|
||||
type="search"
|
||||
/>
|
||||
</div>
|
||||
<LauncherViewTabs
|
||||
activeViewId={activeViewId}
|
||||
savedViews={views.map((view) => ({
|
||||
viewId: view.viewId,
|
||||
name: view.name
|
||||
}))}
|
||||
onSelectView={selectView}
|
||||
/>
|
||||
{isDesktop ? (
|
||||
<div className="mb-6 flex w-full min-w-0 items-center gap-3">
|
||||
{renderToolbarSearch("w-64")}
|
||||
<div className="min-w-0 flex-1 overflow-x-auto">
|
||||
{renderToolbarViews()}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0 justify-end max-md:order-1 max-md:justify-start">
|
||||
<LauncherSaveViewMenu
|
||||
isDefaultView={isDefaultView}
|
||||
isAdmin={isAdmin}
|
||||
isOrgWideView={isOrgWideView}
|
||||
hasUnsavedChanges={hasUnsavedChanges}
|
||||
onSaveToCurrent={handleSaveToCurrent}
|
||||
onSaveAsNew={handleSaveAsNew}
|
||||
onSaveForEveryone={handleSaveForEveryone}
|
||||
onMakePersonal={handleMakePersonal}
|
||||
onResetView={handleResetView}
|
||||
/>
|
||||
<LauncherFilterPopover
|
||||
orgId={orgId}
|
||||
selectedSites={selectedSites}
|
||||
selectedLabels={selectedLabels}
|
||||
onSitesChange={(sites) =>
|
||||
applyConfigPatch({
|
||||
siteIds: sites.map((site) => site.siteId)
|
||||
})
|
||||
}
|
||||
onLabelsChange={(labels) =>
|
||||
applyConfigPatch({
|
||||
labelIds: labels.map(
|
||||
(label) => label.labelId
|
||||
)
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LauncherSortButton
|
||||
order={config.order}
|
||||
onToggle={() =>
|
||||
applyConfigPatch({
|
||||
order:
|
||||
config.order === "asc" ? "desc" : "asc"
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LauncherSettingsMenu
|
||||
config={config}
|
||||
isDefaultView={isDefaultView}
|
||||
onConfigChange={applyConfigPatch}
|
||||
onDeleteView={() => {
|
||||
if (!isDefaultView) {
|
||||
deleteViewMutation.mutate(activeViewId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<LauncherRefreshButton
|
||||
onRefresh={refreshData}
|
||||
isRefreshing={isRefreshing || isNavigating}
|
||||
/>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{renderToolbarActions()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-6 flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 overflow-x-auto">
|
||||
{renderToolbarActions()}
|
||||
</div>
|
||||
{renderToolbarSearch("w-full")}
|
||||
<div className="overflow-x-auto">
|
||||
{renderToolbarViews()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<LauncherGroupList
|
||||
orgId={orgId}
|
||||
@@ -495,19 +513,6 @@ export default function ResourceLauncher({
|
||||
groupsPagination={groupsPagination}
|
||||
resourcesByGroupKey={resourcesByGroupKey}
|
||||
onClearFilters={handleClearFilters}
|
||||
onResourceSelect={setSelectedResource}
|
||||
/>
|
||||
|
||||
<LauncherResourcePanel
|
||||
open={selectedResource != null}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setSelectedResource(null);
|
||||
}
|
||||
}}
|
||||
resource={selectedResource}
|
||||
orgId={orgId}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
|
||||
<Credenza open={saveDialogOpen} onOpenChange={setSaveDialogOpen}>
|
||||
|
||||
Reference in New Issue
Block a user