Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.

Commit 0c267c0

Browse files
author
Kerry
authored
Add more information to session detail page (#1659)
* rename `session` route to `browser-sessions` * add session detail route * stubbed route with userid * get session and display as session tile on session detail page * improve error message * useMemo instead of ref * oauth session detail page * compat session detail * link to session detail from compat and oauth sessions
1 parent 21d3d3a commit 0c267c0

File tree

10 files changed

+349
-16
lines changed

10 files changed

+349
-16
lines changed

frontend/src/components/CompatSession.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { atomFamily } from "jotai/utils";
1818
import { atomWithMutation } from "jotai-urql";
1919
import { useTransition } from "react";
2020

21+
import { Link } from "../Router";
2122
import { FragmentType, graphql, useFragment } from "../gql";
2223

2324
import { Session } from "./Session";
@@ -47,7 +48,7 @@ const END_SESSION_MUTATION = graphql(/* GraphQL */ `
4748
}
4849
`);
4950

50-
const endCompatSessionFamily = atomFamily((id: string) => {
51+
export const endCompatSessionFamily = atomFamily((id: string) => {
5152
const endCompatSession = atomWithMutation(END_SESSION_MUTATION);
5253

5354
// A proxy atom which pre-sets the id variable in the mutation
@@ -59,7 +60,7 @@ const endCompatSessionFamily = atomFamily((id: string) => {
5960
return endCompatSessionAtom;
6061
});
6162

62-
const simplifyUrl = (url: string): string => {
63+
export const simplifyUrl = (url: string): string => {
6364
let parsed;
6465
try {
6566
parsed = new URL(url);
@@ -93,14 +94,18 @@ const CompatSession: React.FC<{
9394
});
9495
};
9596

97+
const sessionName = (
98+
<Link route={{ type: "session", id: data.deviceId }}>{data.deviceId}</Link>
99+
);
100+
96101
const clientName = data.ssoLogin?.redirectUri
97102
? simplifyUrl(data.ssoLogin.redirectUri)
98103
: undefined;
99104

100105
return (
101106
<Session
102107
id={data.id}
103-
name={data.deviceId}
108+
name={sessionName}
104109
createdAt={data.createdAt}
105110
finishedAt={data.finishedAt || undefined}
106111
clientName={clientName}

frontend/src/components/OAuth2Session.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { atomFamily } from "jotai/utils";
1818
import { atomWithMutation } from "jotai-urql";
1919
import { useTransition } from "react";
2020

21+
import { Link } from "../Router";
2122
import { FragmentType, graphql, useFragment } from "../gql";
2223
import { getDeviceIdFromScope } from "../utils/deviceIdFromScope";
2324

@@ -39,7 +40,7 @@ export const OAUTH2_SESSION_FRAGMENT = graphql(/* GraphQL */ `
3940
}
4041
`);
4142

42-
type Oauth2SessionType = {
43+
export type Oauth2SessionType = {
4344
id: string;
4445
scope: string;
4546
createdAt: string;
@@ -64,7 +65,7 @@ const END_SESSION_MUTATION = graphql(/* GraphQL */ `
6465
}
6566
`);
6667

67-
const endSessionFamily = atomFamily((id: string) => {
68+
export const endSessionFamily = atomFamily((id: string) => {
6869
const endSession = atomWithMutation(END_SESSION_MUTATION);
6970

7071
// A proxy atom which pre-sets the id variable in the mutation
@@ -96,12 +97,16 @@ const OAuth2Session: React.FC<Props> = ({ session }) => {
9697
});
9798
};
9899

99-
const sessionName = getDeviceIdFromScope(data.scope);
100+
const deviceId = getDeviceIdFromScope(data.scope);
101+
102+
const name = deviceId && (
103+
<Link route={{ type: "session", id: deviceId }}>{deviceId}</Link>
104+
);
100105

101106
return (
102107
<Session
103108
id={data.id}
104-
name={sessionName}
109+
name={name}
105110
createdAt={data.createdAt}
106111
finishedAt={data.finishedAt || undefined}
107112
clientName={data.client.clientName}

frontend/src/components/Session/Session.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
import { H6, Body } from "@vector-im/compound-web";
16+
import { ReactNode } from "react";
1617

1718
import Block from "../Block";
1819
import DateTime from "../DateTime";
@@ -25,7 +26,7 @@ const SessionMetadata: React.FC<React.ComponentProps<typeof Body>> = (
2526

2627
export type SessionProps = {
2728
id: string;
28-
name?: string;
29+
name?: string | ReactNode;
2930
createdAt: string;
3031
finishedAt?: string;
3132
clientName?: string;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2022 The Matrix.org Foundation C.I.C.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { H3, Button } from "@vector-im/compound-web";
16+
import { useSetAtom } from "jotai";
17+
import { useTransition } from "react";
18+
19+
import { FragmentType, useFragment } from "../../gql";
20+
import BlockList from "../BlockList/BlockList";
21+
import {
22+
COMPAT_SESSION_FRAGMENT,
23+
endCompatSessionFamily,
24+
simplifyUrl,
25+
} from "../CompatSession";
26+
import DateTime from "../DateTime";
27+
28+
import SessionDetails from "./SessionDetails";
29+
30+
type Props = {
31+
session: FragmentType<typeof COMPAT_SESSION_FRAGMENT>;
32+
};
33+
34+
const CompatSessionDetail: React.FC<Props> = ({ session }) => {
35+
const [pending, startTransition] = useTransition();
36+
const data = useFragment(COMPAT_SESSION_FRAGMENT, session);
37+
const endSession = useSetAtom(endCompatSessionFamily(data.id));
38+
39+
// @TODO(kerrya) make this wait for session refresh properly
40+
// https://github.com/matrix-org/matrix-authentication-service/issues/1533
41+
const onSessionEnd = (): void => {
42+
startTransition(() => {
43+
endSession();
44+
});
45+
};
46+
47+
const finishedAt = data.finishedAt
48+
? [{ label: "Finished", value: <DateTime datetime={data.createdAt} /> }]
49+
: [];
50+
const sessionDetails = [
51+
{ label: "ID", value: <code>{data.id}</code> },
52+
{ label: "Device ID", value: <code>{data.deviceId}</code> },
53+
{ label: "Signed in", value: <DateTime datetime={data.createdAt} /> },
54+
...finishedAt,
55+
];
56+
57+
const clientName = data.ssoLogin?.redirectUri
58+
? simplifyUrl(data.ssoLogin.redirectUri)
59+
: undefined;
60+
61+
const clientDetails = [
62+
{ label: "Name", value: clientName },
63+
{
64+
label: "Uri",
65+
value: (
66+
<a target="_blank" href={data.ssoLogin?.redirectUri}>
67+
{data.ssoLogin?.redirectUri}
68+
</a>
69+
),
70+
},
71+
];
72+
73+
return (
74+
<div>
75+
<BlockList>
76+
<H3>{data.deviceId || data.id}</H3>
77+
<SessionDetails title="Session" details={sessionDetails} />
78+
<SessionDetails title="Client" details={clientDetails} />
79+
{!data.finishedAt && (
80+
<Button
81+
kind="destructive"
82+
size="sm"
83+
onClick={onSessionEnd}
84+
disabled={pending}
85+
>
86+
{/* @TODO(kerrya) put this back after pending state works properly */}
87+
{/* { pending && <LoadingSpinner />} */}
88+
End session
89+
</Button>
90+
)}
91+
</BlockList>
92+
</div>
93+
);
94+
};
95+
96+
export default CompatSessionDetail;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2022 The Matrix.org Foundation C.I.C.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { H3, Button } from "@vector-im/compound-web";
16+
import { useSetAtom } from "jotai";
17+
import { useTransition } from "react";
18+
19+
import { FragmentType, useFragment } from "../../gql";
20+
import { getDeviceIdFromScope } from "../../utils/deviceIdFromScope";
21+
import BlockList from "../BlockList/BlockList";
22+
import DateTime from "../DateTime";
23+
import {
24+
OAUTH2_SESSION_FRAGMENT,
25+
Oauth2SessionType,
26+
endSessionFamily,
27+
} from "../OAuth2Session";
28+
29+
import SessionDetails from "./SessionDetails";
30+
31+
type Props = {
32+
session: FragmentType<typeof OAUTH2_SESSION_FRAGMENT>;
33+
};
34+
35+
const OAuth2SessionDetail: React.FC<Props> = ({ session }) => {
36+
const [pending, startTransition] = useTransition();
37+
const data = useFragment(
38+
OAUTH2_SESSION_FRAGMENT,
39+
session,
40+
) as Oauth2SessionType;
41+
const endSession = useSetAtom(endSessionFamily(data.id));
42+
43+
// @TODO(kerrya) make this wait for session refresh properly
44+
// https://github.com/matrix-org/matrix-authentication-service/issues/1533
45+
const onSessionEnd = (): void => {
46+
startTransition(() => {
47+
endSession();
48+
});
49+
};
50+
51+
const deviceId = getDeviceIdFromScope(data.scope);
52+
53+
const scopes = data.scope.split(" ");
54+
55+
const finishedAt = data.finishedAt
56+
? [{ label: "Finished", value: <DateTime datetime={data.createdAt} /> }]
57+
: [];
58+
const sessionDetails = [
59+
{ label: "ID", value: <code>{data.id}</code> },
60+
{ label: "Device ID", value: <code>{deviceId}</code> },
61+
{ label: "Signed in", value: <DateTime datetime={data.createdAt} /> },
62+
...finishedAt,
63+
{
64+
label: "Scopes",
65+
value: (
66+
<>
67+
{scopes.map((scope) => (
68+
<p>
69+
<code key={scope}>{scope}</code>
70+
</p>
71+
))}
72+
</>
73+
),
74+
},
75+
];
76+
77+
const clientDetails = [
78+
{ label: "Name", value: data.client.clientName },
79+
{ label: "ID", value: <code>{data.client.clientId}</code> },
80+
{
81+
label: "Uri",
82+
value: (
83+
<a target="_blank" href={data.client.clientUri}>
84+
{data.client.clientUri}
85+
</a>
86+
),
87+
},
88+
];
89+
90+
return (
91+
<div>
92+
<BlockList>
93+
<H3>{deviceId || data.id}</H3>
94+
<SessionDetails title="Session" details={sessionDetails} />
95+
<SessionDetails title="Client" details={clientDetails} />
96+
{!data.finishedAt && (
97+
<Button
98+
kind="destructive"
99+
size="sm"
100+
onClick={onSessionEnd}
101+
disabled={pending}
102+
>
103+
{/* @TODO(kerrya) put this back after pending state works properly */}
104+
{/* { pending && <LoadingSpinner />} */}
105+
End session
106+
</Button>
107+
)}
108+
</BlockList>
109+
</div>
110+
);
111+
};
112+
113+
export default OAuth2SessionDetail;

frontend/src/components/SessionDetail/SessionDetail.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import { useMemo } from "react";
2020

2121
import { Link } from "../../Router";
2222
import { graphql } from "../../gql/gql";
23-
import CompatSession from "../CompatSession";
24-
import OAuth2Session from "../OAuth2Session";
23+
24+
import CompatSessionDetail from "./CompatSessionDetail";
25+
import OAuth2SessionDetail from "./OAuth2SessionDetail";
2526

2627
const QUERY = graphql(/* GraphQL */ `
2728
query SessionQuery($userId: ID!, $deviceId: String!) {
@@ -70,9 +71,9 @@ const SessionDetail: React.FC<{
7071
const sessionType = session.__typename;
7172

7273
if (sessionType === "Oauth2Session") {
73-
return <OAuth2Session session={session} />;
74+
return <OAuth2SessionDetail session={session} />;
7475
} else {
75-
return <CompatSession session={session} />;
76+
return <CompatSessionDetail session={session} />;
7677
}
7778
};
7879

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* Copyright 2023 The Matrix.org Foundation C.I.C.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
.list {
17+
display: flex;
18+
flex-direction: column;
19+
margin-top: var(--cpd-space-1x);
20+
gap: var(--cpd-space-1x);
21+
}
22+
23+
.detail-row {
24+
display: flex;
25+
flex-direction: row;
26+
gap: var(--cpd-space-4x);
27+
}
28+
29+
.detail-label {
30+
flex: 0 0 20%;
31+
color: var(--cpd-color-text-secondary);
32+
}
33+
34+
.detail-value {
35+
overflow-wrap: anywhere;
36+
}

0 commit comments

Comments
 (0)