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
Make the session list way better
This commit is contained in:
@@ -119,21 +119,19 @@
|
||||
"title": "Crypto identity reset temporarily allowed"
|
||||
}
|
||||
},
|
||||
"selectable_session": {
|
||||
"label": "Select session"
|
||||
},
|
||||
"session": {
|
||||
"current_badge": "Current",
|
||||
"current": "Current",
|
||||
"device_id_label": "Device ID",
|
||||
"finished_date": "Finished <datetime/>",
|
||||
"finished_label": "Finished",
|
||||
"id_label": "ID",
|
||||
"ip_label": "IP Address",
|
||||
"last_active_label": "Last Active",
|
||||
"last_auth_label": "Last Authentication",
|
||||
"name_for_platform": "{{name}} for {{platform}}",
|
||||
"scopes_label": "Scopes",
|
||||
"signed_in_date": "Signed in <datetime/>",
|
||||
"signed_in_label": "Signed in",
|
||||
"unknown_browser": "Unknown browser",
|
||||
"unknown_device": "Unknown device",
|
||||
"uri_label": "Uri",
|
||||
"user_id_label": "User ID",
|
||||
"username_label": "User name"
|
||||
|
||||
@@ -12,14 +12,19 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Badge } from "@vector-im/compound-web";
|
||||
import { parseISO } from "date-fns";
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation } from "urql";
|
||||
|
||||
import { FragmentType, graphql, useFragment } from "../gql";
|
||||
import { DeviceType } from "../gql/graphql";
|
||||
|
||||
import DateTime from "./DateTime";
|
||||
import EndSessionButton from "./Session/EndSessionButton";
|
||||
import Session from "./Session/Session";
|
||||
import LastActive from "./Session/LastActive";
|
||||
import * as Card from "./SessionCard";
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment BrowserSession_session on BrowserSession {
|
||||
@@ -77,38 +82,84 @@ type Props = {
|
||||
|
||||
const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onSessionEnd = useEndBrowserSession(data.id, isCurrent);
|
||||
|
||||
const deviceType = data.userAgent?.deviceType ?? DeviceType.Unknown;
|
||||
|
||||
let deviceName: string | null = null;
|
||||
let clientName: string | null = null;
|
||||
|
||||
// If we have a model, use that as the device name, and the browser (+ OS) as the client name
|
||||
if (data.userAgent?.model) {
|
||||
deviceName = data.userAgent.model;
|
||||
if (data.userAgent?.name) {
|
||||
if (data.userAgent?.os) {
|
||||
clientName = t("frontend.session.name_for_platform", {
|
||||
name: data.userAgent.name,
|
||||
platform: data.userAgent.os,
|
||||
});
|
||||
} else {
|
||||
clientName = data.userAgent.name;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Else use the browser as the device name
|
||||
deviceName = data.userAgent?.name ?? t("frontend.session.unknown_browser");
|
||||
// and if we have an OS, use that as the client name
|
||||
clientName = data.userAgent?.os ?? null;
|
||||
}
|
||||
|
||||
const createdAt = parseISO(data.createdAt);
|
||||
const finishedAt = data.finishedAt ? parseISO(data.finishedAt) : undefined;
|
||||
const lastActiveAt = data.lastActiveAt
|
||||
? parseISO(data.lastActiveAt)
|
||||
: undefined;
|
||||
let sessionName = "Browser session";
|
||||
if (data.userAgent) {
|
||||
if (data.userAgent.model && data.userAgent.name) {
|
||||
sessionName = `${data.userAgent.name} on ${data.userAgent.model}`;
|
||||
} else if (data.userAgent.name && data.userAgent.os) {
|
||||
sessionName = `${data.userAgent.name} on ${data.userAgent.os}`;
|
||||
} else if (data.userAgent.name) {
|
||||
sessionName = data.userAgent.name;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Session
|
||||
id={data.id}
|
||||
name={sessionName}
|
||||
createdAt={createdAt}
|
||||
finishedAt={finishedAt}
|
||||
isCurrent={isCurrent}
|
||||
deviceType={data.userAgent?.deviceType}
|
||||
lastActiveIp={data.lastActiveIp || undefined}
|
||||
lastActiveAt={lastActiveAt}
|
||||
<Card.Root>
|
||||
<Card.LinkBody
|
||||
to="/sessions/$id"
|
||||
params={{ id: data.id }}
|
||||
disabled={!!data.finishedAt}
|
||||
>
|
||||
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
|
||||
</Session>
|
||||
<Card.Header type={deviceType}>
|
||||
<Card.Name name={deviceName} />
|
||||
{clientName && <Card.Client name={clientName} />}
|
||||
</Card.Header>
|
||||
|
||||
<Card.Metadata>
|
||||
{lastActiveAt && !isCurrent && (
|
||||
<Card.Info label={t("frontend.session.last_active_label")}>
|
||||
<LastActive lastActive={lastActiveAt} />
|
||||
</Card.Info>
|
||||
)}
|
||||
|
||||
<Card.Info label={t("frontend.session.signed_in_label")}>
|
||||
<DateTime datetime={createdAt} />
|
||||
</Card.Info>
|
||||
|
||||
{isCurrent && (
|
||||
<Badge kind="success" className="self-center">
|
||||
{t("frontend.session.current")}
|
||||
</Badge>
|
||||
)}
|
||||
</Card.Metadata>
|
||||
</Card.LinkBody>
|
||||
|
||||
{!data.finishedAt && (
|
||||
<Card.Action>
|
||||
<EndSessionButton endSession={onSessionEnd}>
|
||||
<Card.Body compact>
|
||||
<Card.Header type={deviceType}>
|
||||
<Card.Name name={deviceName} />
|
||||
{clientName && <Card.Client name={clientName} />}
|
||||
</Card.Header>
|
||||
</Card.Body>
|
||||
</EndSessionButton>
|
||||
</Card.Action>
|
||||
)}
|
||||
</Card.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,12 +13,16 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { parseISO } from "date-fns";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation } from "urql";
|
||||
|
||||
import { FragmentType, graphql, useFragment } from "../gql";
|
||||
import { DeviceType } from "../gql/graphql";
|
||||
|
||||
import { Session } from "./Session";
|
||||
import DateTime from "./DateTime";
|
||||
import EndSessionButton from "./Session/EndSessionButton";
|
||||
import LastActive from "./Session/LastActive";
|
||||
import * as Card from "./SessionCard";
|
||||
|
||||
export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment CompatSession_session on CompatSession {
|
||||
@@ -29,7 +33,6 @@ export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
lastActiveIp
|
||||
lastActiveAt
|
||||
userAgent {
|
||||
raw
|
||||
name
|
||||
os
|
||||
model
|
||||
@@ -78,6 +81,7 @@ export const simplifyUrl = (url: string): string => {
|
||||
const CompatSession: React.FC<{
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
}> = ({ session }) => {
|
||||
const { t } = useTranslation();
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const [, endCompatSession] = useMutation(END_SESSION_MUTATION);
|
||||
|
||||
@@ -85,39 +89,68 @@ const CompatSession: React.FC<{
|
||||
await endCompatSession({ id: data.id });
|
||||
};
|
||||
|
||||
let clientName = data.ssoLogin?.redirectUri
|
||||
const clientName = data.ssoLogin?.redirectUri
|
||||
? simplifyUrl(data.ssoLogin.redirectUri)
|
||||
: undefined;
|
||||
|
||||
if (data.userAgent) {
|
||||
if (data.userAgent.model && data.userAgent.name) {
|
||||
clientName = `${data.userAgent.name} on ${data.userAgent.model}`;
|
||||
} else if (data.userAgent.name && data.userAgent.os) {
|
||||
clientName = `${data.userAgent.name} on ${data.userAgent.os}`;
|
||||
} else if (data.userAgent.name) {
|
||||
clientName = data.userAgent.name;
|
||||
}
|
||||
}
|
||||
const deviceType = data.userAgent?.deviceType ?? DeviceType.Unknown;
|
||||
|
||||
const deviceName =
|
||||
data.userAgent?.model ??
|
||||
(data.userAgent?.name
|
||||
? data.userAgent?.os
|
||||
? t("frontend.session.name_for_platform", {
|
||||
name: data.userAgent.name,
|
||||
platform: data.userAgent.os,
|
||||
})
|
||||
: data.userAgent.name
|
||||
: t("frontend.session.unknown_device"));
|
||||
|
||||
const createdAt = parseISO(data.createdAt);
|
||||
const finishedAt = data.finishedAt ? parseISO(data.finishedAt) : undefined;
|
||||
const lastActiveAt = data.lastActiveAt
|
||||
? parseISO(data.lastActiveAt)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Session
|
||||
id={data.id}
|
||||
name={data.deviceId}
|
||||
createdAt={createdAt}
|
||||
finishedAt={finishedAt}
|
||||
clientName={clientName}
|
||||
deviceType={data.userAgent?.deviceType}
|
||||
lastActiveIp={data.lastActiveIp || undefined}
|
||||
lastActiveAt={lastActiveAt}
|
||||
<Card.Root>
|
||||
<Card.LinkBody
|
||||
to="/sessions/$id"
|
||||
params={{ id: data.id }}
|
||||
disabled={!!data.finishedAt}
|
||||
>
|
||||
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
|
||||
</Session>
|
||||
<Card.Header type={deviceType}>
|
||||
<Card.Name name={deviceName} />
|
||||
{clientName && <Card.Client name={clientName} />}
|
||||
</Card.Header>
|
||||
|
||||
<Card.Metadata>
|
||||
{lastActiveAt && (
|
||||
<Card.Info label={t("frontend.session.last_active_label")}>
|
||||
<LastActive lastActive={lastActiveAt} />
|
||||
</Card.Info>
|
||||
)}
|
||||
<Card.Info label={t("frontend.session.signed_in_label")}>
|
||||
<DateTime datetime={createdAt} />
|
||||
</Card.Info>
|
||||
<Card.Info label={t("frontend.session.device_id_label")}>
|
||||
{data.deviceId}
|
||||
</Card.Info>
|
||||
</Card.Metadata>
|
||||
</Card.LinkBody>
|
||||
|
||||
{!data.finishedAt && (
|
||||
<Card.Action>
|
||||
<EndSessionButton endSession={onSessionEnd}>
|
||||
<Card.Body compact>
|
||||
<Card.Header type={deviceType}>
|
||||
<Card.Name name={deviceName} />
|
||||
{clientName && <Card.Client name={clientName} />}
|
||||
</Card.Header>
|
||||
</Card.Body>
|
||||
</EndSessionButton>
|
||||
</Card.Action>
|
||||
)}
|
||||
</Card.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,14 +13,17 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { parseISO } from "date-fns";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation } from "urql";
|
||||
|
||||
import { FragmentType, graphql, useFragment } from "../gql";
|
||||
import { DeviceType, Oauth2ApplicationType } from "../gql/graphql";
|
||||
import { getDeviceIdFromScope } from "../utils/deviceIdFromScope";
|
||||
|
||||
import { Session } from "./Session";
|
||||
import DateTime from "./DateTime";
|
||||
import EndSessionButton from "./Session/EndSessionButton";
|
||||
import LastActive from "./Session/LastActive";
|
||||
import * as Card from "./SessionCard";
|
||||
|
||||
export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment OAuth2Session_session on Oauth2Session {
|
||||
@@ -32,9 +35,9 @@ export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
lastActiveAt
|
||||
|
||||
userAgent {
|
||||
name
|
||||
model
|
||||
os
|
||||
osVersion
|
||||
deviceType
|
||||
}
|
||||
|
||||
@@ -77,6 +80,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const OAuth2Session: React.FC<Props> = ({ session }) => {
|
||||
const { t } = useTranslation();
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const [, endSession] = useMutation(END_SESSION_MUTATION);
|
||||
|
||||
@@ -87,7 +91,6 @@ const OAuth2Session: React.FC<Props> = ({ session }) => {
|
||||
const deviceId = getDeviceIdFromScope(data.scope);
|
||||
|
||||
const createdAt = parseISO(data.createdAt);
|
||||
const finishedAt = data.finishedAt ? parseISO(data.finishedAt) : undefined;
|
||||
const lastActiveAt = data.lastActiveAt
|
||||
? parseISO(data.lastActiveAt)
|
||||
: undefined;
|
||||
@@ -98,26 +101,67 @@ const OAuth2Session: React.FC<Props> = ({ session }) => {
|
||||
: data.userAgent?.deviceType) ??
|
||||
getDeviceTypeFromClientAppType(data.client.applicationType);
|
||||
|
||||
let clientName = data.client.clientName || data.client.clientId || undefined;
|
||||
const clientName = data.client.clientName || data.client.clientId;
|
||||
|
||||
if (data.userAgent?.model) {
|
||||
clientName = `${clientName} on ${data.userAgent.model}`;
|
||||
}
|
||||
const deviceName =
|
||||
data.userAgent?.model ??
|
||||
(data.userAgent?.name
|
||||
? data.userAgent?.os
|
||||
? t("frontend.session.name_for_platform", {
|
||||
name: data.userAgent.name,
|
||||
platform: data.userAgent.os,
|
||||
})
|
||||
: data.userAgent.name
|
||||
: t("frontend.session.unknown_device"));
|
||||
|
||||
return (
|
||||
<Session
|
||||
id={data.id}
|
||||
name={deviceId}
|
||||
createdAt={createdAt}
|
||||
finishedAt={finishedAt}
|
||||
clientName={clientName}
|
||||
clientLogoUri={data.client.logoUri || undefined}
|
||||
deviceType={deviceType}
|
||||
lastActiveIp={data.lastActiveIp || undefined}
|
||||
lastActiveAt={lastActiveAt}
|
||||
<Card.Root>
|
||||
<Card.LinkBody
|
||||
to="/sessions/$id"
|
||||
params={{ id: data.id }}
|
||||
disabled={!!data.finishedAt}
|
||||
>
|
||||
{!data.finishedAt && <EndSessionButton endSession={onSessionEnd} />}
|
||||
</Session>
|
||||
<Card.Header type={deviceType}>
|
||||
<Card.Name name={deviceName} />
|
||||
<Card.Client
|
||||
name={clientName}
|
||||
logoUri={data.client.logoUri ?? undefined}
|
||||
/>
|
||||
</Card.Header>
|
||||
|
||||
<Card.Metadata>
|
||||
{lastActiveAt && (
|
||||
<Card.Info label={t("frontend.session.last_active_label")}>
|
||||
<LastActive lastActive={lastActiveAt} />
|
||||
</Card.Info>
|
||||
)}
|
||||
<Card.Info label={t("frontend.session.signed_in_label")}>
|
||||
<DateTime datetime={createdAt} />
|
||||
</Card.Info>
|
||||
{deviceId && (
|
||||
<Card.Info label={t("frontend.session.device_id_label")}>
|
||||
{deviceId}
|
||||
</Card.Info>
|
||||
)}
|
||||
</Card.Metadata>
|
||||
</Card.LinkBody>
|
||||
|
||||
{!data.finishedAt && (
|
||||
<Card.Action>
|
||||
<EndSessionButton endSession={onSessionEnd}>
|
||||
<Card.Body compact>
|
||||
<Card.Header type={deviceType}>
|
||||
<Card.Name name={deviceName} />
|
||||
<Card.Client
|
||||
name={clientName}
|
||||
logoUri={data.client.logoUri ?? undefined}
|
||||
/>
|
||||
</Card.Header>
|
||||
</Card.Body>
|
||||
</EndSessionButton>
|
||||
</Card.Action>
|
||||
)}
|
||||
</Card.Root>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
*/
|
||||
|
||||
.icon {
|
||||
color: var(--cpd-color-icon-primary);
|
||||
background-color: var(--cpd-color-bg-subtle-primary);
|
||||
flex: 0 0 var(--cpd-space-4x);
|
||||
color: var(--cpd-color-icon-secondary);
|
||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
||||
box-sizing: content-box;
|
||||
height: var(--cpd-space-4x);
|
||||
width: var(--cpd-space-4x);
|
||||
height: var(--cpd-space-6x);
|
||||
width: var(--cpd-space-6x);
|
||||
padding: var(--cpd-space-2x);
|
||||
border-radius: var(--cpd-space-2x);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
.session {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
gap:var(--cpd-space-4x);
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.session-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-top: var(--cpd-space-1x);
|
||||
margin-bottom: var(--cpd-space-1x);
|
||||
}
|
||||
|
||||
.session-metadata {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--cpd-space-1x);
|
||||
color: var(--cpd-color-text-secondary);
|
||||
|
||||
&[data-finished] {
|
||||
color: var(--cpd-color-text-critical-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.session-actions {
|
||||
margin-top: var(--cpd-space-1x);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
// 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 type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { parseISO } from "date-fns";
|
||||
import { ReactElement } from "react";
|
||||
|
||||
import BlockList from "../BlockList/BlockList";
|
||||
|
||||
import Session from "./Session";
|
||||
|
||||
const meta = {
|
||||
title: "UI/Session/Session",
|
||||
component: Session,
|
||||
tags: ["autodocs"],
|
||||
|
||||
argTypes: {
|
||||
createdAt: { control: { type: "date" } },
|
||||
finishedAt: { control: { type: "date" } },
|
||||
lastActiveAt: { control: { type: "date" } },
|
||||
},
|
||||
|
||||
decorators: [
|
||||
(Story): ReactElement => (
|
||||
<div style={{ width: "378px" }}>
|
||||
<BlockList>
|
||||
<Story />
|
||||
</BlockList>
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof Session>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Session>;
|
||||
|
||||
const defaultProps = {
|
||||
id: "oauth2_session:01H5VAGA5NYTKJVXP3HMMKDJQ0",
|
||||
createdAt: parseISO("2023-06-29T03:35:17.451292+00:00"),
|
||||
};
|
||||
|
||||
export const BasicSession: Story = {
|
||||
args: {
|
||||
...defaultProps,
|
||||
name: "KlTqK9CRt3",
|
||||
lastActiveIp: "2001:8003:c4614:f501:3091:888a:49c7",
|
||||
lastActiveAt: parseISO("2023-07-29T03:35:17.451292+00:00"),
|
||||
clientName: "Element",
|
||||
},
|
||||
};
|
||||
|
||||
export const BasicFinishedSession: Story = {
|
||||
args: {
|
||||
...defaultProps,
|
||||
name: "Chrome on Android",
|
||||
finishedAt: parseISO("2023-06-30T03:35:17.451292+00:00"),
|
||||
},
|
||||
};
|
||||
|
||||
export const WithClientLogo: Story = {
|
||||
args: {
|
||||
...defaultProps,
|
||||
name: "KlTqK9CRt3",
|
||||
clientName: "Element",
|
||||
clientLogoUri: "https://element.io/images/logo-mark-primary.svg",
|
||||
},
|
||||
};
|
||||
|
||||
export const WithMinimumProps: Story = {
|
||||
args: defaultProps,
|
||||
};
|
||||
|
||||
export const WithChildActions: Story = {
|
||||
args: {
|
||||
...defaultProps,
|
||||
name: "KlTqK9CRt3",
|
||||
clientName: "Element",
|
||||
clientLogoUri: "https://element.io/images/logo-mark-primary.svg",
|
||||
children: (
|
||||
<>
|
||||
<Button size="sm" onClick={(): void => {}} kind="destructive">
|
||||
End session
|
||||
</Button>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -1,93 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { parseISO } from "date-fns";
|
||||
import { create } from "react-test-renderer";
|
||||
import { describe, expect, it, beforeAll } from "vitest";
|
||||
|
||||
import { mockLocale } from "../../test-utils/mockLocale";
|
||||
import { DummyRouter } from "../../test-utils/router";
|
||||
|
||||
import Session from "./Session";
|
||||
|
||||
describe("<Session />", () => {
|
||||
const defaultProps = {
|
||||
id: "session-id",
|
||||
createdAt: parseISO("2023-06-29T03:35:17.451292+00:00"),
|
||||
};
|
||||
|
||||
const finishedAt = parseISO("2023-06-29T03:35:19.451292+00:00");
|
||||
|
||||
beforeAll(() => mockLocale());
|
||||
|
||||
it("renders an active session", () => {
|
||||
const component = create(
|
||||
<DummyRouter>
|
||||
<Session {...defaultProps} />
|
||||
</DummyRouter>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished session", () => {
|
||||
const component = create(
|
||||
<DummyRouter>
|
||||
<Session {...defaultProps} finishedAt={finishedAt} />
|
||||
</DummyRouter>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("uses session name when truthy", () => {
|
||||
const name = "test session name";
|
||||
const component = create(
|
||||
<DummyRouter>
|
||||
<Session {...defaultProps} finishedAt={finishedAt} name={name} />
|
||||
</DummyRouter>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("uses client name when truthy", () => {
|
||||
const clientName = "Element";
|
||||
const component = create(
|
||||
<DummyRouter>
|
||||
<Session
|
||||
{...defaultProps}
|
||||
finishedAt={finishedAt}
|
||||
clientName={clientName}
|
||||
clientLogoUri="https://client.org/logo.png"
|
||||
/>
|
||||
</DummyRouter>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders ip address", () => {
|
||||
const clientName = "Element";
|
||||
const component = create(
|
||||
<DummyRouter>
|
||||
<Session
|
||||
{...defaultProps}
|
||||
finishedAt={finishedAt}
|
||||
clientName={clientName}
|
||||
lastActiveIp="127.0.0.1"
|
||||
/>
|
||||
</DummyRouter>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
// 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 { Link } from "@tanstack/react-router";
|
||||
import { H6, Text, Badge } from "@vector-im/compound-web";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
import { DeviceType } from "../../gql/graphql";
|
||||
import Block from "../Block";
|
||||
import DateTime from "../DateTime";
|
||||
|
||||
import ClientAvatar from "./ClientAvatar";
|
||||
import DeviceTypeIcon from "./DeviceTypeIcon";
|
||||
import LastActive from "./LastActive";
|
||||
import styles from "./Session.module.css";
|
||||
|
||||
const SessionMetadata: React.FC<React.ComponentProps<typeof Text>> = (
|
||||
props,
|
||||
) => <Text {...props} size="sm" className={styles.sessionMetadata} />;
|
||||
|
||||
type SessionProps = {
|
||||
id: string;
|
||||
name?: string;
|
||||
createdAt: Date;
|
||||
finishedAt?: Date;
|
||||
clientName?: string;
|
||||
clientLogoUri?: string;
|
||||
isCurrent?: boolean;
|
||||
deviceType?: DeviceType;
|
||||
lastActiveIp?: string;
|
||||
lastActiveAt?: Date;
|
||||
};
|
||||
|
||||
const Session: React.FC<React.PropsWithChildren<SessionProps>> = ({
|
||||
id,
|
||||
name,
|
||||
createdAt,
|
||||
finishedAt,
|
||||
clientName,
|
||||
clientLogoUri,
|
||||
lastActiveIp,
|
||||
lastActiveAt,
|
||||
isCurrent,
|
||||
children,
|
||||
deviceType,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Block className={styles.session}>
|
||||
<DeviceTypeIcon deviceType={deviceType || DeviceType.Unknown} />
|
||||
<div className={styles.container}>
|
||||
{isCurrent && (
|
||||
<Badge kind="success">{t("frontend.session.current_badge")}</Badge>
|
||||
)}
|
||||
<H6 className={styles.sessionName} title={id}>
|
||||
<Link to="/sessions/$id" params={{ id }}>
|
||||
{name || id}
|
||||
</Link>
|
||||
</H6>
|
||||
<SessionMetadata weight="semibold">
|
||||
<Trans
|
||||
i18nKey="frontend.session.signed_in_date"
|
||||
components={{ datetime: <DateTime datetime={createdAt} /> }}
|
||||
/>
|
||||
</SessionMetadata>
|
||||
{!!finishedAt && (
|
||||
<SessionMetadata weight="semibold" data-finished={true}>
|
||||
<Trans
|
||||
i18nKey="frontend.session.finished_date"
|
||||
components={{ datetime: <DateTime datetime={finishedAt} /> }}
|
||||
/>
|
||||
</SessionMetadata>
|
||||
)}
|
||||
{!!lastActiveAt && (
|
||||
<SessionMetadata>
|
||||
<LastActive lastActive={lastActiveAt} />
|
||||
</SessionMetadata>
|
||||
)}
|
||||
{!!lastActiveIp && <SessionMetadata>{lastActiveIp}</SessionMetadata>}
|
||||
{!!clientName && (
|
||||
<SessionMetadata>
|
||||
<ClientAvatar
|
||||
size="var(--cpd-space-4x)"
|
||||
name={clientName}
|
||||
logoUri={clientLogoUri}
|
||||
/>{" "}
|
||||
<SessionMetadata weight="semibold" as="span">
|
||||
{clientName}
|
||||
</SessionMetadata>
|
||||
</SessionMetadata>
|
||||
)}
|
||||
{!!children && <div className={styles.sessionActions}>{children}</div>}
|
||||
</div>
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
export default Session;
|
||||
165
frontend/src/components/SessionCard/SessionCard.module.css
Normal file
165
frontend/src/components/SessionCard/SessionCard.module.css
Normal file
@@ -0,0 +1,165 @@
|
||||
/* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
.root {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.root:has(.action) .body .header {
|
||||
/* We can't exactly know the width of the action button,
|
||||
* so we use a somewhat arbitrary safe value:
|
||||
* - the button padding is 4x + 5x
|
||||
* - the icon is 5x wide
|
||||
* - a 2x safe margin
|
||||
* - the approximate width of the button text "Sign out"
|
||||
*
|
||||
* Of course that depends on the translation, but it's a good start
|
||||
*/
|
||||
padding-inline-end: calc(var(--cpd-space-16x) + 10ch);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.root:has(.action) .body {
|
||||
/* On small screen, the action button is at the bottom, and we can accurately
|
||||
* calculate the height of the button:
|
||||
*
|
||||
* - the button padding is 1x + 1x
|
||||
* - its line height is 6.5x
|
||||
* - plus 2x 1px of border (so 0.5x)
|
||||
* - the 4x margin on top of the button
|
||||
* - the regular 6x padding
|
||||
*/
|
||||
padding-block-end: calc(var(--cpd-space-9x) + var(--cpd-space-6x) + var(--cpd-space-6x));
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
gap: var(--cpd-space-4x);
|
||||
flex-direction: column;
|
||||
|
||||
border-radius: var(--cpd-space-4x);
|
||||
background-color: var(--cpd-color-bg-canvas-default);
|
||||
outline: 1px solid var(--cpd-color-border-interactive-secondary);
|
||||
outline-offset: -1px;
|
||||
box-shadow: 0px 1.2px 2.4px 0px rgba(0, 0, 0, 0.15);
|
||||
padding: var(--cpd-space-6x);
|
||||
|
||||
&.disabled {
|
||||
outline-color: var(--cpd-color-border-disabled);
|
||||
background-color: var(--cpd-color-bg-canvas-disabled);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.compact {
|
||||
box-shadow: none;
|
||||
padding: var(--cpd-space-3x);
|
||||
}
|
||||
}
|
||||
|
||||
a.body:not(.disabled) {
|
||||
transition-property: outline-color, box-shadow;
|
||||
transition-duration: 0.1s;
|
||||
transition-timing-function: linear;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
box-shadow: none;
|
||||
outline: 2px solid var(--cpd-color-border-interactive-hovered);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline-color: var(--cpd-color-border-focused);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.action {
|
||||
position: absolute;
|
||||
/* This padding creates a safe area for the action button */
|
||||
padding: var(--cpd-space-6x) var(--cpd-space-6x) var(--cpd-space-2x) var(--cpd-space-2x);
|
||||
inset-block-start: 0;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
padding: var(--cpd-space-6x);
|
||||
inset-block-end: 0;
|
||||
inset-inline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
gap: var(--cpd-space-4x);
|
||||
align-items: center;
|
||||
|
||||
& .content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
/* This makes sure it can shrink, and that the text doesn't overflow */
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
|
||||
& .name {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font: var(--cpd-font-body-md-semibold);
|
||||
letter-spacing: var(--cpd-font-letter-spacing-body-md);
|
||||
color: var(--cpd-color-text-primary);
|
||||
}
|
||||
|
||||
& .client {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font: var(--cpd-font-body-sm-regular);
|
||||
letter-spacing: var(--cpd-font-letter-spacing-body-sm);
|
||||
color: var(--cpd-color-text-secondary);
|
||||
|
||||
& img {
|
||||
margin-inline-end: var(--cpd-space-1x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.metadata {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--cpd-space-4x) var(--cpd-space-10x);
|
||||
|
||||
& .key {
|
||||
font: var(--cpd-font-body-sm-regular);
|
||||
letter-spacing: var(--cpd-font-letter-spacing-body-sm);
|
||||
color: var(--cpd-color-text-secondary);
|
||||
}
|
||||
|
||||
& .value {
|
||||
font: var(--cpd-font-body-md-regular);
|
||||
letter-spacing: var(--cpd-font-letter-spacing-body-md);
|
||||
color: var(--cpd-color-text-primary);
|
||||
}
|
||||
}
|
||||
75
frontend/src/components/SessionCard/SessionCard.stories.tsx
Normal file
75
frontend/src/components/SessionCard/SessionCard.stories.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2024 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 type { Meta, StoryObj } from "@storybook/react";
|
||||
import IconSignOut from "@vector-im/compound-design-tokens/icons/sign-out.svg?react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { DeviceType } from "../../gql/graphql";
|
||||
|
||||
import * as Card from "./SessionCard";
|
||||
|
||||
const Template: React.FC<{
|
||||
deviceType: DeviceType;
|
||||
deviceName: string;
|
||||
clientName?: string;
|
||||
disabled?: boolean;
|
||||
}> = ({ deviceType, deviceName, clientName, disabled }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Card.Root>
|
||||
<Card.Body disabled={disabled}>
|
||||
<Card.Header type={deviceType}>
|
||||
<Card.Name name={deviceName} />
|
||||
{clientName && <Card.Client name={clientName} />}
|
||||
</Card.Header>
|
||||
<Card.Metadata>
|
||||
<Card.Info label="Last active">2 hours ago</Card.Info>
|
||||
<Card.Info label="Signed in">NOV 30, 2023</Card.Info>
|
||||
<Card.Info label="Device ID">XXXXXX</Card.Info>
|
||||
</Card.Metadata>
|
||||
</Card.Body>
|
||||
{!disabled && (
|
||||
<Card.Action>
|
||||
<Button kind="secondary" destructive size="sm" Icon={IconSignOut}>
|
||||
{t("frontend.end_session_button.text")}
|
||||
</Button>
|
||||
</Card.Action>
|
||||
)}
|
||||
</Card.Root>
|
||||
);
|
||||
};
|
||||
|
||||
const meta = {
|
||||
title: "UI/Session/Card",
|
||||
component: Template,
|
||||
args: {
|
||||
disabled: false,
|
||||
deviceName: "MacBook Pro 16",
|
||||
clientName: "Firefox",
|
||||
deviceType: DeviceType.Pc,
|
||||
},
|
||||
argTypes: {
|
||||
deviceType: { control: "select", options: Object.values(DeviceType) },
|
||||
disabled: { control: "boolean" },
|
||||
deviceName: { control: "text" },
|
||||
clientName: { control: "text" },
|
||||
},
|
||||
} satisfies Meta<typeof Template>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Template>;
|
||||
|
||||
export const Basic: Story = {};
|
||||
101
frontend/src/components/SessionCard/SessionCard.tsx
Normal file
101
frontend/src/components/SessionCard/SessionCard.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2024 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 { LinkComponent, useLinkProps } from "@tanstack/react-router";
|
||||
import cx from "classnames";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
import { DeviceType } from "../../gql/graphql";
|
||||
import ClientAvatar from "../Session/ClientAvatar";
|
||||
import DeviceTypeIcon from "../Session/DeviceTypeIcon";
|
||||
|
||||
import styles from "./SessionCard.module.css";
|
||||
|
||||
export const Root: React.FC<React.PropsWithChildren> = ({ children }) => (
|
||||
<section className={styles.root}>{children}</section>
|
||||
);
|
||||
|
||||
type BodyProps = React.PropsWithChildren<{
|
||||
disabled?: boolean;
|
||||
compact?: boolean;
|
||||
}>;
|
||||
export const LinkBody: LinkComponent = forwardRef<
|
||||
HTMLAnchorElement,
|
||||
Parameters<typeof useLinkProps>[0] & BodyProps
|
||||
>(({ children, disabled, compact, ...props }, ref) => {
|
||||
const linkProps = useLinkProps({
|
||||
className: cx(
|
||||
styles.body,
|
||||
compact && styles.compact,
|
||||
disabled && styles.disabled,
|
||||
),
|
||||
...props,
|
||||
});
|
||||
return (
|
||||
<a ref={ref} {...linkProps}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}) as LinkComponent;
|
||||
|
||||
export const Body: React.FC<BodyProps> = ({ children, compact, disabled }) => (
|
||||
<div
|
||||
className={cx(
|
||||
styles.body,
|
||||
compact && styles.compact,
|
||||
disabled && styles.disabled,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
type HeaderProps = React.PropsWithChildren<{ type: DeviceType }>;
|
||||
export const Header: React.FC<HeaderProps> = ({ type, children }) => (
|
||||
<header className={styles.header}>
|
||||
<DeviceTypeIcon deviceType={type} />
|
||||
<div className={styles.content}>{children}</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
type NameProps = { name: string };
|
||||
export const Name: React.FC<NameProps> = ({ name }) => (
|
||||
<div className={styles.name}>{name}</div>
|
||||
);
|
||||
|
||||
type ClientProps = { name: string; logoUri?: string };
|
||||
export const Client: React.FC<ClientProps> = ({ name, logoUri }) => (
|
||||
<div className={styles.client}>
|
||||
<ClientAvatar name={name} size="var(--cpd-space-5x)" logoUri={logoUri} />
|
||||
{name}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Metadata: React.FC<React.PropsWithChildren> = ({ children }) => (
|
||||
<ul className={styles.metadata}>{children}</ul>
|
||||
);
|
||||
|
||||
export const Info: React.FC<React.PropsWithChildren<{ label: string }>> = ({
|
||||
label,
|
||||
children,
|
||||
}) => (
|
||||
<li>
|
||||
<div className={styles.key}>{label}</div>
|
||||
<div className={styles.value}>{children}</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
export const Action: React.FC<React.PropsWithChildren> = ({ children }) => (
|
||||
<div className={styles.action}>{children}</div>
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
// Copyright 2024 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.
|
||||
@@ -12,4 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
export { default as Session } from "./Session";
|
||||
export {
|
||||
Root,
|
||||
LinkBody,
|
||||
Body,
|
||||
Header,
|
||||
Name,
|
||||
Client,
|
||||
Metadata,
|
||||
Info,
|
||||
Action,
|
||||
} from "./SessionCard";
|
||||
@@ -1,8 +1,21 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<CompatSession /> > renders a finished session 1`] = `
|
||||
<div
|
||||
className="_block_17898c _session_634806"
|
||||
<section
|
||||
className="_root_e2909e"
|
||||
>
|
||||
<a
|
||||
className="_body_e2909e _disabled_e2909e"
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
>
|
||||
<header
|
||||
className="_header_e2909e"
|
||||
>
|
||||
<svg
|
||||
aria-label="Unknown device type"
|
||||
@@ -21,67 +34,72 @@ exports[`<CompatSession /> > renders a finished session 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
className="_container_634806"
|
||||
className="_content_e2909e"
|
||||
>
|
||||
<h6
|
||||
className="_typography_yh5dq_162 _font-body-md-semibold_yh5dq_64 _sessionName_634806"
|
||||
title="session-id"
|
||||
<div
|
||||
className="_name_e2909e"
|
||||
>
|
||||
<a
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
Unknown device
|
||||
</div>
|
||||
<div
|
||||
className="_client_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</a>
|
||||
</h6>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
element.io
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<ul
|
||||
className="_metadata_e2909e"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Signed in
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
<time
|
||||
dateTime="2023-06-29T03:35:17Z"
|
||||
>
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
data-finished={true}
|
||||
>
|
||||
Finished
|
||||
<time
|
||||
dateTime="2023-06-29T03:35:19Z"
|
||||
>
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
1.2.3.4
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
|
||||
<span
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
>
|
||||
element.io
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Device ID
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</a>
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`<CompatSession /> > renders an active session 1`] = `
|
||||
<div
|
||||
className="_block_17898c _session_634806"
|
||||
<section
|
||||
className="_root_e2909e"
|
||||
>
|
||||
<a
|
||||
className="_body_e2909e"
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
>
|
||||
<header
|
||||
className="_header_e2909e"
|
||||
>
|
||||
<svg
|
||||
aria-label="Unknown device type"
|
||||
@@ -100,51 +118,55 @@ exports[`<CompatSession /> > renders an active session 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
className="_container_634806"
|
||||
className="_content_e2909e"
|
||||
>
|
||||
<h6
|
||||
className="_typography_yh5dq_162 _font-body-md-semibold_yh5dq_64 _sessionName_634806"
|
||||
title="session-id"
|
||||
<div
|
||||
className="_name_e2909e"
|
||||
>
|
||||
<a
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
Unknown device
|
||||
</div>
|
||||
<div
|
||||
className="_client_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</a>
|
||||
</h6>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
element.io
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<ul
|
||||
className="_metadata_e2909e"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Signed in
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
<time
|
||||
dateTime="2023-06-29T03:35:17Z"
|
||||
>
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
1.2.3.4
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
|
||||
<span
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
>
|
||||
element.io
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
className="_sessionActions_634806"
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Device ID
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</a>
|
||||
<div
|
||||
className="_action_e2909e"
|
||||
>
|
||||
<button
|
||||
aria-controls="radix-:r0:"
|
||||
@@ -174,6 +196,5 @@ exports[`<CompatSession /> > renders an active session 1`] = `
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<OAuth2Session /> > renders a finished session 1`] = `
|
||||
<div
|
||||
className="_block_17898c _session_634806"
|
||||
<section
|
||||
className="_root_e2909e"
|
||||
>
|
||||
<a
|
||||
className="_body_e2909e _disabled_e2909e"
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
>
|
||||
<header
|
||||
className="_header_e2909e"
|
||||
>
|
||||
<svg
|
||||
aria-label="Computer"
|
||||
@@ -18,67 +31,72 @@ exports[`<OAuth2Session /> > renders a finished session 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
className="_container_634806"
|
||||
className="_content_e2909e"
|
||||
>
|
||||
<h6
|
||||
className="_typography_yh5dq_162 _font-body-md-semibold_yh5dq_64 _sessionName_634806"
|
||||
title="session-id"
|
||||
<div
|
||||
className="_name_e2909e"
|
||||
>
|
||||
<a
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
Unknown device
|
||||
</div>
|
||||
<div
|
||||
className="_client_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</a>
|
||||
</h6>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
Element
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<ul
|
||||
className="_metadata_e2909e"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Signed in
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
<time
|
||||
dateTime="2023-06-29T03:35:17Z"
|
||||
>
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
data-finished={true}
|
||||
>
|
||||
Finished
|
||||
<time
|
||||
dateTime="2023-06-29T03:35:19Z"
|
||||
>
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
1.2.3.4
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
|
||||
<span
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
>
|
||||
Element
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Device ID
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</a>
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`<OAuth2Session /> > renders an active session 1`] = `
|
||||
<div
|
||||
className="_block_17898c _session_634806"
|
||||
<section
|
||||
className="_root_e2909e"
|
||||
>
|
||||
<a
|
||||
className="_body_e2909e"
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
>
|
||||
<header
|
||||
className="_header_e2909e"
|
||||
>
|
||||
<svg
|
||||
aria-label="Computer"
|
||||
@@ -94,51 +112,55 @@ exports[`<OAuth2Session /> > renders an active session 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
className="_container_634806"
|
||||
className="_content_e2909e"
|
||||
>
|
||||
<h6
|
||||
className="_typography_yh5dq_162 _font-body-md-semibold_yh5dq_64 _sessionName_634806"
|
||||
title="session-id"
|
||||
<div
|
||||
className="_name_e2909e"
|
||||
>
|
||||
<a
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
Unknown device
|
||||
</div>
|
||||
<div
|
||||
className="_client_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</a>
|
||||
</h6>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
Element
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<ul
|
||||
className="_metadata_e2909e"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Signed in
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
<time
|
||||
dateTime="2023-06-29T03:35:17Z"
|
||||
>
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
1.2.3.4
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
|
||||
<span
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
>
|
||||
Element
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
className="_sessionActions_634806"
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Device ID
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</a>
|
||||
<div
|
||||
className="_action_e2909e"
|
||||
>
|
||||
<button
|
||||
aria-controls="radix-:r0:"
|
||||
@@ -168,13 +190,25 @@ exports[`<OAuth2Session /> > renders an active session 1`] = `
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
exports[`<OAuth2Session /> > renders correct icon for a native session 1`] = `
|
||||
<div
|
||||
className="_block_17898c _session_634806"
|
||||
<section
|
||||
className="_root_e2909e"
|
||||
>
|
||||
<a
|
||||
className="_body_e2909e _disabled_e2909e"
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
>
|
||||
<header
|
||||
className="_header_e2909e"
|
||||
>
|
||||
<svg
|
||||
aria-label="Mobile"
|
||||
@@ -190,60 +224,52 @@ exports[`<OAuth2Session /> > renders correct icon for a native session 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
className="_container_634806"
|
||||
className="_content_e2909e"
|
||||
>
|
||||
<h6
|
||||
className="_typography_yh5dq_162 _font-body-md-semibold_yh5dq_64 _sessionName_634806"
|
||||
title="session-id"
|
||||
<div
|
||||
className="_name_e2909e"
|
||||
>
|
||||
<a
|
||||
href="/sessions/session-id"
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseEnter={[Function]}
|
||||
onMouseLeave={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
style={{}}
|
||||
Unknown device
|
||||
</div>
|
||||
<div
|
||||
className="_client_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</a>
|
||||
</h6>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
Element
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<ul
|
||||
className="_metadata_e2909e"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Signed in
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
<time
|
||||
dateTime="2023-06-29T03:35:17Z"
|
||||
>
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
data-finished={true}
|
||||
>
|
||||
Finished
|
||||
<time
|
||||
dateTime="2023-06-29T03:35:19Z"
|
||||
>
|
||||
Thu, 29 Jun 2023, 03:35
|
||||
</time>
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
1.2.3.4
|
||||
</p>
|
||||
<p
|
||||
className="_typography_yh5dq_162 _font-body-sm-regular_yh5dq_40 _sessionMetadata_634806"
|
||||
>
|
||||
|
||||
<span
|
||||
className="_typography_yh5dq_162 _font-body-sm-semibold_yh5dq_45 _sessionMetadata_634806"
|
||||
>
|
||||
Element
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
className="_key_e2909e"
|
||||
>
|
||||
Device ID
|
||||
</div>
|
||||
<div
|
||||
className="_value_e2909e"
|
||||
>
|
||||
abcd1234
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</a>
|
||||
</section>
|
||||
`;
|
||||
|
||||
@@ -19,11 +19,11 @@ const documents = {
|
||||
types.EndBrowserSessionDocument,
|
||||
"\n fragment OAuth2Client_detail on Oauth2Client {\n id\n clientId\n clientName\n clientUri\n logoUri\n tosUri\n policyUri\n redirectUris\n }\n":
|
||||
types.OAuth2Client_DetailFragmentDoc,
|
||||
"\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n raw\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n":
|
||||
"\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n":
|
||||
types.CompatSession_SessionFragmentDoc,
|
||||
"\n mutation EndCompatSession($id: ID!) {\n endCompatSession(input: { compatSessionId: $id }) {\n status\n compatSession {\n id\n finishedAt\n }\n }\n }\n":
|
||||
types.EndCompatSessionDocument,
|
||||
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n userAgent {\n model\n os\n osVersion\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n":
|
||||
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n":
|
||||
types.OAuth2Session_SessionFragmentDoc,
|
||||
"\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n":
|
||||
types.EndOAuth2SessionDocument,
|
||||
@@ -119,8 +119,8 @@ export function graphql(
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n raw\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n raw\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n"];
|
||||
source: "\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment CompatSession_session on CompatSession {\n id\n createdAt\n deviceId\n finishedAt\n lastActiveIp\n lastActiveAt\n userAgent {\n name\n os\n model\n deviceType\n }\n ssoLogin {\n id\n redirectUri\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -131,8 +131,8 @@ export function graphql(
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(
|
||||
source: "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n userAgent {\n model\n os\n osVersion\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n userAgent {\n model\n os\n osVersion\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n"];
|
||||
source: "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n",
|
||||
): (typeof documents)["\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n\n userAgent {\n name\n model\n os\n deviceType\n }\n\n client {\n id\n clientId\n clientName\n applicationType\n logoUri\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -1237,7 +1237,6 @@ export type CompatSession_SessionFragment = {
|
||||
lastActiveAt?: string | null;
|
||||
userAgent?: {
|
||||
__typename?: "UserAgent";
|
||||
raw: string;
|
||||
name?: string | null;
|
||||
os?: string | null;
|
||||
model?: string | null;
|
||||
@@ -1277,9 +1276,9 @@ export type OAuth2Session_SessionFragment = {
|
||||
lastActiveAt?: string | null;
|
||||
userAgent?: {
|
||||
__typename?: "UserAgent";
|
||||
name?: string | null;
|
||||
model?: string | null;
|
||||
os?: string | null;
|
||||
osVersion?: string | null;
|
||||
deviceType: DeviceType;
|
||||
} | null;
|
||||
client: {
|
||||
@@ -1897,7 +1896,6 @@ export const CompatSession_SessionFragmentDoc = {
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "raw" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "os" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "model" } },
|
||||
@@ -1946,9 +1944,9 @@ export const OAuth2Session_SessionFragmentDoc = {
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "model" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "os" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "osVersion" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceType" } },
|
||||
],
|
||||
},
|
||||
@@ -2573,9 +2571,9 @@ export const EndOAuth2SessionDocument = {
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "model" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "os" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "osVersion" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceType" } },
|
||||
],
|
||||
},
|
||||
@@ -4233,7 +4231,6 @@ export const AppSessionsListQueryDocument = {
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "raw" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "os" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "model" } },
|
||||
@@ -4277,9 +4274,9 @@ export const AppSessionsListQueryDocument = {
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "model" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "os" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "osVersion" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "deviceType" } },
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user