@cytario/web 2.1.4 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -20
- package/app/.server/auth/README.md +8 -8
- package/app/.server/auth/authMiddleware.ts +9 -25
- package/app/.server/auth/exchangeAuthCode.ts +2 -6
- package/app/.server/auth/getS3Client.ts +3 -13
- package/app/.server/auth/getSessionCredentials.ts +6 -20
- package/app/.server/auth/getUserInfo.ts +2 -6
- package/app/.server/auth/keycloakAdmin/client.ts +2 -9
- package/app/.server/auth/keycloakAdmin/groups.ts +9 -26
- package/app/.server/auth/keycloakAdmin/users.ts +7 -23
- package/app/.server/auth/oauthState.ts +4 -13
- package/app/.server/auth/redirectIfAuthenticated.ts +1 -3
- package/app/.server/auth/refreshAuthTokens.ts +5 -19
- package/app/.server/auth/sessionMiddleware.ts +1 -4
- package/app/.server/auth/sessionStorage.ts +1 -4
- package/app/.server/auth/verifyIdToken.ts +1 -3
- package/app/.server/auth/wellKnownEndpoints.ts +1 -4
- package/app/.server/db/redis.ts +5 -1
- package/app/.server/logging.ts +1 -4
- package/app/.server/requestDurationMiddleware.ts +1 -4
- package/app/components/.client/ImageViewer/README.md +5 -5
- package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsController.tsx +7 -9
- package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerBrightfieldItem.tsx +1 -2
- package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItem.tsx +2 -5
- package/app/components/.client/ImageViewer/components/ChannelsController/ChannelsControllerItemList.tsx +7 -15
- package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorPicker.tsx +2 -9
- package/app/components/.client/ImageViewer/components/ChannelsController/ColorPicker/ColorSwatch.tsx +1 -4
- package/app/components/.client/ImageViewer/components/ChannelsController/DomainSlider.tsx +1 -3
- package/app/components/.client/ImageViewer/components/ChannelsController/Histogram.tsx +16 -18
- package/app/components/.client/ImageViewer/components/ChannelsController/HistogramChannel.tsx +2 -8
- package/app/components/.client/ImageViewer/components/ChannelsController/MinMaxSettings.tsx +6 -15
- package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarDragHandle.tsx +1 -5
- package/app/components/.client/ImageViewer/components/FeatureBar/FeatureBarToggle.tsx +1 -5
- package/app/components/.client/ImageViewer/components/FeatureBar/FeatureItem.tsx +1 -5
- package/app/components/.client/ImageViewer/components/FeatureBar/Presets.tsx +3 -11
- package/app/components/.client/ImageViewer/components/FeatureBar/useFeatureBar.tsx +16 -25
- package/app/components/.client/ImageViewer/components/Image/Channels/useChannelsLayer.ts +7 -18
- package/app/components/.client/ImageViewer/components/Image/ImageContainer.tsx +1 -1
- package/app/components/.client/ImageViewer/components/Image/ImagePanel.tsx +6 -26
- package/app/components/.client/ImageViewer/components/Image/ImagePreview.tsx +2 -9
- package/app/components/.client/ImageViewer/components/Image/Overlays/AdditivePolygonLayer.tsx +1 -5
- package/app/components/.client/ImageViewer/components/Image/Overlays/AdditiveScatterplotLayer.tsx +1 -5
- package/app/components/.client/ImageViewer/components/Image/Overlays/OverlaysLayer.tsx +6 -24
- package/app/components/.client/ImageViewer/components/Image/Overlays/markerUniforms.ts +2 -5
- package/app/components/.client/ImageViewer/components/Image/Overlays/useOverlaysLayer.tsx +7 -21
- package/app/components/.client/ImageViewer/components/Image/useInitializeChannels.ts +1 -7
- package/app/components/.client/ImageViewer/components/Image/useResizeObserver.ts +1 -1
- package/app/components/.client/ImageViewer/components/Magnifier.tsx +5 -13
- package/app/components/.client/ImageViewer/components/Measurements/ActiveViewStatePreview.tsx +3 -8
- package/app/components/.client/ImageViewer/components/Measurements/CursorTick.tsx +2 -7
- package/app/components/.client/ImageViewer/components/Measurements/Ruler.tsx +1 -8
- package/app/components/.client/ImageViewer/components/Measurements/SlideCarrier.tsx +3 -13
- package/app/components/.client/ImageViewer/components/Measurements/Tick.tsx +1 -1
- package/app/components/.client/ImageViewer/components/Measurements/calculateViewStateToFit.ts +1 -1
- package/app/components/.client/ImageViewer/components/Measurements/useMeasurements.ts +9 -28
- package/app/components/.client/ImageViewer/components/OverlaysController/AddOverlay.tsx +4 -13
- package/app/components/.client/ImageViewer/components/OverlaysController/OverlayPicker.modal.tsx +1 -6
- package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.Item.tsx +24 -54
- package/app/components/.client/ImageViewer/components/OverlaysController/OverlaysController.tsx +1 -3
- package/app/components/.client/ImageViewer/components/SplitViewToggle.tsx +1 -3
- package/app/components/.client/ImageViewer/components/ViewerHeader.tsx +1 -3
- package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.d.ts +9 -11
- package/app/components/.client/ImageViewer/state/decoders/decodeJPEG2000.js +11 -11
- package/app/components/.client/ImageViewer/state/decoders/decoder.worker.js +49 -49
- package/app/components/.client/ImageViewer/state/decoders/genericDecoder.ts +76 -81
- package/app/components/.client/ImageViewer/state/decoders/jp2k-decoder.ts +9 -9
- package/app/components/.client/ImageViewer/state/decoders/lzwDecoder.ts +9 -9
- package/app/components/.client/ImageViewer/state/loaders/loadBioformatsZarrWithCredentials.ts +10 -22
- package/app/components/.client/ImageViewer/state/store/ViewerStoreContext.tsx +4 -18
- package/app/components/.client/ImageViewer/state/store/createViewerStore.ts +110 -194
- package/app/components/.client/ImageViewer/state/store/getInitialChannelsState.ts +2 -6
- package/app/components/.client/ImageViewer/state/store/selectors.ts +9 -9
- package/app/components/.client/ImageViewer/state/store/types.ts +3 -12
- package/app/components/.client/ImageViewer/state/transport/CredentialedHTTPStore.ts +1 -5
- package/app/components/.client/ImageViewer/state/transport/SigV4TiffClient.ts +2 -9
- package/app/components/.client/ImageViewer/utils/getSelectionStats.ts +1 -4
- package/app/components/.client/ImageViewer/utils/handleImageViewerHover.ts +1 -1
- package/app/components/.client/ImageViewer/utils/mapChannelConfigsToState.ts +2 -4
- package/app/components/.client/ImageViewer/utils/useTilesLoading.ts +3 -3
- package/app/components/AppHeader.tsx +1 -4
- package/app/components/Breadcrumbs/Breadcrumbs.tsx +4 -13
- package/app/components/Breadcrumbs/getCrumbs.tsx +1 -1
- package/app/components/ClientOnly.tsx +1 -1
- package/app/components/Container.tsx +3 -15
- package/app/components/DataGrid/ConvertOverlay.modal.tsx +1 -6
- package/app/components/DataGrid/DataGrid.tsx +7 -27
- package/app/components/DataGrid/WktSvg.tsx +2 -4
- package/app/components/DataGrid/getParquetSchema.ts +1 -4
- package/app/components/DescriptionList.tsx +1 -3
- package/app/components/DirectoryView/ConnectionMenu.tsx +8 -22
- package/app/components/DirectoryView/DirectoryView.tsx +10 -46
- package/app/components/DirectoryView/DirectoryViewGrid.tsx +16 -49
- package/app/components/DirectoryView/DirectoryViewTableConnection.tsx +1 -4
- package/app/components/DirectoryView/DirectoryViewTableDirectory.tsx +2 -7
- package/app/components/DirectoryView/DirectoryViewTree.tsx +5 -21
- package/app/components/DirectoryView/FilterBar.tsx +9 -48
- package/app/components/DirectoryView/buildDirectoryTree.ts +6 -25
- package/app/components/DirectoryView/filterNodes.ts +4 -11
- package/app/components/DirectoryView/modals/Cyberduck.modal.tsx +6 -15
- package/app/components/DirectoryView/modals/FileInfo.modal.tsx +1 -5
- package/app/components/DirectoryView/useLayoutStore.ts +5 -25
- package/app/components/GlobalSearch/GlobalSearch.tsx +1 -4
- package/app/components/GlobalSearch/SearchBar.tsx +1 -7
- package/app/components/GlobalSearch/Suggestions.tsx +0 -1
- package/app/components/ImageViewer/state/formatRegistry.ts +5 -18
- package/app/components/LavaLoader.tsx +4 -11
- package/app/components/Layout/Footer.tsx +1 -4
- package/app/components/Pills/ScopePill.tsx +2 -8
- package/app/components/Table/ColumnFilterInput.tsx +5 -20
- package/app/components/Table/ColumnResizeHandle.tsx +1 -5
- package/app/components/Table/ColumnSortButton.tsx +1 -5
- package/app/components/Table/SelectionFooter.tsx +3 -5
- package/app/components/Table/Table.tsx +5 -21
- package/app/components/Table/TableBodyRow.tsx +19 -31
- package/app/components/Table/TableHeaderRow.tsx +7 -28
- package/app/components/Table/TableMenu.tsx +4 -20
- package/app/components/Table/state/createTableStore.ts +3 -9
- package/app/components/Table/state/useTableStore.ts +1 -3
- package/app/components/Table/types.ts +4 -10
- package/app/components/Table/useColumnFilters.ts +1 -4
- package/app/components/Table/useColumnVisibility.ts +4 -11
- package/app/components/Table/useColumnWidths.ts +1 -3
- package/app/components/Table/useTableSorting.ts +4 -12
- package/app/components/Tooltip/Tooltip.tsx +4 -17
- package/app/components/Tooltip/TooltipSpan.tsx +5 -28
- package/app/components/Tooltip/useCopyToClipboard.ts +1 -3
- package/app/components/Tooltip/useMiddleEllipsis.ts +2 -7
- package/app/components/Tooltip/useOverflowDetection.ts +2 -5
- package/app/components/UserMenu.tsx +2 -9
- package/app/entry.server.tsx +9 -19
- package/app/hooks/useSearchParam.ts +2 -4
- package/app/lib/bootstrapPluginsCore.ts +4 -9
- package/app/root.tsx +4 -15
- package/app/routes/admin/assertAdminScope.ts +1 -3
- package/app/routes/admin/assertGroupPathsInScope.ts +3 -11
- package/app/routes/admin/assertGroupsInScope.ts +3 -11
- package/app/routes/admin/assertUsersInScope.ts +2 -8
- package/app/routes/admin/bulkInvite/bulkInvite.action.ts +2 -13
- package/app/routes/admin/bulkInvite/bulkInvite.form.tsx +18 -35
- package/app/routes/admin/createGroup/createGroup.action.ts +3 -10
- package/app/routes/admin/createGroup/createGroup.form.tsx +2 -9
- package/app/routes/admin/createGroup/createGroup.modal.tsx +1 -5
- package/app/routes/admin/inviteUser/inviteUser.action.ts +1 -4
- package/app/routes/admin/inviteUser/inviteUser.form.tsx +2 -8
- package/app/routes/admin/inviteUser/inviteUser.loader.ts +1 -4
- package/app/routes/admin/inviteUser/inviteUser.modal.tsx +3 -16
- package/app/routes/admin/updateUser/updateUser.form.tsx +4 -15
- package/app/routes/admin/updateUser/updateUser.modal.tsx +5 -23
- package/app/routes/admin/updateUser/userDetail.action.ts +2 -10
- package/app/routes/admin/users/BulkActions.tsx +15 -38
- package/app/routes/admin/users/bulkUsers.action.ts +2 -9
- package/app/routes/admin/users/bulkUsers.schema.ts +1 -6
- package/app/routes/admin/users/users.route.tsx +14 -63
- package/app/routes/api/cyberduck-profile.$name.ts +6 -2
- package/app/routes/auth/callback.route.tsx +8 -33
- package/app/routes/auth/login.route.tsx +8 -11
- package/app/routes/auth/logout.route.tsx +4 -14
- package/app/routes/config.route.tsx +1 -5
- package/app/routes/connections/connection.form.tsx +5 -14
- package/app/routes/connections/connection.schema.ts +4 -16
- package/app/routes/connections/connections.loader.ts +11 -23
- package/app/routes/connections/connections.route.tsx +1 -5
- package/app/routes/connections/connections.server.ts +1 -3
- package/app/routes/connections/createConnection.action.ts +2 -8
- package/app/routes/connections/createConnection.modal.tsx +3 -13
- package/app/routes/connections/deleteConnection.action.ts +1 -4
- package/app/routes/connections/updateConnection.action.ts +2 -8
- package/app/routes/connections/updateConnection.modal.tsx +4 -14
- package/app/routes/home/home.route.tsx +8 -33
- package/app/routes/layouts/ModalOutlet.tsx +6 -18
- package/app/routes/objects/objects.loader.ts +5 -19
- package/app/routes/objects/objects.route.tsx +11 -30
- package/app/routes/presign.route.tsx +5 -18
- package/app/routes/recent.route.tsx +1 -4
- package/app/routes/search.route.tsx +4 -17
- package/app/routes.ts +1 -4
- package/app/tailwind.css +17 -12
- package/app/types/cornerstone-codecs.d.ts +25 -29
- package/app/utils/connectionsStore/selectors.ts +2 -6
- package/app/utils/connectionsStore/useConnectionsStore.ts +2 -6
- package/app/utils/db/convertCsvToParquet.ts +1 -3
- package/app/utils/db/createDatabase.ts +1 -3
- package/app/utils/db/createSingleton.ts +1 -1
- package/app/utils/db/getBlobFromObjectNode.ts +3 -9
- package/app/utils/db/getGeomQuery.ts +1 -1
- package/app/utils/db/getMarkerInfoWasm.ts +1 -3
- package/app/utils/db/getTileBoundingBox.ts +1 -4
- package/app/utils/db/sqlQueries.ts +1 -4
- package/app/utils/fileType.ts +80 -10
- package/app/utils/filterObjects.ts +2 -5
- package/app/utils/localFilesStore/useFileStore.ts +7 -7
- package/app/utils/recentlyViewed.server.ts +1 -4
- package/app/utils/resourceId.ts +3 -11
- package/app/utils/s3Provider.ts +3 -7
- package/app/utils/signedFetch.ts +4 -13
- package/bin-src/codegen.ts +4 -1
- package/package.json +5 -1
- package/prisma/seed.ts +1 -2
- package/public/favicon/site.webmanifest +1 -1
- package/server.js +1 -4
- package/server.js.map +1 -1
- package/vite-plugins/cytario-plugins.ts +2 -8
|
@@ -49,8 +49,7 @@ const FIELD_TO_STEP: Record<string, number> = {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
const dtClass = "text-[var(--color-text-secondary)]";
|
|
52
|
-
const ddClass =
|
|
53
|
-
"font-[number:var(--font-weight-medium)] text-[var(--color-text-primary)]";
|
|
52
|
+
const ddClass = "font-[number:var(--font-weight-medium)] text-[var(--color-text-primary)]";
|
|
54
53
|
|
|
55
54
|
function SummaryRow({ label, value }: { label: string; value: string }) {
|
|
56
55
|
return (
|
|
@@ -83,8 +82,7 @@ export const ConnectionForm = ({
|
|
|
83
82
|
}>();
|
|
84
83
|
const navigation = useNavigation();
|
|
85
84
|
|
|
86
|
-
const serverErrors =
|
|
87
|
-
actionData?.status === "error" ? actionData.errors : undefined;
|
|
85
|
+
const serverErrors = actionData?.status === "error" ? actionData.errors : undefined;
|
|
88
86
|
const isSubmitting = navigation.state === "submitting";
|
|
89
87
|
|
|
90
88
|
// Compute the initial step from server errors so we navigate to the
|
|
@@ -151,9 +149,7 @@ export const ConnectionForm = ({
|
|
|
151
149
|
|
|
152
150
|
const fieldsPerStep: Record<number, (keyof ConnectBucketFormData)[]> = {
|
|
153
151
|
0: ["providerType", "s3Uri", "name"],
|
|
154
|
-
1: isAWS
|
|
155
|
-
? ["ownerScope", "roleArn", "bucketRegion"]
|
|
156
|
-
: ["ownerScope", "bucketEndpoint"],
|
|
152
|
+
1: isAWS ? ["ownerScope", "roleArn", "bucketRegion"] : ["ownerScope", "bucketEndpoint"],
|
|
157
153
|
};
|
|
158
154
|
|
|
159
155
|
const onSubmit = (data: ConnectBucketFormData) => {
|
|
@@ -300,9 +296,7 @@ export const ConnectionForm = ({
|
|
|
300
296
|
]}
|
|
301
297
|
selectedKey={field.value}
|
|
302
298
|
onSelectionChange={(key) => field.onChange(key)}
|
|
303
|
-
renderItem={(item) =>
|
|
304
|
-
<ScopePill scope={item.id} />
|
|
305
|
-
)}
|
|
299
|
+
renderItem={(item) => <ScopePill scope={item.id} />}
|
|
306
300
|
/>
|
|
307
301
|
)}
|
|
308
302
|
/>
|
|
@@ -389,10 +383,7 @@ export const ConnectionForm = ({
|
|
|
389
383
|
].join(" ")}
|
|
390
384
|
>
|
|
391
385
|
<dl className="flex flex-col gap-[var(--spacing-2)] text-[length:var(--font-size-sm)]">
|
|
392
|
-
<SummaryRow
|
|
393
|
-
label="Provider"
|
|
394
|
-
value={isAWS ? "AWS S3" : "MinIO"}
|
|
395
|
-
/>
|
|
386
|
+
<SummaryRow label="Provider" value={isAWS ? "AWS S3" : "MinIO"} />
|
|
396
387
|
<SummaryRow label="Name" value={nameValue} />
|
|
397
388
|
<SummaryRow label="Bucket" value={bucketName} />
|
|
398
389
|
{prefix && <SummaryRow label="Prefix" value={prefix} />}
|
|
@@ -10,14 +10,8 @@ export const connectionNameSchema = z
|
|
|
10
10
|
/^[a-zA-Z0-9][a-zA-Z0-9 -]*[a-zA-Z0-9]$/,
|
|
11
11
|
"Name must be alphanumeric with hyphens or spaces, no leading/trailing hyphens or spaces",
|
|
12
12
|
)
|
|
13
|
-
.refine(
|
|
14
|
-
|
|
15
|
-
"Name must not contain consecutive hyphens",
|
|
16
|
-
)
|
|
17
|
-
.refine(
|
|
18
|
-
(val) => !val.includes(" "),
|
|
19
|
-
"Name must not contain consecutive spaces",
|
|
20
|
-
);
|
|
13
|
+
.refine((val) => !val.includes("--"), "Name must not contain consecutive hyphens")
|
|
14
|
+
.refine((val) => !val.includes(" "), "Name must not contain consecutive spaces");
|
|
21
15
|
|
|
22
16
|
// AWS ARN pattern validation
|
|
23
17
|
const arnPattern = /^arn:aws:iam::\d{12}:role\/[\w+=,.@-]+$/;
|
|
@@ -39,11 +33,7 @@ const s3UriSchema = z
|
|
|
39
33
|
/** Auto-suggest a connection name from an S3 URI (e.g. "s3://my-bucket/path" → "my-bucket"). */
|
|
40
34
|
export function suggestName(s3Uri: string): string {
|
|
41
35
|
const { bucketName, prefix } = parseS3Uri(s3Uri);
|
|
42
|
-
const lastSegment = prefix
|
|
43
|
-
.replace(/\/$/, "")
|
|
44
|
-
.split("/")
|
|
45
|
-
.filter(Boolean)
|
|
46
|
-
.pop();
|
|
36
|
+
const lastSegment = prefix.replace(/\/$/, "").split("/").filter(Boolean).pop();
|
|
47
37
|
const base = lastSegment ? `${bucketName} ${lastSegment}` : bucketName;
|
|
48
38
|
return base
|
|
49
39
|
.replace(/[^a-zA-Z0-9 -]/g, " ")
|
|
@@ -54,9 +44,7 @@ export function suggestName(s3Uri: string): string {
|
|
|
54
44
|
}
|
|
55
45
|
|
|
56
46
|
// Helper to parse S3 URI into bucket name and prefix
|
|
57
|
-
export const parseS3Uri = (
|
|
58
|
-
uri: string,
|
|
59
|
-
): { bucketName: string; prefix: string } => {
|
|
47
|
+
export const parseS3Uri = (uri: string): { bucketName: string; prefix: string } => {
|
|
60
48
|
const cleaned = uri.replace(/^s3:\/\//, "");
|
|
61
49
|
const [bucketName, ...prefixParts] = cleaned.split("/");
|
|
62
50
|
const prefix = prefixParts.join("/");
|
|
@@ -21,13 +21,7 @@ const fetchPreviewObject = async (
|
|
|
21
21
|
const creds = credentials[config.name];
|
|
22
22
|
if (!creds) return undefined;
|
|
23
23
|
const s3 = await getS3Client(config, creds, userId);
|
|
24
|
-
const objects = await getObjects(
|
|
25
|
-
config,
|
|
26
|
-
s3,
|
|
27
|
-
null,
|
|
28
|
-
config.prefix || undefined,
|
|
29
|
-
100,
|
|
30
|
-
);
|
|
24
|
+
const objects = await getObjects(config, s3, null, config.prefix || undefined, 100);
|
|
31
25
|
return objects.find((obj) => isImageFile(obj.Key ?? ""));
|
|
32
26
|
};
|
|
33
27
|
|
|
@@ -57,17 +51,13 @@ export interface LoaderData {
|
|
|
57
51
|
pinnedPaths: SerializedPinnedPath[];
|
|
58
52
|
}
|
|
59
53
|
|
|
60
|
-
export async function loadConnections({
|
|
61
|
-
context,
|
|
62
|
-
}: LoaderFunctionArgs) {
|
|
54
|
+
export async function loadConnections({ context }: LoaderFunctionArgs) {
|
|
63
55
|
const { connectionConfigs, credentials, user } = context.get(authContext);
|
|
64
56
|
const userId = user.sub;
|
|
65
57
|
|
|
66
58
|
const [previews, recentlyViewedRaw, pinnedPathsRaw] = await Promise.all([
|
|
67
59
|
Promise.allSettled(
|
|
68
|
-
connectionConfigs.map((config) =>
|
|
69
|
-
fetchPreviewObject(config, credentials, userId),
|
|
70
|
-
),
|
|
60
|
+
connectionConfigs.map((config) => fetchPreviewObject(config, credentials, userId)),
|
|
71
61
|
),
|
|
72
62
|
getRecentlyViewed(userId, 20),
|
|
73
63
|
getPinnedPaths(userId),
|
|
@@ -88,16 +78,14 @@ export async function loadConnections({
|
|
|
88
78
|
};
|
|
89
79
|
});
|
|
90
80
|
|
|
91
|
-
const recentlyViewed: SerializedRecentlyViewed[] = recentlyViewedRaw.map(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}),
|
|
100
|
-
);
|
|
81
|
+
const recentlyViewed: SerializedRecentlyViewed[] = recentlyViewedRaw.map((item) => ({
|
|
82
|
+
id: item.id,
|
|
83
|
+
connectionName: item.connectionName,
|
|
84
|
+
pathName: item.pathName,
|
|
85
|
+
name: item.name,
|
|
86
|
+
type: item.type,
|
|
87
|
+
viewedAt: item.viewedAt.toISOString(),
|
|
88
|
+
}));
|
|
101
89
|
|
|
102
90
|
const pinnedPaths: SerializedPinnedPath[] = pinnedPathsRaw.map((pin) => ({
|
|
103
91
|
id: pin.id,
|
|
@@ -63,11 +63,7 @@ export default function ConnectionsListRoute() {
|
|
|
63
63
|
title="No storage connections"
|
|
64
64
|
description="Add a storage connection to view your cloud storage."
|
|
65
65
|
action={
|
|
66
|
-
<Button
|
|
67
|
-
size="lg"
|
|
68
|
-
variant="neutral"
|
|
69
|
-
onPress={() => openModal("add-connection")}
|
|
70
|
-
>
|
|
66
|
+
<Button size="lg" variant="neutral" onPress={() => openModal("add-connection")}>
|
|
71
67
|
Connect Storage
|
|
72
68
|
</Button>
|
|
73
69
|
}
|
|
@@ -4,9 +4,7 @@ import { prisma } from "~/.server/db/prisma";
|
|
|
4
4
|
import { canSee, filterVisible } from "~/utils/authorization";
|
|
5
5
|
|
|
6
6
|
/** List all connection configs visible to the user. */
|
|
7
|
-
export async function listConnections(
|
|
8
|
-
user: UserProfile,
|
|
9
|
-
): Promise<ConnectionConfig[]> {
|
|
7
|
+
export async function listConnections(user: UserProfile): Promise<ConnectionConfig[]> {
|
|
10
8
|
const allConfigs = await prisma.connectionConfig.findMany();
|
|
11
9
|
return filterVisible(user, allConfigs);
|
|
12
10
|
}
|
|
@@ -42,10 +42,7 @@ export async function createConnection(
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export const createAction = async ({
|
|
46
|
-
request,
|
|
47
|
-
context,
|
|
48
|
-
}: ActionFunctionArgs) => {
|
|
45
|
+
export const createAction = async ({ request, context }: ActionFunctionArgs) => {
|
|
49
46
|
const { user } = context.get(authContext);
|
|
50
47
|
|
|
51
48
|
const formData = await request.formData();
|
|
@@ -108,10 +105,7 @@ export const createAction = async ({
|
|
|
108
105
|
headers: { "Set-Cookie": await sessionStorage.commitSession(session) },
|
|
109
106
|
});
|
|
110
107
|
} catch (error) {
|
|
111
|
-
if (
|
|
112
|
-
error instanceof Prisma.PrismaClientKnownRequestError &&
|
|
113
|
-
error.code === "P2002"
|
|
114
|
-
) {
|
|
108
|
+
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
|
|
115
109
|
return {
|
|
116
110
|
errors: {
|
|
117
111
|
name: ["This name is already taken. Please choose another."],
|
|
@@ -21,24 +21,14 @@ export function resolveDefaultScope(
|
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export default function CreateConnectionModal({
|
|
25
|
-
|
|
26
|
-
}: {
|
|
27
|
-
onClose: () => void;
|
|
28
|
-
}) {
|
|
29
|
-
const rootData = useRouteLoaderData("root") as
|
|
30
|
-
| { user?: UserProfile }
|
|
31
|
-
| undefined;
|
|
24
|
+
export default function CreateConnectionModal({ onClose }: { onClose: () => void }) {
|
|
25
|
+
const rootData = useRouteLoaderData("root") as { user?: UserProfile } | undefined;
|
|
32
26
|
const user = rootData?.user;
|
|
33
27
|
const [searchParams] = useSearchParams();
|
|
34
28
|
|
|
35
29
|
if (!user) return null;
|
|
36
30
|
|
|
37
|
-
const defaultScope = resolveDefaultScope(
|
|
38
|
-
searchParams.get("scope"),
|
|
39
|
-
user.adminScopes,
|
|
40
|
-
user.sub,
|
|
41
|
-
);
|
|
31
|
+
const defaultScope = resolveDefaultScope(searchParams.get("scope"), user.adminScopes, user.sub);
|
|
42
32
|
|
|
43
33
|
return (
|
|
44
34
|
<RouteModal title="Connect Storage" onClose={onClose}>
|
|
@@ -24,10 +24,7 @@ export async function deleteConnection(user: UserProfile, name: string) {
|
|
|
24
24
|
await prisma.connectionConfig.delete({ where: { id: config.id } });
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export const deleteAction = async ({
|
|
28
|
-
request,
|
|
29
|
-
context,
|
|
30
|
-
}: ActionFunctionArgs) => {
|
|
27
|
+
export const deleteAction = async ({ request, context }: ActionFunctionArgs) => {
|
|
31
28
|
const { user } = context.get(authContext);
|
|
32
29
|
const session = context.get(sessionContext);
|
|
33
30
|
const formData = await request.formData();
|
|
@@ -65,10 +65,7 @@ export async function updateConnection(
|
|
|
65
65
|
return { ...updated, previousName, previousBucketName };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export const updateAction = async ({
|
|
69
|
-
request,
|
|
70
|
-
context,
|
|
71
|
-
}: ActionFunctionArgs) => {
|
|
68
|
+
export const updateAction = async ({ request, context }: ActionFunctionArgs) => {
|
|
72
69
|
const { user } = context.get(authContext);
|
|
73
70
|
const session = context.get(sessionContext);
|
|
74
71
|
const formData = await request.formData();
|
|
@@ -141,10 +138,7 @@ export const updateAction = async ({
|
|
|
141
138
|
headers: { "Set-Cookie": await sessionStorage.commitSession(session) },
|
|
142
139
|
});
|
|
143
140
|
} catch (error) {
|
|
144
|
-
if (
|
|
145
|
-
error instanceof Prisma.PrismaClientKnownRequestError &&
|
|
146
|
-
error.code === "P2002"
|
|
147
|
-
) {
|
|
141
|
+
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
|
|
148
142
|
return {
|
|
149
143
|
errors: {
|
|
150
144
|
name: [
|
|
@@ -17,15 +17,12 @@ export default function UpdateConnectionModal({
|
|
|
17
17
|
nodeName ? state.connections[nodeName]?.connectionConfig : undefined,
|
|
18
18
|
);
|
|
19
19
|
|
|
20
|
-
const rootData = useRouteLoaderData("root") as
|
|
21
|
-
| { user?: UserProfile }
|
|
22
|
-
| undefined;
|
|
20
|
+
const rootData = useRouteLoaderData("root") as { user?: UserProfile } | undefined;
|
|
23
21
|
const user = rootData?.user;
|
|
24
22
|
|
|
25
23
|
if (!user || !nodeName || !connectionConfig) return null;
|
|
26
24
|
|
|
27
|
-
const { bucketName, prefix, provider, ownerScope, roleArn, region, endpoint } =
|
|
28
|
-
connectionConfig;
|
|
25
|
+
const { bucketName, prefix, provider, ownerScope, roleArn, region, endpoint } = connectionConfig;
|
|
29
26
|
|
|
30
27
|
const s3Uri = prefix ? `${bucketName}/${prefix}` : bucketName;
|
|
31
28
|
|
|
@@ -41,15 +38,8 @@ export default function UpdateConnectionModal({
|
|
|
41
38
|
};
|
|
42
39
|
|
|
43
40
|
return (
|
|
44
|
-
<RouteModal
|
|
45
|
-
|
|
46
|
-
onClose={() => onClose(["nodeName"])}
|
|
47
|
-
>
|
|
48
|
-
<ConnectionForm
|
|
49
|
-
adminScopes={user.adminScopes}
|
|
50
|
-
userId={user.sub}
|
|
51
|
-
initialData={initialData}
|
|
52
|
-
/>
|
|
41
|
+
<RouteModal title="Edit Connection" onClose={() => onClose(["nodeName"])}>
|
|
42
|
+
<ConnectionForm adminScopes={user.adminScopes} userId={user.sub} initialData={initialData} />
|
|
53
43
|
</RouteModal>
|
|
54
44
|
);
|
|
55
45
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { Button, EmptyState } from "@cytario/design";
|
|
2
2
|
import { FileSearch } from "lucide-react";
|
|
3
3
|
import { useMemo } from "react";
|
|
4
|
-
import {
|
|
5
|
-
type MetaFunction,
|
|
6
|
-
type ShouldRevalidateFunction,
|
|
7
|
-
useLoaderData,
|
|
8
|
-
} from "react-router";
|
|
4
|
+
import { type MetaFunction, type ShouldRevalidateFunction, useLoaderData } from "react-router";
|
|
9
5
|
|
|
10
6
|
import { Section } from "~/components/Container";
|
|
11
7
|
import { DashboardSection } from "~/components/DashboardSection";
|
|
@@ -18,7 +14,6 @@ import {
|
|
|
18
14
|
} from "~/routes/connections/connections.loader";
|
|
19
15
|
import { isImageFile } from "~/utils/fileType";
|
|
20
16
|
|
|
21
|
-
|
|
22
17
|
const title = "Storage Connections";
|
|
23
18
|
const MAX_RECENT_IMAGES = 4;
|
|
24
19
|
const MAX_PINNED = 10;
|
|
@@ -27,10 +22,7 @@ const MAX_RECENT_FILES = 6;
|
|
|
27
22
|
const MAX_CONNECTIONS = 100;
|
|
28
23
|
|
|
29
24
|
export const meta: MetaFunction = () => {
|
|
30
|
-
return [
|
|
31
|
-
{ title },
|
|
32
|
-
{ name: "description", content: "Manage your storage connections" },
|
|
33
|
-
];
|
|
25
|
+
return [{ title }, { name: "description", content: "Manage your storage connections" }];
|
|
34
26
|
};
|
|
35
27
|
|
|
36
28
|
export const shouldRevalidate: ShouldRevalidateFunction = ({
|
|
@@ -48,8 +40,7 @@ export const shouldRevalidate: ShouldRevalidateFunction = ({
|
|
|
48
40
|
export { loadConnections as loader } from "~/routes/connections/connections.loader";
|
|
49
41
|
|
|
50
42
|
export default function HomeRoute() {
|
|
51
|
-
const { nodes, connectionConfigs, recentlyViewed, pinnedPaths } =
|
|
52
|
-
useLoaderData<LoaderData>();
|
|
43
|
+
const { nodes, connectionConfigs, recentlyViewed, pinnedPaths } = useLoaderData<LoaderData>();
|
|
53
44
|
|
|
54
45
|
const { openModal } = useModal();
|
|
55
46
|
|
|
@@ -62,9 +53,7 @@ export default function HomeRoute() {
|
|
|
62
53
|
const allRecentItems: TreeNode[] = useMemo(
|
|
63
54
|
() =>
|
|
64
55
|
recentlyViewed
|
|
65
|
-
.filter((item: SerializedRecentlyViewed) =>
|
|
66
|
-
configByName.has(item.connectionName),
|
|
67
|
-
)
|
|
56
|
+
.filter((item: SerializedRecentlyViewed) => configByName.has(item.connectionName))
|
|
68
57
|
.map((item: SerializedRecentlyViewed) => {
|
|
69
58
|
return {
|
|
70
59
|
id: `${item.connectionName}/${item.pathName}`,
|
|
@@ -93,9 +82,7 @@ export default function HomeRoute() {
|
|
|
93
82
|
const pinnedNodes: TreeNode[] = useMemo(
|
|
94
83
|
() =>
|
|
95
84
|
pinnedPaths
|
|
96
|
-
.filter((pin: SerializedPinnedPath) =>
|
|
97
|
-
configByName.has(pin.connectionName),
|
|
98
|
-
)
|
|
85
|
+
.filter((pin: SerializedPinnedPath) => configByName.has(pin.connectionName))
|
|
99
86
|
.map((pin: SerializedPinnedPath) => {
|
|
100
87
|
return {
|
|
101
88
|
id: `${pin.connectionName}/${pin.pathName}`,
|
|
@@ -108,9 +95,7 @@ export default function HomeRoute() {
|
|
|
108
95
|
pin.totalSize != null || pin.lastModified != null
|
|
109
96
|
? ({
|
|
110
97
|
Size: pin.totalSize ?? undefined,
|
|
111
|
-
LastModified: pin.lastModified
|
|
112
|
-
? new Date(pin.lastModified)
|
|
113
|
-
: undefined,
|
|
98
|
+
LastModified: pin.lastModified ? new Date(pin.lastModified) : undefined,
|
|
114
99
|
} as TreeNode["_Object"])
|
|
115
100
|
: undefined,
|
|
116
101
|
};
|
|
@@ -128,12 +113,7 @@ export default function HomeRoute() {
|
|
|
128
113
|
showAllHref="/recent"
|
|
129
114
|
/>
|
|
130
115
|
|
|
131
|
-
<DashboardSection
|
|
132
|
-
title="Pinned"
|
|
133
|
-
nodes={pinnedNodes}
|
|
134
|
-
viewMode="list"
|
|
135
|
-
maxItems={MAX_PINNED}
|
|
136
|
-
/>
|
|
116
|
+
<DashboardSection title="Pinned" nodes={pinnedNodes} viewMode="list" maxItems={MAX_PINNED} />
|
|
137
117
|
|
|
138
118
|
<DashboardSection
|
|
139
119
|
title="Recently Browsed"
|
|
@@ -166,18 +146,13 @@ export default function HomeRoute() {
|
|
|
166
146
|
title="Start exploring your data"
|
|
167
147
|
description="Add a storage connection to view your cloud storage."
|
|
168
148
|
action={
|
|
169
|
-
<Button
|
|
170
|
-
size="lg"
|
|
171
|
-
variant="neutral"
|
|
172
|
-
onPress={() => openModal("add-connection")}
|
|
173
|
-
>
|
|
149
|
+
<Button size="lg" variant="neutral" onPress={() => openModal("add-connection")}>
|
|
174
150
|
Connect Storage
|
|
175
151
|
</Button>
|
|
176
152
|
}
|
|
177
153
|
/>
|
|
178
154
|
</Section>
|
|
179
155
|
)}
|
|
180
|
-
|
|
181
156
|
</div>
|
|
182
157
|
);
|
|
183
158
|
}
|
|
@@ -11,24 +11,12 @@ type ModalComponent = ComponentType<{ onClose: (extraKeys?: string[]) => void }>
|
|
|
11
11
|
* Add new entries here to register search-param-driven modals.
|
|
12
12
|
*/
|
|
13
13
|
const MODAL_REGISTRY = {
|
|
14
|
-
"add-connection": lazy(
|
|
15
|
-
|
|
16
|
-
),
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
),
|
|
20
|
-
"edit-connection": lazy(
|
|
21
|
-
() => import("~/routes/connections/updateConnection.modal"),
|
|
22
|
-
),
|
|
23
|
-
"directory-info": lazy(
|
|
24
|
-
() => import("~/components/DirectoryView/modals/DirectoryInfo.modal"),
|
|
25
|
-
),
|
|
26
|
-
"file-info": lazy(
|
|
27
|
-
() => import("~/components/DirectoryView/modals/FileInfo.modal"),
|
|
28
|
-
),
|
|
29
|
-
cyberduck: lazy(
|
|
30
|
-
() => import("~/components/DirectoryView/modals/Cyberduck.modal"),
|
|
31
|
-
),
|
|
14
|
+
"add-connection": lazy(() => import("~/routes/connections/createConnection.modal")),
|
|
15
|
+
"convert-overlay": lazy(() => import("~/components/DataGrid/ConvertOverlay.modal")),
|
|
16
|
+
"edit-connection": lazy(() => import("~/routes/connections/updateConnection.modal")),
|
|
17
|
+
"directory-info": lazy(() => import("~/components/DirectoryView/modals/DirectoryInfo.modal")),
|
|
18
|
+
"file-info": lazy(() => import("~/components/DirectoryView/modals/FileInfo.modal")),
|
|
19
|
+
cyberduck: lazy(() => import("~/components/DirectoryView/modals/Cyberduck.modal")),
|
|
32
20
|
} satisfies Record<string, ModalComponent>;
|
|
33
21
|
|
|
34
22
|
/**
|
|
@@ -5,10 +5,7 @@ import { type LoaderFunctionArgs } from "react-router";
|
|
|
5
5
|
import { ConnectionConfig } from "~/.generated/client";
|
|
6
6
|
import { authContext } from "~/.server/auth/authMiddleware";
|
|
7
7
|
import { getS3Client } from "~/.server/auth/getS3Client";
|
|
8
|
-
import {
|
|
9
|
-
buildDirectoryTree,
|
|
10
|
-
TreeNode,
|
|
11
|
-
} from "~/components/DirectoryView/buildDirectoryTree";
|
|
8
|
+
import { buildDirectoryTree, TreeNode } from "~/components/DirectoryView/buildDirectoryTree";
|
|
12
9
|
import { type NotificationInput } from "~/components/Notification/Notification.store";
|
|
13
10
|
import { getConnection } from "~/routes/connections/connections.server";
|
|
14
11
|
import { getObjects } from "~/utils/getObjects";
|
|
@@ -37,8 +34,7 @@ export const loader = async ({
|
|
|
37
34
|
params,
|
|
38
35
|
context,
|
|
39
36
|
}: LoaderFunctionArgs): Promise<BucketRouteLoaderResponse> => {
|
|
40
|
-
const { user, credentials: connectionsCredentials } =
|
|
41
|
-
context.get(authContext);
|
|
37
|
+
const { user, credentials: connectionsCredentials } = context.get(authContext);
|
|
42
38
|
const { name: connectionName } = params;
|
|
43
39
|
|
|
44
40
|
if (!connectionName) throw new Error("Connection name is required");
|
|
@@ -51,16 +47,11 @@ export const loader = async ({
|
|
|
51
47
|
const { bucketName } = connectionConfig;
|
|
52
48
|
|
|
53
49
|
const credentials = connectionsCredentials[connectionName];
|
|
54
|
-
if (!credentials)
|
|
55
|
-
throw new Error(`No credentials for connection: ${connectionName}`);
|
|
50
|
+
if (!credentials) throw new Error(`No credentials for connection: ${connectionName}`);
|
|
56
51
|
|
|
57
52
|
const urlPath = params["*"] ?? "";
|
|
58
53
|
const connPrefix = connectionConfig.prefix?.replace(/\/$/, "") ?? "";
|
|
59
|
-
const pathName = connPrefix
|
|
60
|
-
? urlPath
|
|
61
|
-
? `${connPrefix}/${urlPath}`
|
|
62
|
-
: connPrefix
|
|
63
|
-
: urlPath;
|
|
54
|
+
const pathName = connPrefix ? (urlPath ? `${connPrefix}/${urlPath}` : connPrefix) : urlPath;
|
|
64
55
|
const prefix = getPrefix(pathName);
|
|
65
56
|
const name = getName(pathName, bucketName);
|
|
66
57
|
|
|
@@ -94,12 +85,7 @@ export const loader = async ({
|
|
|
94
85
|
);
|
|
95
86
|
|
|
96
87
|
if (objects.length > 0) {
|
|
97
|
-
const nodes = buildDirectoryTree(
|
|
98
|
-
objects,
|
|
99
|
-
connectionName,
|
|
100
|
-
prefix,
|
|
101
|
-
urlPath,
|
|
102
|
-
);
|
|
88
|
+
const nodes = buildDirectoryTree(objects, connectionName, prefix, urlPath);
|
|
103
89
|
|
|
104
90
|
return {
|
|
105
91
|
connectionName,
|
|
@@ -9,10 +9,7 @@ import {
|
|
|
9
9
|
useNavigate,
|
|
10
10
|
} from "react-router";
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
type BucketRouteLoaderResponse,
|
|
14
|
-
loader,
|
|
15
|
-
} from "./objects.loader";
|
|
12
|
+
import { type BucketRouteLoaderResponse, loader } from "./objects.loader";
|
|
16
13
|
import { requestDurationMiddleware } from "~/.server/requestDurationMiddleware";
|
|
17
14
|
import { getCrumbs } from "~/components/Breadcrumbs/getCrumbs";
|
|
18
15
|
import { ClientOnly } from "~/components/ClientOnly";
|
|
@@ -35,9 +32,9 @@ import { createSignedFetch } from "~/utils/signedFetch";
|
|
|
35
32
|
|
|
36
33
|
// Lazy load Viewer to prevent SSR issues with client-only code
|
|
37
34
|
const Viewer = lazy(() =>
|
|
38
|
-
import("~/components/.client/ImageViewer/components/ImageViewer").then(
|
|
39
|
-
|
|
40
|
-
),
|
|
35
|
+
import("~/components/.client/ImageViewer/components/ImageViewer").then((module) => ({
|
|
36
|
+
default: module.Viewer,
|
|
37
|
+
})),
|
|
41
38
|
);
|
|
42
39
|
|
|
43
40
|
export { loader };
|
|
@@ -82,10 +79,7 @@ export const handle = {
|
|
|
82
79
|
* This route has no mutating forms of its own, so skipping the
|
|
83
80
|
* `formAction` branch is safe.
|
|
84
81
|
*/
|
|
85
|
-
export const shouldRevalidate: ShouldRevalidateFunction = ({
|
|
86
|
-
currentUrl,
|
|
87
|
-
nextUrl,
|
|
88
|
-
}) => {
|
|
82
|
+
export const shouldRevalidate: ShouldRevalidateFunction = ({ currentUrl, nextUrl }) => {
|
|
89
83
|
if (currentUrl.pathname !== nextUrl.pathname) return true;
|
|
90
84
|
if (currentUrl.search !== nextUrl.search) return true;
|
|
91
85
|
return false;
|
|
@@ -157,10 +151,7 @@ export default function ObjectsRoute() {
|
|
|
157
151
|
{ method: "delete", action: "/api/pinned" },
|
|
158
152
|
);
|
|
159
153
|
} else {
|
|
160
|
-
const totalSize = nodes.reduce(
|
|
161
|
-
(sum, node) => sum + computeDirectorySize(node),
|
|
162
|
-
0,
|
|
163
|
-
);
|
|
154
|
+
const totalSize = nodes.reduce((sum, node) => sum + computeDirectorySize(node), 0);
|
|
164
155
|
const lastModified = nodes.reduce(
|
|
165
156
|
(max, node) => Math.max(max, computeDirectoryLastModified(node)),
|
|
166
157
|
0,
|
|
@@ -169,9 +160,7 @@ export default function ObjectsRoute() {
|
|
|
169
160
|
{
|
|
170
161
|
connectionName,
|
|
171
162
|
pathName: urlPath,
|
|
172
|
-
displayName: urlPath
|
|
173
|
-
? getName(urlPath, connectionName)
|
|
174
|
-
: connectionName,
|
|
163
|
+
displayName: urlPath ? getName(urlPath, connectionName) : connectionName,
|
|
175
164
|
totalSize: String(totalSize),
|
|
176
165
|
lastModified: lastModified ? String(lastModified) : "",
|
|
177
166
|
},
|
|
@@ -203,10 +192,7 @@ export default function ObjectsRoute() {
|
|
|
203
192
|
{isPinned ? <BookmarkCheck size={16} /> : <Bookmark size={16} />}
|
|
204
193
|
{isPinned ? "Pinned" : "Pin"}
|
|
205
194
|
</Button>
|
|
206
|
-
<Button
|
|
207
|
-
onPress={() => openModal("cyberduck", { connectionName })}
|
|
208
|
-
variant="secondary"
|
|
209
|
-
>
|
|
195
|
+
<Button onPress={() => openModal("cyberduck", { connectionName })} variant="secondary">
|
|
210
196
|
<Download size={16} />
|
|
211
197
|
Access with Cyberduck
|
|
212
198
|
</Button>
|
|
@@ -226,13 +212,10 @@ export default function ObjectsRoute() {
|
|
|
226
212
|
<header className="flex items-center justify-between p-4 bg-amber-100 border-b border-amber-300 text-amber-900">
|
|
227
213
|
<div className="flex items-center gap-2">
|
|
228
214
|
<span className="text-sm">
|
|
229
|
-
CSV files are slow to query. Convert to Parquet for better
|
|
230
|
-
performance.
|
|
215
|
+
CSV files are slow to query. Convert to Parquet for better performance.
|
|
231
216
|
</span>
|
|
232
217
|
</div>
|
|
233
|
-
<Button onPress={() => openModal("convert-overlay")}>
|
|
234
|
-
Convert to Parquet
|
|
235
|
-
</Button>
|
|
218
|
+
<Button onPress={() => openModal("convert-overlay")}>Convert to Parquet</Button>
|
|
236
219
|
</header>
|
|
237
220
|
)}
|
|
238
221
|
<DataGrid resourceId={resourceId} />
|
|
@@ -251,9 +234,7 @@ export default function ObjectsRoute() {
|
|
|
251
234
|
// Feed it directly to `constructS3Url`, which expects a full key.
|
|
252
235
|
const s3Url = constructS3Url(connectionConfig, pathName);
|
|
253
236
|
const signedFetch = createSignedFetch(
|
|
254
|
-
() =>
|
|
255
|
-
useConnectionsStore.getState().connections[connectionName]
|
|
256
|
-
?.credentials,
|
|
237
|
+
() => useConnectionsStore.getState().connections[connectionName]?.credentials,
|
|
257
238
|
connectionConfig,
|
|
258
239
|
);
|
|
259
240
|
return (
|
|
@@ -11,10 +11,7 @@ export const middleware = [requestDurationMiddleware, authMiddleware];
|
|
|
11
11
|
|
|
12
12
|
const label = createLabel("presign", "gray");
|
|
13
13
|
|
|
14
|
-
export const loader = async ({
|
|
15
|
-
params,
|
|
16
|
-
context,
|
|
17
|
-
}: ActionFunctionArgs): Promise<Response> => {
|
|
14
|
+
export const loader = async ({ params, context }: ActionFunctionArgs): Promise<Response> => {
|
|
18
15
|
const { user, credentials: connectionsCredentials } = context.get(authContext);
|
|
19
16
|
const { name: connectionName } = params;
|
|
20
17
|
const pathName = params["*"] ?? "";
|
|
@@ -27,29 +24,19 @@ export const loader = async ({
|
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
const { provider, bucketName, prefix: connPrefix } = connectionConfig;
|
|
30
|
-
const s3Key = connPrefix
|
|
31
|
-
? `${connPrefix.replace(/\/$/, "")}/${pathName}`
|
|
32
|
-
: pathName;
|
|
27
|
+
const s3Key = connPrefix ? `${connPrefix.replace(/\/$/, "")}/${pathName}` : pathName;
|
|
33
28
|
console.info(`${label} Presign route: ${provider}/${bucketName}/${s3Key}`);
|
|
34
29
|
|
|
35
30
|
const credentials = connectionsCredentials[connectionName];
|
|
36
|
-
if (!credentials)
|
|
37
|
-
throw new Error(`No credentials for connection: ${connectionName}`);
|
|
31
|
+
if (!credentials) throw new Error(`No credentials for connection: ${connectionName}`);
|
|
38
32
|
|
|
39
33
|
try {
|
|
40
34
|
const s3Client = await getS3Client(connectionConfig, credentials, user.sub);
|
|
41
|
-
const presignedUrl = await getPresignedUrl(
|
|
42
|
-
connectionConfig,
|
|
43
|
-
s3Client,
|
|
44
|
-
s3Key,
|
|
45
|
-
);
|
|
35
|
+
const presignedUrl = await getPresignedUrl(connectionConfig, s3Client, s3Key);
|
|
46
36
|
|
|
47
37
|
return Response.json({ url: presignedUrl });
|
|
48
38
|
} catch (error) {
|
|
49
39
|
console.error("Error presigning url", error);
|
|
50
|
-
return Response.json(
|
|
51
|
-
{ error: "Failed to generate presigned URL" },
|
|
52
|
-
{ status: 500 },
|
|
53
|
-
);
|
|
40
|
+
return Response.json({ error: "Failed to generate presigned URL" }, { status: 500 });
|
|
54
41
|
}
|
|
55
42
|
};
|
|
@@ -105,10 +105,7 @@ export default function RecentRoute() {
|
|
|
105
105
|
</>
|
|
106
106
|
}
|
|
107
107
|
>
|
|
108
|
-
<Button
|
|
109
|
-
variant="secondary"
|
|
110
|
-
onPress={() => clearFetcher.submit({}, { method: "delete" })}
|
|
111
|
-
>
|
|
108
|
+
<Button variant="secondary" onPress={() => clearFetcher.submit({}, { method: "delete" })}>
|
|
112
109
|
<Trash2 size={16} />
|
|
113
110
|
Clear history
|
|
114
111
|
</Button>
|