@cytario/web 2.1.4 → 2.1.6
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
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { Button, Checkbox } from "@cytario/design";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import {
|
|
4
|
-
useActionData,
|
|
5
|
-
useNavigate,
|
|
6
|
-
useNavigation,
|
|
7
|
-
useOutletContext,
|
|
8
|
-
} from "react-router";
|
|
3
|
+
import { useActionData, useNavigate, useNavigation, useOutletContext } from "react-router";
|
|
9
4
|
|
|
10
5
|
import { InviteUserForm } from "./inviteUser.form";
|
|
11
6
|
import { type GroupInfo } from "~/.server/auth/keycloakAdmin";
|
|
@@ -50,21 +45,13 @@ export default function InviteModal() {
|
|
|
50
45
|
actionData={actionData}
|
|
51
46
|
/>
|
|
52
47
|
<footer className="flex items-center gap-3 mt-6">
|
|
53
|
-
<Checkbox
|
|
54
|
-
isSelected={inviteAnother}
|
|
55
|
-
onChange={setInviteAnother}
|
|
56
|
-
className="mr-auto"
|
|
57
|
-
>
|
|
48
|
+
<Checkbox isSelected={inviteAnother} onChange={setInviteAnother} className="mr-auto">
|
|
58
49
|
<span className="text-sm text-slate-600">Invite another</span>
|
|
59
50
|
</Checkbox>
|
|
60
51
|
<Button onPress={() => navigate(-1)} variant="secondary">
|
|
61
52
|
Cancel
|
|
62
53
|
</Button>
|
|
63
|
-
<Button
|
|
64
|
-
type="submit"
|
|
65
|
-
form="invite-form"
|
|
66
|
-
isDisabled={isSubmitting}
|
|
67
|
-
>
|
|
54
|
+
<Button type="submit" form="invite-form" isDisabled={isSubmitting}>
|
|
68
55
|
{isSubmitting ? "Inviting..." : "Send Invite"}
|
|
69
56
|
</Button>
|
|
70
57
|
</footer>
|
|
@@ -19,11 +19,7 @@ interface UpdateUserFormProps {
|
|
|
19
19
|
groupPaths: Set<string>;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export const UpdateUserForm = ({
|
|
23
|
-
user,
|
|
24
|
-
groups,
|
|
25
|
-
groupPaths,
|
|
26
|
-
}: UpdateUserFormProps) => {
|
|
22
|
+
export const UpdateUserForm = ({ user, groups, groupPaths }: UpdateUserFormProps) => {
|
|
27
23
|
const submit = useSubmit();
|
|
28
24
|
|
|
29
25
|
const [memberGroupIds, setMemberGroupIds] = useState<Set<string>>(() => {
|
|
@@ -61,10 +57,7 @@ export const UpdateUserForm = ({
|
|
|
61
57
|
}
|
|
62
58
|
});
|
|
63
59
|
for (const group of groups) {
|
|
64
|
-
formData.append(
|
|
65
|
-
`group-${group.id}`,
|
|
66
|
-
String(memberGroupIds.has(group.id)),
|
|
67
|
-
);
|
|
60
|
+
formData.append(`group-${group.id}`, String(memberGroupIds.has(group.id)));
|
|
68
61
|
}
|
|
69
62
|
return formData;
|
|
70
63
|
};
|
|
@@ -73,9 +66,7 @@ export const UpdateUserForm = ({
|
|
|
73
66
|
const changes: string[] = [];
|
|
74
67
|
|
|
75
68
|
if (user.enabled && !data.enabled) {
|
|
76
|
-
changes.push(
|
|
77
|
-
`Disable account for ${user.firstName} ${user.lastName}`,
|
|
78
|
-
);
|
|
69
|
+
changes.push(`Disable account for ${user.firstName} ${user.lastName}`);
|
|
79
70
|
}
|
|
80
71
|
|
|
81
72
|
const addedAdminGroups = groups.filter(
|
|
@@ -249,9 +240,7 @@ export const UpdateUserForm = ({
|
|
|
249
240
|
confirmLabel="Save Changes"
|
|
250
241
|
confirmVariant="primary"
|
|
251
242
|
>
|
|
252
|
-
<p className="text-sm text-slate-600">
|
|
253
|
-
You are about to make the following changes:
|
|
254
|
-
</p>
|
|
243
|
+
<p className="text-sm text-slate-600">You are about to make the following changes:</p>
|
|
255
244
|
<ul className="list-disc list-inside text-sm text-slate-900 space-y-1">
|
|
256
245
|
{warnings.map((w) => (
|
|
257
246
|
<li key={w}>{w}</li>
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import { Button } from "@cytario/design";
|
|
2
|
-
import {
|
|
3
|
-
useNavigate,
|
|
4
|
-
useNavigation,
|
|
5
|
-
useOutletContext,
|
|
6
|
-
useParams,
|
|
7
|
-
} from "react-router";
|
|
2
|
+
import { useNavigate, useNavigation, useOutletContext, useParams } from "react-router";
|
|
8
3
|
|
|
9
4
|
import { UpdateUserForm } from "./updateUser.form";
|
|
10
|
-
import {
|
|
11
|
-
type UserWithGroups,
|
|
12
|
-
type GroupInfo,
|
|
13
|
-
} from "~/.server/auth/keycloakAdmin";
|
|
5
|
+
import { type UserWithGroups, type GroupInfo } from "~/.server/auth/keycloakAdmin";
|
|
14
6
|
import { RouteModal } from "~/components/RouteModal";
|
|
15
7
|
|
|
16
8
|
export { userDetailAction as action } from "./userDetail.action";
|
|
@@ -33,23 +25,13 @@ export default function UserModal() {
|
|
|
33
25
|
}
|
|
34
26
|
|
|
35
27
|
return (
|
|
36
|
-
<RouteModal
|
|
37
|
-
|
|
38
|
-
>
|
|
39
|
-
<UpdateUserForm
|
|
40
|
-
user={match.user}
|
|
41
|
-
groups={groups}
|
|
42
|
-
groupPaths={match.groupPaths}
|
|
43
|
-
/>
|
|
28
|
+
<RouteModal title={`Edit User \u2014 ${match.user.firstName} ${match.user.lastName}`}>
|
|
29
|
+
<UpdateUserForm user={match.user} groups={groups} groupPaths={match.groupPaths} />
|
|
44
30
|
<footer className="flex gap-3 justify-end mt-6">
|
|
45
31
|
<Button onPress={() => navigate(-1)} variant="secondary">
|
|
46
32
|
Cancel
|
|
47
33
|
</Button>
|
|
48
|
-
<Button
|
|
49
|
-
type="submit"
|
|
50
|
-
form="update-form"
|
|
51
|
-
isDisabled={isSubmitting}
|
|
52
|
-
>
|
|
34
|
+
<Button type="submit" form="update-form" isDisabled={isSubmitting}>
|
|
53
35
|
{isSubmitting ? "Saving..." : "Save Changes"}
|
|
54
36
|
</Button>
|
|
55
37
|
</footer>
|
|
@@ -5,19 +5,11 @@ import { assertGroupsInScope } from "../assertGroupsInScope";
|
|
|
5
5
|
import { assertUsersInScope } from "../assertUsersInScope";
|
|
6
6
|
import { authContext } from "~/.server/auth/authMiddleware";
|
|
7
7
|
import { getSession } from "~/.server/auth/getSession";
|
|
8
|
-
import {
|
|
9
|
-
addUserToGroup,
|
|
10
|
-
removeUserFromGroup,
|
|
11
|
-
updateUser,
|
|
12
|
-
} from "~/.server/auth/keycloakAdmin";
|
|
8
|
+
import { addUserToGroup, removeUserFromGroup, updateUser } from "~/.server/auth/keycloakAdmin";
|
|
13
9
|
import { sessionStorage } from "~/.server/auth/sessionStorage";
|
|
14
10
|
import { updateUserSchema } from "~/routes/admin/updateUser/updateUser.schema";
|
|
15
11
|
|
|
16
|
-
export const userDetailAction: ActionFunction = async ({
|
|
17
|
-
request,
|
|
18
|
-
context,
|
|
19
|
-
params,
|
|
20
|
-
}) => {
|
|
12
|
+
export const userDetailAction: ActionFunction = async ({ request, context, params }) => {
|
|
21
13
|
const { user } = context.get(authContext);
|
|
22
14
|
const { adminUrl, scope } = assertAdminScope(request.url, user.adminScopes);
|
|
23
15
|
|
|
@@ -3,17 +3,10 @@ import { Ban, Check, UserMinus, UserPlus } from "lucide-react";
|
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
4
|
import { useNavigation, useSubmit } from "react-router";
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
type GroupInfo,
|
|
8
|
-
type UserWithGroups,
|
|
9
|
-
} from "~/.server/auth/keycloakAdmin";
|
|
6
|
+
import { type GroupInfo, type UserWithGroups } from "~/.server/auth/keycloakAdmin";
|
|
10
7
|
import { ConfirmDialog } from "~/components/ConfirmDialog";
|
|
11
8
|
|
|
12
|
-
type BulkIntent =
|
|
13
|
-
| "addToGroup"
|
|
14
|
-
| "removeFromGroup"
|
|
15
|
-
| "enableAccounts"
|
|
16
|
-
| "disableAccounts";
|
|
9
|
+
type BulkIntent = "addToGroup" | "removeFromGroup" | "enableAccounts" | "disableAccounts";
|
|
17
10
|
|
|
18
11
|
interface BulkActionsProps {
|
|
19
12
|
selectedUserIds: string[];
|
|
@@ -81,12 +74,7 @@ function GroupSelector({
|
|
|
81
74
|
);
|
|
82
75
|
}
|
|
83
76
|
|
|
84
|
-
export function BulkActions({
|
|
85
|
-
selectedUserIds,
|
|
86
|
-
users,
|
|
87
|
-
groups,
|
|
88
|
-
onSuccess,
|
|
89
|
-
}: BulkActionsProps) {
|
|
77
|
+
export function BulkActions({ selectedUserIds, users, groups, onSuccess }: BulkActionsProps) {
|
|
90
78
|
const submit = useSubmit();
|
|
91
79
|
const { state } = useNavigation();
|
|
92
80
|
const isSubmitting = state === "submitting";
|
|
@@ -96,10 +84,7 @@ export function BulkActions({
|
|
|
96
84
|
const [selectedGroupId, setSelectedGroupId] = useState("");
|
|
97
85
|
|
|
98
86
|
const allGroupOptions = useMemo(
|
|
99
|
-
() =>
|
|
100
|
-
groups
|
|
101
|
-
.filter((g) => !g.isAdmin)
|
|
102
|
-
.map((g) => ({ id: g.id, name: g.path })),
|
|
87
|
+
() => groups.filter((g) => !g.isAdmin).map((g) => ({ id: g.id, name: g.path })),
|
|
103
88
|
[groups],
|
|
104
89
|
);
|
|
105
90
|
|
|
@@ -110,19 +95,13 @@ export function BulkActions({
|
|
|
110
95
|
|
|
111
96
|
// Remove: only groups at least one selected user is in
|
|
112
97
|
const removeGroupOptions = useMemo(
|
|
113
|
-
() =>
|
|
114
|
-
allGroupOptions.filter((o) =>
|
|
115
|
-
selectedUsers.some((u) => u.groupPaths.has(o.name)),
|
|
116
|
-
),
|
|
98
|
+
() => allGroupOptions.filter((o) => selectedUsers.some((u) => u.groupPaths.has(o.name))),
|
|
117
99
|
[allGroupOptions, selectedUsers],
|
|
118
100
|
);
|
|
119
101
|
|
|
120
102
|
// Add: only groups where at least one selected user is NOT yet a member
|
|
121
103
|
const addGroupOptions = useMemo(
|
|
122
|
-
() =>
|
|
123
|
-
allGroupOptions.filter((o) =>
|
|
124
|
-
selectedUsers.some((u) => !u.groupPaths.has(o.name)),
|
|
125
|
-
),
|
|
104
|
+
() => allGroupOptions.filter((o) => selectedUsers.some((u) => !u.groupPaths.has(o.name))),
|
|
126
105
|
[allGroupOptions, selectedUsers],
|
|
127
106
|
);
|
|
128
107
|
|
|
@@ -145,10 +124,7 @@ export function BulkActions({
|
|
|
145
124
|
const formData = new FormData();
|
|
146
125
|
formData.set("intent", intent);
|
|
147
126
|
formData.set("userIds", selectedUserIds.join(","));
|
|
148
|
-
if (
|
|
149
|
-
selectedGroupId &&
|
|
150
|
-
(intent === "addToGroup" || intent === "removeFromGroup")
|
|
151
|
-
) {
|
|
127
|
+
if (selectedGroupId && (intent === "addToGroup" || intent === "removeFromGroup")) {
|
|
152
128
|
formData.set("groupId", selectedGroupId);
|
|
153
129
|
}
|
|
154
130
|
|
|
@@ -215,15 +191,16 @@ export function BulkActions({
|
|
|
215
191
|
confirmVariant={config.confirmVariant}
|
|
216
192
|
>
|
|
217
193
|
<p className="text-sm text-slate-600">
|
|
218
|
-
This will affect
|
|
219
|
-
<span className="font-medium text-slate-900">{count}</span> user
|
|
194
|
+
This will affect <span className="font-medium text-slate-900">{count}</span> user
|
|
220
195
|
{count !== 1 ? "s" : ""}.
|
|
221
196
|
</p>
|
|
222
|
-
{config.needsGroup &&
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
197
|
+
{config.needsGroup && (
|
|
198
|
+
<GroupSelector
|
|
199
|
+
options={getGroupOptions(intent)}
|
|
200
|
+
value={selectedGroupId}
|
|
201
|
+
onChange={setSelectedGroupId}
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
227
204
|
</ConfirmDialog>
|
|
228
205
|
)}
|
|
229
206
|
</>
|
|
@@ -6,11 +6,7 @@ import { assertGroupsInScope } from "../assertGroupsInScope";
|
|
|
6
6
|
import { assertUsersInScope } from "../assertUsersInScope";
|
|
7
7
|
import { authContext } from "~/.server/auth/authMiddleware";
|
|
8
8
|
import { getSession } from "~/.server/auth/getSession";
|
|
9
|
-
import {
|
|
10
|
-
addUserToGroup,
|
|
11
|
-
removeUserFromGroup,
|
|
12
|
-
setUserEnabled,
|
|
13
|
-
} from "~/.server/auth/keycloakAdmin";
|
|
9
|
+
import { addUserToGroup, removeUserFromGroup, setUserEnabled } from "~/.server/auth/keycloakAdmin";
|
|
14
10
|
import { sessionStorage } from "~/.server/auth/sessionStorage";
|
|
15
11
|
|
|
16
12
|
const actionLabels = {
|
|
@@ -20,10 +16,7 @@ const actionLabels = {
|
|
|
20
16
|
disableAccounts: "disabled",
|
|
21
17
|
} as const;
|
|
22
18
|
|
|
23
|
-
export const bulkUsersAction: ActionFunction = async ({
|
|
24
|
-
request,
|
|
25
|
-
context,
|
|
26
|
-
}) => {
|
|
19
|
+
export const bulkUsersAction: ActionFunction = async ({ request, context }) => {
|
|
27
20
|
const { user } = context.get(authContext);
|
|
28
21
|
const { adminUrl, scope } = assertAdminScope(request.url, user.adminScopes);
|
|
29
22
|
|
|
@@ -2,12 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const bulkActionSchema = z
|
|
4
4
|
.object({
|
|
5
|
-
intent: z.enum([
|
|
6
|
-
"addToGroup",
|
|
7
|
-
"removeFromGroup",
|
|
8
|
-
"enableAccounts",
|
|
9
|
-
"disableAccounts",
|
|
10
|
-
]),
|
|
5
|
+
intent: z.enum(["addToGroup", "removeFromGroup", "enableAccounts", "disableAccounts"]),
|
|
11
6
|
userIds: z
|
|
12
7
|
.string()
|
|
13
8
|
.min(1, "At least one user is required")
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Badge,
|
|
3
|
-
Banner,
|
|
4
|
-
Button,
|
|
5
|
-
ButtonLink,
|
|
6
|
-
EmptyState,
|
|
7
|
-
Pill,
|
|
8
|
-
} from "@cytario/design";
|
|
1
|
+
import { Badge, Banner, Button, ButtonLink, EmptyState, Pill } from "@cytario/design";
|
|
9
2
|
import { type RowSelectionState } from "@tanstack/react-table";
|
|
10
3
|
import { FolderPlus, Plug, UserPlus, Users, UsersRound } from "lucide-react";
|
|
11
4
|
import { useMemo, useState } from "react";
|
|
@@ -19,19 +12,12 @@ import {
|
|
|
19
12
|
|
|
20
13
|
import { BulkActions } from "./BulkActions";
|
|
21
14
|
import type { ConnectionConfig } from "~/.generated/client";
|
|
22
|
-
import {
|
|
23
|
-
type UserWithGroups,
|
|
24
|
-
type GroupInfo,
|
|
25
|
-
} from "~/.server/auth/keycloakAdmin";
|
|
15
|
+
import { type UserWithGroups, type GroupInfo } from "~/.server/auth/keycloakAdmin";
|
|
26
16
|
import { Container, Section, SectionHeader } from "~/components/Container";
|
|
27
17
|
import { ProviderPill } from "~/components/Pills/ProviderPill";
|
|
28
18
|
import { ScopePill } from "~/components/Pills/ScopePill";
|
|
29
19
|
import { SelectionFooter } from "~/components/Table/SelectionFooter";
|
|
30
|
-
import {
|
|
31
|
-
type CellRenderers,
|
|
32
|
-
type ColumnConfig,
|
|
33
|
-
Table,
|
|
34
|
-
} from "~/components/Table/Table";
|
|
20
|
+
import { type CellRenderers, type ColumnConfig, Table } from "~/components/Table/Table";
|
|
35
21
|
import { useModal } from "~/hooks/useModal";
|
|
36
22
|
|
|
37
23
|
export const meta: MetaFunction = () => [{ title: "Admin — Users" }];
|
|
@@ -46,9 +32,7 @@ export const shouldRevalidate: ShouldRevalidateFunction = ({
|
|
|
46
32
|
defaultShouldRevalidate,
|
|
47
33
|
}) => {
|
|
48
34
|
if (formAction) return defaultShouldRevalidate;
|
|
49
|
-
return (
|
|
50
|
-
currentUrl.searchParams.get("scope") !== nextUrl.searchParams.get("scope")
|
|
51
|
-
);
|
|
35
|
+
return currentUrl.searchParams.get("scope") !== nextUrl.searchParams.get("scope");
|
|
52
36
|
};
|
|
53
37
|
|
|
54
38
|
interface UserRow {
|
|
@@ -65,10 +49,7 @@ function buildGroupColumn(
|
|
|
65
49
|
allGroups: GroupInfo[],
|
|
66
50
|
counts: Map<string, number>,
|
|
67
51
|
totalCount: number,
|
|
68
|
-
{
|
|
69
|
-
pillVisibleCount,
|
|
70
|
-
...extra
|
|
71
|
-
}: Partial<ColumnConfig> & { pillVisibleCount?: number } = {},
|
|
52
|
+
{ pillVisibleCount, ...extra }: Partial<ColumnConfig> & { pillVisibleCount?: number } = {},
|
|
72
53
|
): ColumnConfig {
|
|
73
54
|
const options = [
|
|
74
55
|
{ label: "All", value: "" },
|
|
@@ -159,25 +140,11 @@ function buildColumns(
|
|
|
159
140
|
],
|
|
160
141
|
filterRender: (option) => {
|
|
161
142
|
const label =
|
|
162
|
-
option.value === "true"
|
|
163
|
-
|
|
164
|
-
: option.value === "false"
|
|
165
|
-
? "Disabled"
|
|
166
|
-
: option.label;
|
|
167
|
-
return (
|
|
168
|
-
<Pill color={option.value === "true" ? "green" : "slate"}>
|
|
169
|
-
{label}
|
|
170
|
-
</Pill>
|
|
171
|
-
);
|
|
143
|
+
option.value === "true" ? "Active" : option.value === "false" ? "Disabled" : option.label;
|
|
144
|
+
return <Pill color={option.value === "true" ? "green" : "slate"}>{label}</Pill>;
|
|
172
145
|
},
|
|
173
146
|
},
|
|
174
|
-
buildGroupColumn(
|
|
175
|
-
"groups",
|
|
176
|
-
"Groups",
|
|
177
|
-
groups,
|
|
178
|
-
groupCounts,
|
|
179
|
-
totalCount,
|
|
180
|
-
),
|
|
147
|
+
buildGroupColumn("groups", "Groups", groups, groupCounts, totalCount),
|
|
181
148
|
];
|
|
182
149
|
}
|
|
183
150
|
|
|
@@ -193,9 +160,7 @@ function buildCellRenderers(scope: string): CellRenderers<UserRow> {
|
|
|
193
160
|
),
|
|
194
161
|
enabled: (row) => {
|
|
195
162
|
const label = row.enabled === "true" ? "Active" : "Disabled";
|
|
196
|
-
return
|
|
197
|
-
<Pill color={row.enabled === "true" ? "green" : "slate"}>{label}</Pill>
|
|
198
|
-
);
|
|
163
|
+
return <Pill color={row.enabled === "true" ? "green" : "slate"}>{label}</Pill>;
|
|
199
164
|
},
|
|
200
165
|
groups: (row) => (
|
|
201
166
|
<div className="flex flex-wrap gap-1">
|
|
@@ -279,21 +244,14 @@ export default function AdminUsersRoute() {
|
|
|
279
244
|
>
|
|
280
245
|
Bulk Invite
|
|
281
246
|
</ButtonLink>
|
|
282
|
-
<Button
|
|
283
|
-
variant="secondary"
|
|
284
|
-
iconLeft={Plug}
|
|
285
|
-
onPress={() => openModal("add-connection")}
|
|
286
|
-
>
|
|
247
|
+
<Button variant="secondary" iconLeft={Plug} onPress={() => openModal("add-connection")}>
|
|
287
248
|
Connect Storage
|
|
288
249
|
</Button>
|
|
289
250
|
</SectionHeader>
|
|
290
251
|
|
|
291
252
|
<Container>
|
|
292
253
|
<section aria-labelledby="connections-heading" className="mb-6">
|
|
293
|
-
<h3
|
|
294
|
-
id="connections-heading"
|
|
295
|
-
className="text-sm font-medium text-slate-500 mb-2"
|
|
296
|
-
>
|
|
254
|
+
<h3 id="connections-heading" className="text-sm font-medium text-slate-500 mb-2">
|
|
297
255
|
Connections
|
|
298
256
|
</h3>
|
|
299
257
|
{connections.length > 0 ? (
|
|
@@ -314,12 +272,8 @@ export default function AdminUsersRoute() {
|
|
|
314
272
|
))}
|
|
315
273
|
</ul>
|
|
316
274
|
) : (
|
|
317
|
-
<Banner
|
|
318
|
-
|
|
319
|
-
title="No connections linked to this group"
|
|
320
|
-
>
|
|
321
|
-
Members won't be able to access any data until you connect
|
|
322
|
-
storage.
|
|
275
|
+
<Banner variant="warning" title="No connections linked to this group">
|
|
276
|
+
Members won't be able to access any data until you connect storage.
|
|
323
277
|
</Banner>
|
|
324
278
|
)}
|
|
325
279
|
</section>
|
|
@@ -342,10 +296,7 @@ export default function AdminUsersRoute() {
|
|
|
342
296
|
title="No users yet"
|
|
343
297
|
description="Invite team members to get started."
|
|
344
298
|
action={
|
|
345
|
-
<ButtonLink
|
|
346
|
-
href={`/admin/users/invite?scope=${encodeURIComponent(scope)}`}
|
|
347
|
-
size="lg"
|
|
348
|
-
>
|
|
299
|
+
<ButtonLink href={`/admin/users/invite?scope=${encodeURIComponent(scope)}`} size="lg">
|
|
349
300
|
Invite User
|
|
350
301
|
</ButtonLink>
|
|
351
302
|
}
|
|
@@ -161,9 +161,13 @@ ${scopesXml}
|
|
|
161
161
|
<key>STS Endpoint</key>
|
|
162
162
|
<string>${escapeXml(stsEndpoint)}</string>
|
|
163
163
|
<key>Properties</key>
|
|
164
|
-
<dict>${
|
|
164
|
+
<dict>${
|
|
165
|
+
roleArn
|
|
166
|
+
? `
|
|
165
167
|
<key>s3.assumerole.rolearn</key>
|
|
166
|
-
<string>${escapeXml(roleArn)}</string>`
|
|
168
|
+
<string>${escapeXml(roleArn)}</string>`
|
|
169
|
+
: ""
|
|
170
|
+
}
|
|
167
171
|
${s3PropertiesXml} </dict>
|
|
168
172
|
</dict>
|
|
169
173
|
</plist>`;
|
|
@@ -48,10 +48,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
|
48
48
|
// Validate required parameters
|
|
49
49
|
if (!code || !state) {
|
|
50
50
|
console.error(`${label} Missing code or state parameter`);
|
|
51
|
-
return failWithNotification(
|
|
52
|
-
request,
|
|
53
|
-
"Authentication failed. Missing required parameters.",
|
|
54
|
-
);
|
|
51
|
+
return failWithNotification(request, "Authentication failed. Missing required parameters.");
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
try {
|
|
@@ -59,19 +56,13 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
|
59
56
|
const stateData = await validateOAuthState(state);
|
|
60
57
|
if (!stateData) {
|
|
61
58
|
console.error(`${label} Invalid or expired state parameter`);
|
|
62
|
-
return failWithNotification(
|
|
63
|
-
request,
|
|
64
|
-
"Authentication session expired. Please try again.",
|
|
65
|
-
);
|
|
59
|
+
return failWithNotification(request, "Authentication session expired. Please try again.");
|
|
66
60
|
}
|
|
67
61
|
|
|
68
62
|
// Guard for in-flight states from before PKCE deployment
|
|
69
63
|
if (!stateData.codeVerifier || !stateData.nonce) {
|
|
70
64
|
console.error(`${label} State missing codeVerifier or nonce`);
|
|
71
|
-
return failWithNotification(
|
|
72
|
-
request,
|
|
73
|
-
"Authentication session invalid. Please try again.",
|
|
74
|
-
);
|
|
65
|
+
return failWithNotification(request, "Authentication session invalid. Please try again.");
|
|
75
66
|
}
|
|
76
67
|
|
|
77
68
|
console.info(`${label} State validated successfully`);
|
|
@@ -81,28 +72,18 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
|
81
72
|
|
|
82
73
|
// Exchange authorization code for tokens with PKCE verifier
|
|
83
74
|
console.info(`${label} Exchanging authorization code for tokens`);
|
|
84
|
-
const tokens = await exchangeAuthCode(
|
|
85
|
-
code,
|
|
86
|
-
redirectUri,
|
|
87
|
-
stateData.codeVerifier,
|
|
88
|
-
);
|
|
75
|
+
const tokens = await exchangeAuthCode(code, redirectUri, stateData.codeVerifier);
|
|
89
76
|
|
|
90
77
|
// Verify ID token signature via JWKS and validate nonce from verified payload
|
|
91
78
|
const idTokenPayload = await verifyIdToken(tokens.id_token);
|
|
92
79
|
if (!idTokenPayload) {
|
|
93
80
|
console.error(`${label} ID token signature verification failed`);
|
|
94
|
-
return failWithNotification(
|
|
95
|
-
request,
|
|
96
|
-
"Authentication failed. Please try again.",
|
|
97
|
-
);
|
|
81
|
+
return failWithNotification(request, "Authentication failed. Please try again.");
|
|
98
82
|
}
|
|
99
83
|
|
|
100
84
|
if (idTokenPayload.nonce !== stateData.nonce) {
|
|
101
85
|
console.error(`${label} Nonce mismatch in ID token`);
|
|
102
|
-
return failWithNotification(
|
|
103
|
-
request,
|
|
104
|
-
"Authentication failed. Please try again.",
|
|
105
|
-
);
|
|
86
|
+
return failWithNotification(request, "Authentication failed. Please try again.");
|
|
106
87
|
}
|
|
107
88
|
|
|
108
89
|
// Get user info using the access token
|
|
@@ -126,20 +107,14 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
|
126
107
|
|
|
127
108
|
// Redirect to the originally requested page or default to home
|
|
128
109
|
const redirectTo = validateRedirectTo(stateData.redirectTo);
|
|
129
|
-
console.info(
|
|
130
|
-
`${label} Authentication successful, redirecting to:`,
|
|
131
|
-
redirectTo,
|
|
132
|
-
);
|
|
110
|
+
console.info(`${label} Authentication successful, redirecting to:`, redirectTo);
|
|
133
111
|
|
|
134
112
|
return redirect(redirectTo, {
|
|
135
113
|
headers: { "Set-Cookie": await sessionStorage.commitSession(session) },
|
|
136
114
|
});
|
|
137
115
|
} catch (error) {
|
|
138
116
|
console.error(`${label} Authentication failed:`, error);
|
|
139
|
-
return failWithNotification(
|
|
140
|
-
request,
|
|
141
|
-
"Authentication failed. Please try again.",
|
|
142
|
-
);
|
|
117
|
+
return failWithNotification(request, "Authentication failed. Please try again.");
|
|
143
118
|
}
|
|
144
119
|
};
|
|
145
120
|
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { LoaderFunctionArgs, redirect, MetaFunction } from "react-router";
|
|
2
2
|
|
|
3
3
|
import { getSession } from "~/.server/auth/getSession";
|
|
4
|
-
import {
|
|
5
|
-
generateOAuthState,
|
|
6
|
-
validateRedirectTo,
|
|
7
|
-
} from "~/.server/auth/oauthState";
|
|
4
|
+
import { generateOAuthState, validateRedirectTo } from "~/.server/auth/oauthState";
|
|
8
5
|
import { getWellKnownEndpoints } from "~/.server/auth/wellKnownEndpoints";
|
|
9
6
|
import { createLabel } from "~/.server/logging";
|
|
10
7
|
import { cytarioConfig } from "~/config";
|
|
@@ -14,8 +11,7 @@ export const meta: MetaFunction = () => {
|
|
|
14
11
|
{ title: "Cytario | Login" },
|
|
15
12
|
{
|
|
16
13
|
name: "description",
|
|
17
|
-
content:
|
|
18
|
-
"Log in to Cytario to access your secure workspace and manage your data.",
|
|
14
|
+
content: "Log in to Cytario to access your secure workspace and manage your data.",
|
|
19
15
|
},
|
|
20
16
|
];
|
|
21
17
|
};
|
|
@@ -75,10 +71,9 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
|
75
71
|
return redirect(authUrl.toString());
|
|
76
72
|
} catch (error) {
|
|
77
73
|
console.error(`${label} Failed to initiate login:`, error);
|
|
78
|
-
throw new Response(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
74
|
+
throw new Response("Unable to connect to authentication service. Please try again later.", {
|
|
75
|
+
status: 502,
|
|
76
|
+
});
|
|
82
77
|
}
|
|
83
78
|
};
|
|
84
79
|
|
|
@@ -86,7 +81,9 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
|
86
81
|
export default function LoginRoute() {
|
|
87
82
|
return (
|
|
88
83
|
<div className="flex items-center justify-center h-screen">
|
|
89
|
-
<p role="status" className="text-slate-500">
|
|
84
|
+
<p role="status" className="text-slate-500">
|
|
85
|
+
Redirecting to login...
|
|
86
|
+
</p>
|
|
90
87
|
</div>
|
|
91
88
|
);
|
|
92
89
|
}
|
|
@@ -31,18 +31,14 @@ const revokeRefreshToken = async (
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
if (!response.ok) {
|
|
34
|
-
console.warn(
|
|
35
|
-
`${label} Token revocation returned ${response.status}`,
|
|
36
|
-
);
|
|
34
|
+
console.warn(`${label} Token revocation returned ${response.status}`);
|
|
37
35
|
}
|
|
38
36
|
} catch (error) {
|
|
39
37
|
console.warn(`${label} Token revocation failed:`, error);
|
|
40
38
|
}
|
|
41
39
|
};
|
|
42
40
|
|
|
43
|
-
export const loader = async ({
|
|
44
|
-
request,
|
|
45
|
-
}: LoaderFunctionArgs): Promise<Response> => {
|
|
41
|
+
export const loader = async ({ request }: LoaderFunctionArgs): Promise<Response> => {
|
|
46
42
|
const session = await getSession(request);
|
|
47
43
|
const { authTokens } = await getSessionData(session);
|
|
48
44
|
|
|
@@ -52,18 +48,12 @@ export const loader = async ({
|
|
|
52
48
|
// Revoke refresh token before ending session (best-effort)
|
|
53
49
|
if (authTokens?.refreshToken) {
|
|
54
50
|
console.info(`${label} Revoking refresh token`);
|
|
55
|
-
await revokeRefreshToken(
|
|
56
|
-
authTokens.refreshToken,
|
|
57
|
-
wellKnownEndpoints.revocation_endpoint,
|
|
58
|
-
);
|
|
51
|
+
await revokeRefreshToken(authTokens.refreshToken, wellKnownEndpoints.revocation_endpoint);
|
|
59
52
|
}
|
|
60
53
|
|
|
61
54
|
// Build the logout URL with post_logout_redirect_uri
|
|
62
55
|
const logoutUrl = new URL(wellKnownEndpoints.end_session_endpoint);
|
|
63
|
-
logoutUrl.searchParams.set(
|
|
64
|
-
"post_logout_redirect_uri",
|
|
65
|
-
`${cytarioConfig.endpoints.webapp}/login`,
|
|
66
|
-
);
|
|
56
|
+
logoutUrl.searchParams.set("post_logout_redirect_uri", `${cytarioConfig.endpoints.webapp}/login`);
|
|
67
57
|
|
|
68
58
|
// Include id_token_hint for better logout behavior
|
|
69
59
|
if (authTokens?.idToken) {
|
|
@@ -23,11 +23,7 @@ function PreferencesSection() {
|
|
|
23
23
|
return (
|
|
24
24
|
<div className="flex flex-col gap-3">
|
|
25
25
|
<H2>Preferences</H2>
|
|
26
|
-
<Switch
|
|
27
|
-
isSelected={showHiddenFiles}
|
|
28
|
-
onChange={toggleShowHiddenFiles}
|
|
29
|
-
className="text-sm"
|
|
30
|
-
>
|
|
26
|
+
<Switch isSelected={showHiddenFiles} onChange={toggleShowHiddenFiles} className="text-sm">
|
|
31
27
|
Show hidden files
|
|
32
28
|
</Switch>
|
|
33
29
|
<p className="text-xs text-(--color-text-secondary)">
|