feat: Addition of Sessions page
This commit is contained in:
parent
ca255985c0
commit
22496e36eb
@ -6,6 +6,7 @@ import { assert, type Equals } from "tsafe/assert";
|
|||||||
|
|
||||||
const Password = lazy(() => import("keycloakify/account/pages/Password"));
|
const Password = lazy(() => import("keycloakify/account/pages/Password"));
|
||||||
const Account = lazy(() => import("keycloakify/account/pages/Account"));
|
const Account = lazy(() => import("keycloakify/account/pages/Account"));
|
||||||
|
const Sessions = lazy(() => import("keycloakify/account/pages/Sessions"));
|
||||||
|
|
||||||
export default function Fallback(props: PageProps<KcContext, I18n>) {
|
export default function Fallback(props: PageProps<KcContext, I18n>) {
|
||||||
const { kcContext, ...rest } = props;
|
const { kcContext, ...rest } = props;
|
||||||
@ -16,6 +17,8 @@ export default function Fallback(props: PageProps<KcContext, I18n>) {
|
|||||||
switch (kcContext.pageId) {
|
switch (kcContext.pageId) {
|
||||||
case "password.ftl":
|
case "password.ftl":
|
||||||
return <Password kcContext={kcContext} {...rest} />;
|
return <Password kcContext={kcContext} {...rest} />;
|
||||||
|
case "sessions.ftl":
|
||||||
|
return <Sessions kcContext={kcContext} {...rest} />;
|
||||||
case "account.ftl":
|
case "account.ftl":
|
||||||
return <Account kcContext={kcContext} {...rest} />;
|
return <Account kcContext={kcContext} {...rest} />;
|
||||||
}
|
}
|
||||||
|
@ -11,4 +11,11 @@ export type TemplateProps<KcContext extends KcContext.Common, I18nExtended exten
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ClassKey = "kcHtmlClass" | "kcBodyClass" | "kcButtonClass" | "kcButtonPrimaryClass" | "kcButtonLargeClass" | "kcButtonDefaultClass";
|
export type ClassKey =
|
||||||
|
| "kcHtmlClass"
|
||||||
|
| "kcBodyClass"
|
||||||
|
| "kcButtonClass"
|
||||||
|
| "kcButtonPrimaryClass"
|
||||||
|
| "kcButtonLargeClass"
|
||||||
|
| "kcButtonDefaultClass"
|
||||||
|
| "kcContentWrapperClass";
|
||||||
|
@ -3,7 +3,7 @@ import { assert } from "tsafe/assert";
|
|||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import { type ThemeType } from "keycloakify/bin/constants";
|
import { type ThemeType } from "keycloakify/bin/constants";
|
||||||
|
|
||||||
export type KcContext = KcContext.Password | KcContext.Account;
|
export type KcContext = KcContext.Password | KcContext.Account | KcContext.Sessions;
|
||||||
|
|
||||||
export declare namespace KcContext {
|
export declare namespace KcContext {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
@ -90,6 +90,15 @@ export declare namespace KcContext {
|
|||||||
lastName?: string;
|
lastName?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
};
|
};
|
||||||
|
sessions: {
|
||||||
|
sessions: {
|
||||||
|
ipAddress: string;
|
||||||
|
started?: any;
|
||||||
|
lastAccess?: any;
|
||||||
|
expires?: any;
|
||||||
|
clients: string[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Password = Common & {
|
export type Password = Common & {
|
||||||
@ -111,6 +120,20 @@ export declare namespace KcContext {
|
|||||||
};
|
};
|
||||||
stateChecker: string;
|
stateChecker: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Sessions = Common & {
|
||||||
|
pageId: "sessions.ftl";
|
||||||
|
sessions: {
|
||||||
|
sessions: {
|
||||||
|
ipAddress: string;
|
||||||
|
started?: any;
|
||||||
|
lastAccess?: any;
|
||||||
|
expires?: any;
|
||||||
|
clients: string[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
stateChecker: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -147,6 +147,17 @@ export const kcContextCommonMock: KcContext.Common = {
|
|||||||
"lastName": "doe",
|
"lastName": "doe",
|
||||||
"email": "john.doe@code.gouv.fr",
|
"email": "john.doe@code.gouv.fr",
|
||||||
"username": "doe_j"
|
"username": "doe_j"
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"sessions": [
|
||||||
|
{
|
||||||
|
"ipAddress": "127.0.0.1",
|
||||||
|
"started": new Date().toString(),
|
||||||
|
"lastAccess": new Date().toString(),
|
||||||
|
"expires": new Date().toString(),
|
||||||
|
"clients": ["Chrome", "Firefox"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -173,5 +184,22 @@ export const kcContextMocks: KcContext[] = [
|
|||||||
"editUsernameAllowed": true
|
"editUsernameAllowed": true
|
||||||
},
|
},
|
||||||
"stateChecker": ""
|
"stateChecker": ""
|
||||||
|
}),
|
||||||
|
id<KcContext.Sessions>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "sessions.ftl",
|
||||||
|
sessions: {
|
||||||
|
sessions: [
|
||||||
|
{
|
||||||
|
...kcContextCommonMock.sessions,
|
||||||
|
ipAddress: "127.0.0.1",
|
||||||
|
started: new Date().toString(),
|
||||||
|
lastAccess: new Date().toString(),
|
||||||
|
expires: new Date().toString(),
|
||||||
|
clients: ["Chrome", "Firefox"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stateChecker": ""
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
@ -6,6 +6,7 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
|
|||||||
"kcHtmlClass": undefined,
|
"kcHtmlClass": undefined,
|
||||||
"kcBodyClass": undefined,
|
"kcBodyClass": undefined,
|
||||||
"kcButtonClass": "btn",
|
"kcButtonClass": "btn",
|
||||||
|
"kcContentWrapperClass": "row",
|
||||||
"kcButtonPrimaryClass": "btn-primary",
|
"kcButtonPrimaryClass": "btn-primary",
|
||||||
"kcButtonLargeClass": "btn-lg",
|
"kcButtonLargeClass": "btn-lg",
|
||||||
"kcButtonDefaultClass": "btn-default"
|
"kcButtonDefaultClass": "btn-default"
|
||||||
|
68
src/account/pages/Sessions.tsx
Normal file
68
src/account/pages/Sessions.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
|
import type { PageProps } from "keycloakify/account/pages/PageProps";
|
||||||
|
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
|
||||||
|
import type { KcContext } from "../kcContext";
|
||||||
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
|
export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "sessions.ftl" }>, I18n>) {
|
||||||
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
|
|
||||||
|
const { getClassName } = useGetClassName({
|
||||||
|
doUseDefaultCss,
|
||||||
|
classes
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log({ kcContext });
|
||||||
|
const { url, stateChecker, sessions } = kcContext;
|
||||||
|
|
||||||
|
const { msg } = i18n;
|
||||||
|
console.log({ sdf: kcContext.locale?.supported });
|
||||||
|
console.log({ asdf: "asdf" });
|
||||||
|
return (
|
||||||
|
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="sessions">
|
||||||
|
<div className={getClassName("kcContentWrapperClass")}>
|
||||||
|
<div className="col-md-10">
|
||||||
|
<h2>{msg("sessionsHtmlTitle")}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table className="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{msg("ip")}</th>
|
||||||
|
<th>{msg("started")}</th>
|
||||||
|
<th>{msg("lastAccess")}</th>
|
||||||
|
<th>{msg("expires")}</th>
|
||||||
|
<th>{msg("clients")}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody role="rowgroup">
|
||||||
|
{sessions.sessions.map((session, index: number) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{session.ipAddress}</td>
|
||||||
|
<td>{session?.started}</td>
|
||||||
|
<td>{session?.lastAccess}</td>
|
||||||
|
<td>{session?.expires}</td>
|
||||||
|
<td>
|
||||||
|
{session.clients.map((client: string, clientIndex: number) => (
|
||||||
|
<div key={clientIndex}>
|
||||||
|
{client}
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<form action={url.sessionsUrl} method="post">
|
||||||
|
<input type="hidden" id="stateChecker" name="stateChecker" value={stateChecker} />
|
||||||
|
<button id="logout-all-sessions" type="submit" className={clsx(getClassName("kcButtonDefaultClass"), getClassName("kcButtonClass"))}>
|
||||||
|
{msg("doLogOutAllSessions")}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</Template>
|
||||||
|
);
|
||||||
|
}
|
@ -27,7 +27,7 @@ export const loginThemePageIds = [
|
|||||||
"saml-post-form.ftl"
|
"saml-post-form.ftl"
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const accountThemePageIds = ["password.ftl", "account.ftl"] as const;
|
export const accountThemePageIds = ["password.ftl", "account.ftl", "sessions.ftl"] as const;
|
||||||
|
|
||||||
export type LoginThemePageId = (typeof loginThemePageIds)[number];
|
export type LoginThemePageId = (typeof loginThemePageIds)[number];
|
||||||
export type AccountThemePageId = (typeof accountThemePageIds)[number];
|
export type AccountThemePageId = (typeof accountThemePageIds)[number];
|
||||||
|
32
stories/account/pages/Sessions.stories.tsx
Normal file
32
stories/account/pages/Sessions.stories.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from "react";
|
||||||
|
import type { ComponentMeta } from "@storybook/react";
|
||||||
|
import { createPageStory } from "../createPageStory";
|
||||||
|
|
||||||
|
const pageId = "sessions.ftl";
|
||||||
|
|
||||||
|
const { PageStory } = createPageStory({ pageId });
|
||||||
|
|
||||||
|
const meta: ComponentMeta<any> = {
|
||||||
|
title: `account/${pageId}`,
|
||||||
|
component: PageStory,
|
||||||
|
parameters: {
|
||||||
|
viewMode: "story",
|
||||||
|
previewTabs: {
|
||||||
|
"storybook/docs/panel": {
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
export const Default = () => <PageStory />;
|
||||||
|
|
||||||
|
export const WithMessage = () => (
|
||||||
|
<PageStory
|
||||||
|
kcContext={{
|
||||||
|
message: { type: "success", summary: "This is a test message" }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
Loading…
x
Reference in New Issue
Block a user