mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-08-28 00:54:19 +00:00
Add header showing total number of devices
This commit is contained in:
@@ -2898,7 +2898,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"soft_limit": {
|
||||
"description": "Upon login in interactive contexts (like OAuth 2.0 sessions), if the\n soft limit is reached, it will display a policy violation screen\n (web UI) to remove sessions before creating the new session.\n\n This is not enforced in non-interactive contexts (like the legacy\n compability login API) as there is no opportunity for us to show\n some UI for people remove some sessions. See [`hard_limit`] for\n enforcement on that side.\n\n [`hard_limit`]: Self::hard_limit",
|
||||
"description": "Upon login in interactive contexts (like OAuth 2.0 sessions), if the\n soft limit is reached, it will display a policy violation screen\n (web UI) to remove sessions before creating the new session.\n\n This is not enforced in non-interactive contexts (like the legacy\n compability login API) as there is no opportunity for us to show\n some UI for people remove some sessions. See [`hard_limit`] for\n enforcement on that side.\n\n This is the limit that is displayed in the UI\n\n [`hard_limit`]: Self::hard_limit",
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 1
|
||||
|
||||
@@ -293,7 +293,9 @@
|
||||
"no_active_sessions": {
|
||||
"default": "You are not signed in to any application.",
|
||||
"inactive_90_days": "All your sessions have been active in the last 90 days."
|
||||
}
|
||||
},
|
||||
"num_devices_filtered_header": "{{filtered_count}} devices match your filter ({{total_count}} total)",
|
||||
"num_devices_header": "{{num_devices}} devices"
|
||||
},
|
||||
"verify_email": {
|
||||
"code_expired_alert": {
|
||||
|
||||
@@ -52,7 +52,7 @@ type Documents = {
|
||||
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n ...AddEmailForm_user\n ...UserEmailList_user\n ...AccountDeleteButton_user\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n accountDeactivationAllowed\n ...AddEmailForm_siteConfig\n ...UserEmailList_siteConfig\n ...PasswordChange_siteConfig\n ...AccountDeleteButton_siteConfig\n }\n }\n": typeof types.UserProfileDocument,
|
||||
"\n query PlanManagementTab {\n siteConfig {\n planManagementIframeUri\n }\n }\n": typeof types.PlanManagementTabDocument,
|
||||
"\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": typeof types.BrowserSessionListDocument,
|
||||
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": typeof types.SessionsOverviewDocument,
|
||||
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n\n # Get the total count of active app sessions before any filtering\n unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {\n totalCount\n }\n }\n }\n }\n": typeof types.SessionsOverviewDocument,
|
||||
"\n query AppSessionsList(\n $before: String\n $after: String\n $first: Int\n $last: Int\n $lastActive: DateFilter\n ) {\n viewer {\n __typename\n\n ... on User {\n id\n appSessions(\n before: $before\n after: $after\n first: $first\n last: $last\n lastActive: $lastActive\n state: ACTIVE\n ) {\n edges {\n cursor\n node {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n startCursor\n endCursor\n hasNextPage\n hasPreviousPage\n }\n }\n }\n }\n }\n": typeof types.AppSessionsListDocument,
|
||||
"\n query CurrentUserGreeting {\n viewer {\n __typename\n ... on User {\n ...UserGreeting_user\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n planManagementIframeUri\n }\n }\n": typeof types.CurrentUserGreetingDocument,
|
||||
"\n query OAuth2Client($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n": typeof types.OAuth2ClientDocument,
|
||||
@@ -109,7 +109,7 @@ const documents: Documents = {
|
||||
"\n query UserProfile {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n user {\n ...AddEmailForm_user\n ...UserEmailList_user\n ...AccountDeleteButton_user\n hasPassword\n emails(first: 0) {\n totalCount\n }\n }\n }\n }\n\n siteConfig {\n emailChangeAllowed\n passwordLoginEnabled\n accountDeactivationAllowed\n ...AddEmailForm_siteConfig\n ...UserEmailList_siteConfig\n ...PasswordChange_siteConfig\n ...AccountDeleteButton_siteConfig\n }\n }\n": types.UserProfileDocument,
|
||||
"\n query PlanManagementTab {\n siteConfig {\n planManagementIframeUri\n }\n }\n": types.PlanManagementTabDocument,
|
||||
"\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n $lastActive: DateFilter\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n lastActive: $lastActive\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": types.BrowserSessionListDocument,
|
||||
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": types.SessionsOverviewDocument,
|
||||
"\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n\n # Get the total count of active app sessions before any filtering\n unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {\n totalCount\n }\n }\n }\n }\n": types.SessionsOverviewDocument,
|
||||
"\n query AppSessionsList(\n $before: String\n $after: String\n $first: Int\n $last: Int\n $lastActive: DateFilter\n ) {\n viewer {\n __typename\n\n ... on User {\n id\n appSessions(\n before: $before\n after: $after\n first: $first\n last: $last\n lastActive: $lastActive\n state: ACTIVE\n ) {\n edges {\n cursor\n node {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n startCursor\n endCursor\n hasNextPage\n hasPreviousPage\n }\n }\n }\n }\n }\n": types.AppSessionsListDocument,
|
||||
"\n query CurrentUserGreeting {\n viewer {\n __typename\n ... on User {\n ...UserGreeting_user\n }\n }\n\n siteConfig {\n ...UserGreeting_siteConfig\n planManagementIframeUri\n }\n }\n": types.CurrentUserGreetingDocument,
|
||||
"\n query OAuth2Client($id: ID!) {\n oauth2Client(id: $id) {\n ...OAuth2Client_detail\n }\n }\n": types.OAuth2ClientDocument,
|
||||
@@ -280,7 +280,7 @@ export function graphql(source: "\n query BrowserSessionList(\n $first: Int\
|
||||
/**
|
||||
* 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 SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n"): typeof import('./graphql').SessionsOverviewDocument;
|
||||
export function graphql(source: "\n query SessionsOverview {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n\n # Get the total count of active app sessions before any filtering\n unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {\n totalCount\n }\n }\n }\n }\n"): typeof import('./graphql').SessionsOverviewDocument;
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -1896,7 +1896,7 @@ export type SessionsOverviewQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
export type SessionsOverviewQuery = { __typename?: 'Query', viewer:
|
||||
| { __typename: 'Anonymous' }
|
||||
| (
|
||||
{ __typename: 'User', id: string }
|
||||
{ __typename: 'User', id: string, unfilteredAppSessions: { __typename?: 'AppSessionConnection', totalCount: number } }
|
||||
& { ' $fragmentRefs'?: { 'BrowserSessionsOverview_UserFragment': BrowserSessionsOverview_UserFragment } }
|
||||
)
|
||||
};
|
||||
@@ -2705,6 +2705,9 @@ export const SessionsOverviewDocument = new TypedDocumentString(`
|
||||
... on User {
|
||||
id
|
||||
...BrowserSessionsOverview_user
|
||||
unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
|
||||
.nav-bar-error-icon {
|
||||
display: inline-block;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { queryOptions, useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { notFound } from "@tanstack/react-router";
|
||||
import { H3 } from "@vector-im/compound-web";
|
||||
import { H3, H4 } from "@vector-im/compound-web";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as v from "valibot";
|
||||
import { ButtonLink } from "../components/ButtonLink";
|
||||
@@ -36,6 +36,11 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
... on User {
|
||||
id
|
||||
...BrowserSessionsOverview_user
|
||||
|
||||
# Get the total count of active app sessions before any filtering
|
||||
unfilteredAppSessions: appSessions(first: 1, state: ACTIVE) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,9 +141,9 @@ function Sessions(): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const { inactive, pagination } = Route.useLoaderDeps();
|
||||
const {
|
||||
data: { viewer },
|
||||
data: { viewer: overviewViewer },
|
||||
} = useSuspenseQuery(query);
|
||||
if (viewer.__typename !== "User") throw notFound();
|
||||
if (overviewViewer.__typename !== "User") throw notFound();
|
||||
|
||||
const { data } = useSuspenseQuery(listQuery(pagination, inactive));
|
||||
if (data.viewer.__typename !== "User") throw notFound();
|
||||
@@ -153,10 +158,25 @@ function Sessions(): React.ReactElement {
|
||||
// We reverse the list as we are paginating backwards
|
||||
const edges = [...appSessions.edges].reverse();
|
||||
|
||||
// By default, we just show a "X devices" header
|
||||
let deviceHeaderText = t("frontend.user_sessions_overview.num_devices_header", {
|
||||
num_devices: appSessions.totalCount,
|
||||
});
|
||||
// But if we're showing a filtered down view, we want to explain how many devices you
|
||||
// filtered down to and how many total unfilterd devices there are total.
|
||||
if (overviewViewer.unfilteredAppSessions.totalCount != appSessions.totalCount) {
|
||||
deviceHeaderText = t("frontend.user_sessions_overview.num_devices_filtered_header", {
|
||||
filtered_count: appSessions.totalCount,
|
||||
total_count: overviewViewer.unfilteredAppSessions.totalCount,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<H3>{t("frontend.user_sessions_overview.heading")}</H3>
|
||||
<BrowserSessionsOverview user={viewer} />
|
||||
<BrowserSessionsOverview user={overviewViewer} />
|
||||
|
||||
<H4>{deviceHeaderText}</H4>
|
||||
<Separator kind="section" />
|
||||
<div className="flex gap-2 justify-start items-center">
|
||||
<Filter
|
||||
|
||||
@@ -68,7 +68,10 @@ function Account(): React.ReactElement {
|
||||
|
||||
<NavBar>
|
||||
<NavItem to="/">{t("frontend.nav.settings")}</NavItem>
|
||||
<NavItem to="/sessions">{t("frontend.nav.devices")} <IconErrorSolid className={styles.navBarErrorIcon} /></NavItem>
|
||||
<NavItem to="/sessions">
|
||||
{t("frontend.nav.devices")}{" "}
|
||||
<IconErrorSolid className={styles.navBarErrorIcon} />
|
||||
</NavItem>
|
||||
{planManagementIframeUri && (
|
||||
<NavItem to="/plan">{t("frontend.nav.plan")}</NavItem>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user