You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-23 11:02:35 +03:00
browser session detail page
This commit is contained in:
committed by
Quentin Gliech
parent
0d5a700182
commit
de04b1679c
@@ -15,6 +15,7 @@
|
|||||||
import { atom, useSetAtom } from "jotai";
|
import { atom, useSetAtom } from "jotai";
|
||||||
import { atomFamily } from "jotai/utils";
|
import { atomFamily } from "jotai/utils";
|
||||||
import { atomWithMutation } from "jotai-urql";
|
import { atomWithMutation } from "jotai-urql";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
import { currentBrowserSessionIdAtom, currentUserIdAtom } from "../atoms";
|
import { currentBrowserSessionIdAtom, currentUserIdAtom } from "../atoms";
|
||||||
import { FragmentType, graphql, useFragment } from "../gql";
|
import { FragmentType, graphql, useFragment } from "../gql";
|
||||||
@@ -27,7 +28,7 @@ import {
|
|||||||
import EndSessionButton from "./Session/EndSessionButton";
|
import EndSessionButton from "./Session/EndSessionButton";
|
||||||
import Session from "./Session/Session";
|
import Session from "./Session/Session";
|
||||||
|
|
||||||
const FRAGMENT = graphql(/* GraphQL */ `
|
export const BROWSER_SESSION_FRAGMENT = graphql(/* GraphQL */ `
|
||||||
fragment BrowserSession_session on BrowserSession {
|
fragment BrowserSession_session on BrowserSession {
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
@@ -52,7 +53,7 @@ const END_SESSION_MUTATION = graphql(/* GraphQL */ `
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const endSessionFamily = atomFamily((id: string) => {
|
export const endBrowserSessionFamily = atomFamily((id: string) => {
|
||||||
const endSession = atomWithMutation(END_SESSION_MUTATION);
|
const endSession = atomWithMutation(END_SESSION_MUTATION);
|
||||||
|
|
||||||
// A proxy atom which pre-sets the id variable in the mutation
|
// A proxy atom which pre-sets the id variable in the mutation
|
||||||
@@ -64,22 +65,17 @@ const endSessionFamily = atomFamily((id: string) => {
|
|||||||
return endSessionAtom;
|
return endSessionAtom;
|
||||||
});
|
});
|
||||||
|
|
||||||
type Props = {
|
export const useEndBrowserSession = (
|
||||||
session: FragmentType<typeof FRAGMENT>;
|
sessionId: string,
|
||||||
isCurrent: boolean;
|
isCurrent: boolean,
|
||||||
};
|
): (() => Promise<void>) => {
|
||||||
|
const endSession = useSetAtom(endBrowserSessionFamily(sessionId));
|
||||||
const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
|
|
||||||
const data = useFragment(FRAGMENT, session);
|
|
||||||
const endSession = useSetAtom(endSessionFamily(data.id));
|
|
||||||
|
|
||||||
// Pull those atoms to reset them when the current session is ended
|
// Pull those atoms to reset them when the current session is ended
|
||||||
const currentUserId = useSetAtom(currentUserIdAtom);
|
const currentUserId = useSetAtom(currentUserIdAtom);
|
||||||
const currentBrowserSessionId = useSetAtom(currentBrowserSessionIdAtom);
|
const currentBrowserSessionId = useSetAtom(currentBrowserSessionIdAtom);
|
||||||
|
|
||||||
const createdAt = data.createdAt;
|
const onSessionEnd = useCallback(async (): Promise<void> => {
|
||||||
|
|
||||||
const onSessionEnd = async (): Promise<void> => {
|
|
||||||
await endSession();
|
await endSession();
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
currentBrowserSessionId({
|
currentBrowserSessionId({
|
||||||
@@ -89,8 +85,22 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
|
|||||||
requestPolicy: "network-only",
|
requestPolicy: "network-only",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}, [isCurrent, endSession, currentBrowserSessionId, currentUserId]);
|
||||||
|
|
||||||
|
return onSessionEnd;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
session: FragmentType<typeof BROWSER_SESSION_FRAGMENT>;
|
||||||
|
isCurrent: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
|
||||||
|
const data = useFragment(BROWSER_SESSION_FRAGMENT, session);
|
||||||
|
|
||||||
|
const onSessionEnd = useEndBrowserSession(data.id, isCurrent);
|
||||||
|
|
||||||
|
const createdAt = data.createdAt;
|
||||||
const deviceInformation = parseUserAgent(data.userAgent || undefined);
|
const deviceInformation = parseUserAgent(data.userAgent || undefined);
|
||||||
const sessionName =
|
const sessionName =
|
||||||
sessionNameFromDeviceInformation(deviceInformation) || "Browser session";
|
sessionNameFromDeviceInformation(deviceInformation) || "Browser session";
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { atomFamily } from "jotai/utils";
|
|||||||
import { atomWithQuery } from "jotai-urql";
|
import { atomWithQuery } from "jotai-urql";
|
||||||
import { useTransition } from "react";
|
import { useTransition } from "react";
|
||||||
|
|
||||||
import { currentBrowserSessionIdAtom, mapQueryAtom } from "../atoms";
|
import { mapQueryAtom } from "../atoms";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
import { BrowserSessionState, PageInfo } from "../gql/graphql";
|
import { BrowserSessionState, PageInfo } from "../gql/graphql";
|
||||||
import {
|
import {
|
||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
Pagination,
|
Pagination,
|
||||||
} from "../pagination";
|
} from "../pagination";
|
||||||
import { isErr, isOk, unwrapErr, unwrapOk } from "../result";
|
import { isErr, isOk, unwrapErr, unwrapOk } from "../result";
|
||||||
|
import { useCurrentBrowserSessionId } from "../utils/session/useCurrentBrowserSessionId";
|
||||||
|
|
||||||
import BlockList from "./BlockList";
|
import BlockList from "./BlockList";
|
||||||
import BrowserSession from "./BrowserSession";
|
import BrowserSession from "./BrowserSession";
|
||||||
@@ -112,20 +113,20 @@ const paginationFamily = atomFamily((userId: string) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
||||||
const currentSessionIdResult = useAtomValue(currentBrowserSessionIdAtom);
|
const { currentBrowserSessionId, currentBrowserSessionIdError } =
|
||||||
|
useCurrentBrowserSessionId();
|
||||||
const [pending, startTransition] = useTransition();
|
const [pending, startTransition] = useTransition();
|
||||||
const result = useAtomValue(browserSessionListFamily(userId));
|
const result = useAtomValue(browserSessionListFamily(userId));
|
||||||
const setPagination = useSetAtom(currentPaginationAtom);
|
const setPagination = useSetAtom(currentPaginationAtom);
|
||||||
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
|
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
|
||||||
const [filter, setFilter] = useAtom(filterAtom);
|
const [filter, setFilter] = useAtom(filterAtom);
|
||||||
|
|
||||||
if (isErr(currentSessionIdResult))
|
if (currentBrowserSessionIdError)
|
||||||
return <GraphQLError error={unwrapErr(currentSessionIdResult)} />;
|
return <GraphQLError error={currentBrowserSessionIdError} />;
|
||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||||
|
|
||||||
const browserSessions = unwrapOk(result);
|
const browserSessions = unwrapOk(result);
|
||||||
if (browserSessions === null) return <>Failed to load browser sessions</>;
|
if (browserSessions === null) return <>Failed to load browser sessions</>;
|
||||||
const currentSessionId = unwrapOk(currentSessionIdResult);
|
|
||||||
|
|
||||||
const paginate = (pagination: Pagination): void => {
|
const paginate = (pagination: Pagination): void => {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
@@ -165,7 +166,7 @@ const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
|
|||||||
<BrowserSession
|
<BrowserSession
|
||||||
key={n.cursor}
|
key={n.cursor}
|
||||||
session={n.node}
|
session={n.node}
|
||||||
isCurrent={n.node.id === currentSessionId}
|
isCurrent={n.node.id === currentBrowserSessionId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</BlockList>
|
</BlockList>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/* Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--cpd-space-1x);
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { H3, Badge } from "@vector-im/compound-web";
|
||||||
|
|
||||||
|
import { FragmentType, useFragment } from "../../gql";
|
||||||
|
import { BROWSER_SESSION_DETAIL_FRAGMENT } from "../../pages/BrowserSession";
|
||||||
|
import {
|
||||||
|
parseUserAgent,
|
||||||
|
sessionNameFromDeviceInformation,
|
||||||
|
} from "../../utils/parseUserAgent";
|
||||||
|
import { useCurrentBrowserSessionId } from "../../utils/session/useCurrentBrowserSessionId";
|
||||||
|
import BlockList from "../BlockList/BlockList";
|
||||||
|
import { useEndBrowserSession } from "../BrowserSession";
|
||||||
|
import DateTime from "../DateTime";
|
||||||
|
import GraphQLError from "../GraphQLError";
|
||||||
|
import EndSessionButton from "../Session/EndSessionButton";
|
||||||
|
|
||||||
|
import styles from "./BrowserSessionDetail.module.css";
|
||||||
|
import SessionDetails from "./SessionDetails";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
session: FragmentType<typeof BROWSER_SESSION_DETAIL_FRAGMENT>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BrowserSessionDetail: React.FC<Props> = ({ session }) => {
|
||||||
|
const data = useFragment(BROWSER_SESSION_DETAIL_FRAGMENT, session);
|
||||||
|
const { currentBrowserSessionId, currentBrowserSessionIdError } =
|
||||||
|
useCurrentBrowserSessionId();
|
||||||
|
|
||||||
|
const isCurrent = currentBrowserSessionId === data.id;
|
||||||
|
const onSessionEnd = useEndBrowserSession(data.id, isCurrent);
|
||||||
|
|
||||||
|
if (currentBrowserSessionIdError)
|
||||||
|
return <GraphQLError error={currentBrowserSessionIdError} />;
|
||||||
|
|
||||||
|
const deviceInformation = parseUserAgent(data.userAgent || undefined);
|
||||||
|
const sessionName =
|
||||||
|
sessionNameFromDeviceInformation(deviceInformation) || "Browser session";
|
||||||
|
|
||||||
|
const finishedAt = data.finishedAt
|
||||||
|
? [{ label: "Finished", value: <DateTime datetime={data.finishedAt} /> }]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const latestAuthentication = data.lastAuthentication
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: "Last Authentication",
|
||||||
|
value: <DateTime datetime={data.lastAuthentication.createdAt} />,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const sessionDetails = [
|
||||||
|
{ label: "ID", value: <code>{data.id}</code> },
|
||||||
|
{ label: "User ID", value: <code>{data.user.id}</code> },
|
||||||
|
{ label: "User Name", value: <code>{data.user.username}</code> },
|
||||||
|
{ label: "Signed in", value: <DateTime datetime={data.createdAt} /> },
|
||||||
|
...finishedAt,
|
||||||
|
...latestAuthentication,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BlockList>
|
||||||
|
<header className={styles.header}>
|
||||||
|
{isCurrent && <Badge kind="success">Current</Badge>}
|
||||||
|
<H3>{sessionName}</H3>
|
||||||
|
</header>
|
||||||
|
<SessionDetails title="Session" details={sessionDetails} />
|
||||||
|
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
|
||||||
|
</BlockList>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BrowserSessionDetail;
|
||||||
@@ -65,7 +65,9 @@ const documents = {
|
|||||||
types.VerifyEmailDocument,
|
types.VerifyEmailDocument,
|
||||||
"\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
"\n mutation ResendVerificationEmail($id: ID!) {\n sendVerificationEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n":
|
||||||
types.ResendVerificationEmailDocument,
|
types.ResendVerificationEmailDocument,
|
||||||
"\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n":
|
"\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n":
|
||||||
|
types.BrowserSession_DetailFragmentDoc,
|
||||||
|
"\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n ...BrowserSession_detail\n }\n }\n":
|
||||||
types.BrowserSessionQueryDocument,
|
types.BrowserSessionQueryDocument,
|
||||||
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n":
|
"\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n":
|
||||||
types.OAuth2ClientQueryDocument,
|
types.OAuth2ClientQueryDocument,
|
||||||
@@ -249,8 +251,14 @@ export function graphql(
|
|||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(
|
export function graphql(
|
||||||
source: "\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n",
|
source: "\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n",
|
||||||
): (typeof documents)["\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n"];
|
): (typeof documents)["\n fragment BrowserSession_detail on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n"];
|
||||||
|
/**
|
||||||
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
|
*/
|
||||||
|
export function graphql(
|
||||||
|
source: "\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n ...BrowserSession_detail\n }\n }\n",
|
||||||
|
): (typeof documents)["\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n ...BrowserSession_detail\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1516,23 +1516,33 @@ export type ResendVerificationEmailMutation = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BrowserSession_DetailFragment = {
|
||||||
|
__typename?: "BrowserSession";
|
||||||
|
id: string;
|
||||||
|
createdAt: any;
|
||||||
|
finishedAt?: any | null;
|
||||||
|
userAgent?: string | null;
|
||||||
|
lastAuthentication?: {
|
||||||
|
__typename?: "Authentication";
|
||||||
|
id: string;
|
||||||
|
createdAt: any;
|
||||||
|
} | null;
|
||||||
|
user: { __typename?: "User"; id: string; username: string };
|
||||||
|
} & { " $fragmentName"?: "BrowserSession_DetailFragment" };
|
||||||
|
|
||||||
export type BrowserSessionQueryQueryVariables = Exact<{
|
export type BrowserSessionQueryQueryVariables = Exact<{
|
||||||
id: Scalars["ID"]["input"];
|
id: Scalars["ID"]["input"];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type BrowserSessionQueryQuery = {
|
export type BrowserSessionQueryQuery = {
|
||||||
__typename?: "Query";
|
__typename?: "Query";
|
||||||
browserSession?: {
|
browserSession?:
|
||||||
__typename?: "BrowserSession";
|
| ({ __typename?: "BrowserSession"; id: string } & {
|
||||||
id: string;
|
" $fragmentRefs"?: {
|
||||||
createdAt: any;
|
BrowserSession_DetailFragment: BrowserSession_DetailFragment;
|
||||||
lastAuthentication?: {
|
};
|
||||||
__typename?: "Authentication";
|
})
|
||||||
id: string;
|
| null;
|
||||||
createdAt: any;
|
|
||||||
} | null;
|
|
||||||
user: { __typename?: "User"; id: string; username: string };
|
|
||||||
} | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OAuth2ClientQueryQueryVariables = Exact<{
|
export type OAuth2ClientQueryQueryVariables = Exact<{
|
||||||
@@ -1929,6 +1939,50 @@ export const UserEmail_VerifyEmailFragmentDoc = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as unknown as DocumentNode<UserEmail_VerifyEmailFragment, unknown>;
|
} as unknown as DocumentNode<UserEmail_VerifyEmailFragment, unknown>;
|
||||||
|
export const BrowserSession_DetailFragmentDoc = {
|
||||||
|
kind: "Document",
|
||||||
|
definitions: [
|
||||||
|
{
|
||||||
|
kind: "FragmentDefinition",
|
||||||
|
name: { kind: "Name", value: "BrowserSession_detail" },
|
||||||
|
typeCondition: {
|
||||||
|
kind: "NamedType",
|
||||||
|
name: { kind: "Name", value: "BrowserSession" },
|
||||||
|
},
|
||||||
|
selectionSet: {
|
||||||
|
kind: "SelectionSet",
|
||||||
|
selections: [
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "userAgent" } },
|
||||||
|
{
|
||||||
|
kind: "Field",
|
||||||
|
name: { kind: "Name", value: "lastAuthentication" },
|
||||||
|
selectionSet: {
|
||||||
|
kind: "SelectionSet",
|
||||||
|
selections: [
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "Field",
|
||||||
|
name: { kind: "Name", value: "user" },
|
||||||
|
selectionSet: {
|
||||||
|
kind: "SelectionSet",
|
||||||
|
selections: [
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "username" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as unknown as DocumentNode<BrowserSession_DetailFragment, unknown>;
|
||||||
export const CurrentViewerQueryDocument = {
|
export const CurrentViewerQueryDocument = {
|
||||||
kind: "Document",
|
kind: "Document",
|
||||||
definitions: [
|
definitions: [
|
||||||
@@ -4152,39 +4206,53 @@ export const BrowserSessionQueryDocument = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
selectionSet: {
|
||||||
|
kind: "SelectionSet",
|
||||||
|
selections: [
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||||
|
{
|
||||||
|
kind: "FragmentSpread",
|
||||||
|
name: { kind: "Name", value: "BrowserSession_detail" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "FragmentDefinition",
|
||||||
|
name: { kind: "Name", value: "BrowserSession_detail" },
|
||||||
|
typeCondition: {
|
||||||
|
kind: "NamedType",
|
||||||
|
name: { kind: "Name", value: "BrowserSession" },
|
||||||
|
},
|
||||||
|
selectionSet: {
|
||||||
|
kind: "SelectionSet",
|
||||||
|
selections: [
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "finishedAt" } },
|
||||||
|
{ kind: "Field", name: { kind: "Name", value: "userAgent" } },
|
||||||
|
{
|
||||||
|
kind: "Field",
|
||||||
|
name: { kind: "Name", value: "lastAuthentication" },
|
||||||
selectionSet: {
|
selectionSet: {
|
||||||
kind: "SelectionSet",
|
kind: "SelectionSet",
|
||||||
selections: [
|
selections: [
|
||||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||||
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
|
||||||
{
|
],
|
||||||
kind: "Field",
|
},
|
||||||
name: { kind: "Name", value: "lastAuthentication" },
|
},
|
||||||
selectionSet: {
|
{
|
||||||
kind: "SelectionSet",
|
kind: "Field",
|
||||||
selections: [
|
name: { kind: "Name", value: "user" },
|
||||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
selectionSet: {
|
||||||
{
|
kind: "SelectionSet",
|
||||||
kind: "Field",
|
selections: [
|
||||||
name: { kind: "Name", value: "createdAt" },
|
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||||
},
|
{ kind: "Field", name: { kind: "Name", value: "username" } },
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kind: "Field",
|
|
||||||
name: { kind: "Name", value: "user" },
|
|
||||||
selectionSet: {
|
|
||||||
kind: "SelectionSet",
|
|
||||||
selections: [
|
|
||||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
|
||||||
{
|
|
||||||
kind: "Field",
|
|
||||||
name: { kind: "Name", value: "username" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,22 +19,32 @@ import { atomWithQuery } from "jotai-urql";
|
|||||||
import { mapQueryAtom } from "../atoms";
|
import { mapQueryAtom } from "../atoms";
|
||||||
import GraphQLError from "../components/GraphQLError";
|
import GraphQLError from "../components/GraphQLError";
|
||||||
import NotFound from "../components/NotFound";
|
import NotFound from "../components/NotFound";
|
||||||
|
import BrowserSessionDetail from "../components/SessionDetail/BrowserSessionDetail";
|
||||||
import { graphql } from "../gql";
|
import { graphql } from "../gql";
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||||
|
|
||||||
|
export const BROWSER_SESSION_DETAIL_FRAGMENT = graphql(/* GraphQL */ `
|
||||||
|
fragment BrowserSession_detail on BrowserSession {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
finishedAt
|
||||||
|
userAgent
|
||||||
|
lastAuthentication {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
const QUERY = graphql(/* GraphQL */ `
|
const QUERY = graphql(/* GraphQL */ `
|
||||||
query BrowserSessionQuery($id: ID!) {
|
query BrowserSessionQuery($id: ID!) {
|
||||||
browserSession(id: $id) {
|
browserSession(id: $id) {
|
||||||
id
|
id
|
||||||
createdAt
|
...BrowserSession_detail
|
||||||
lastAuthentication {
|
|
||||||
id
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
user {
|
|
||||||
id
|
|
||||||
username
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
@@ -58,13 +68,9 @@ const BrowserSession: React.FC<{ id: string }> = ({ id }) => {
|
|||||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||||
|
|
||||||
const browserSession = unwrapOk(result);
|
const browserSession = unwrapOk(result);
|
||||||
if (browserSession === null) return <NotFound />;
|
if (!browserSession) return <NotFound />;
|
||||||
|
|
||||||
return (
|
return <BrowserSessionDetail session={browserSession} />;
|
||||||
<pre>
|
|
||||||
<code>{JSON.stringify(browserSession, null, 2)}</code>
|
|
||||||
</pre>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BrowserSession;
|
export default BrowserSession;
|
||||||
|
|||||||
Reference in New Issue
Block a user