keycloakify 9.5.8 → 9.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/account/Fallback.js +12 -0
- package/account/Fallback.js.map +1 -1
- package/account/TemplateProps.d.ts +1 -1
- package/account/kcContext/KcContext.d.ts +144 -1
- package/account/kcContext/KcContext.js.map +1 -1
- package/account/kcContext/kcContextMocks.js +45 -1
- package/account/kcContext/kcContextMocks.js.map +1 -1
- package/account/lib/useGetClassName.js +8 -1
- package/account/lib/useGetClassName.js.map +1 -1
- package/account/pages/Applications.d.ts +7 -0
- package/account/pages/Applications.js +24 -0
- package/account/pages/Applications.js.map +1 -0
- package/account/pages/Log.d.ts +7 -0
- package/account/pages/Log.js +13 -0
- package/account/pages/Log.js.map +1 -0
- package/account/pages/Sessions.d.ts +7 -0
- package/account/pages/Sessions.js +18 -0
- package/account/pages/Sessions.js.map +1 -0
- package/account/pages/Totp.d.ts +7 -0
- package/account/pages/Totp.js +20 -0
- package/account/pages/Totp.js.map +1 -0
- package/bin/keycloakify/generateFtl/pageId.d.ts +1 -1
- package/bin/keycloakify/generateFtl/pageId.js +1 -1
- package/bin/keycloakify/generateFtl/pageId.js.map +1 -1
- package/package.json +21 -1
- package/src/account/Fallback.tsx +12 -0
- package/src/account/TemplateProps.ts +14 -1
- package/src/account/kcContext/KcContext.ts +148 -1
- package/src/account/kcContext/kcContextMocks.ts +68 -0
- package/src/account/lib/useGetClassName.ts +8 -1
- package/src/account/pages/Applications.tsx +138 -0
- package/src/account/pages/Log.tsx +70 -0
- package/src/account/pages/Sessions.tsx +68 -0
- package/src/account/pages/Totp.tsx +236 -0
- package/src/bin/keycloakify/generateFtl/pageId.ts +1 -1
@@ -11,4 +11,17 @@ export type TemplateProps<KcContext extends KcContext.Common, I18nExtended exten
|
|
11
11
|
children: ReactNode;
|
12
12
|
};
|
13
13
|
|
14
|
-
export type ClassKey =
|
14
|
+
export type ClassKey =
|
15
|
+
| "kcHtmlClass"
|
16
|
+
| "kcBodyClass"
|
17
|
+
| "kcButtonClass"
|
18
|
+
| "kcButtonPrimaryClass"
|
19
|
+
| "kcButtonLargeClass"
|
20
|
+
| "kcButtonDefaultClass"
|
21
|
+
| "kcContentWrapperClass"
|
22
|
+
| "kcFormClass"
|
23
|
+
| "kcFormGroupClass"
|
24
|
+
| "kcInputWrapperClass"
|
25
|
+
| "kcLabelClass"
|
26
|
+
| "kcInputClass"
|
27
|
+
| "kcInputErrorMessageClass";
|
@@ -3,7 +3,7 @@ import { assert } from "tsafe/assert";
|
|
3
3
|
import type { Equals } from "tsafe";
|
4
4
|
import { type ThemeType } from "keycloakify/bin/constants";
|
5
5
|
|
6
|
-
export type KcContext = KcContext.Password | KcContext.Account;
|
6
|
+
export type KcContext = KcContext.Password | KcContext.Account | KcContext.Sessions | KcContext.Totp | KcContext.Applications | KcContext.Log;
|
7
7
|
|
8
8
|
export declare namespace KcContext {
|
9
9
|
export type Common = {
|
@@ -91,6 +91,15 @@ export declare namespace KcContext {
|
|
91
91
|
username?: string;
|
92
92
|
};
|
93
93
|
properties: Record<string, string | undefined>;
|
94
|
+
sessions: {
|
95
|
+
sessions: {
|
96
|
+
ipAddress: string;
|
97
|
+
started?: any;
|
98
|
+
lastAccess?: any;
|
99
|
+
expires?: any;
|
100
|
+
clients: string[];
|
101
|
+
}[];
|
102
|
+
};
|
94
103
|
};
|
95
104
|
|
96
105
|
export type Password = Common & {
|
@@ -112,6 +121,144 @@ export declare namespace KcContext {
|
|
112
121
|
};
|
113
122
|
stateChecker: string;
|
114
123
|
};
|
124
|
+
|
125
|
+
export type Sessions = Common & {
|
126
|
+
pageId: "sessions.ftl";
|
127
|
+
sessions: {
|
128
|
+
sessions: {
|
129
|
+
ipAddress: string;
|
130
|
+
started?: any;
|
131
|
+
lastAccess?: any;
|
132
|
+
expires?: any;
|
133
|
+
clients: string[];
|
134
|
+
}[];
|
135
|
+
};
|
136
|
+
stateChecker: string;
|
137
|
+
};
|
138
|
+
|
139
|
+
export type Totp = Common & {
|
140
|
+
pageId: "totp.ftl";
|
141
|
+
totp: {
|
142
|
+
enabled: boolean;
|
143
|
+
totpSecretEncoded: string;
|
144
|
+
qrUrl: string;
|
145
|
+
policy: {
|
146
|
+
algorithm: "HmacSHA1" | "HmacSHA256" | "HmacSHA512";
|
147
|
+
digits: number;
|
148
|
+
lookAheadWindow: number;
|
149
|
+
} & (
|
150
|
+
| {
|
151
|
+
type: "totp";
|
152
|
+
period: number;
|
153
|
+
}
|
154
|
+
| {
|
155
|
+
type: "hotp";
|
156
|
+
initialCounter: number;
|
157
|
+
}
|
158
|
+
);
|
159
|
+
supportedApplications: string[];
|
160
|
+
totpSecretQrCode: string;
|
161
|
+
manualUrl: string;
|
162
|
+
totpSecret: string;
|
163
|
+
otpCredentials: { id: string; userLabel: string }[];
|
164
|
+
};
|
165
|
+
mode?: "qr" | "manual" | undefined | null;
|
166
|
+
isAppInitiatedAction: boolean;
|
167
|
+
url: {
|
168
|
+
accountUrl: string;
|
169
|
+
passwordUrl: string;
|
170
|
+
totpUrl: string;
|
171
|
+
socialUrl: string;
|
172
|
+
sessionsUrl: string;
|
173
|
+
applicationsUrl: string;
|
174
|
+
logUrl: string;
|
175
|
+
resourceUrl: string;
|
176
|
+
resourcesCommonPath: string;
|
177
|
+
resourcesPath: string;
|
178
|
+
/** @deprecated, not present in recent keycloak version apparently, use kcContext.referrer instead */
|
179
|
+
referrerURI?: string;
|
180
|
+
getLogoutUrl: () => string;
|
181
|
+
};
|
182
|
+
stateChecker: string;
|
183
|
+
};
|
184
|
+
|
185
|
+
export type Applications = Common & {
|
186
|
+
pageId: "applications.ftl";
|
187
|
+
features: {
|
188
|
+
log: boolean;
|
189
|
+
identityFederation: boolean;
|
190
|
+
authorization: boolean;
|
191
|
+
passwordUpdateSupported: boolean;
|
192
|
+
};
|
193
|
+
stateChecker: string;
|
194
|
+
applications: {
|
195
|
+
applications: {
|
196
|
+
realmRolesAvailable: { name: string; description: string }[];
|
197
|
+
resourceRolesAvailable: Record<
|
198
|
+
string,
|
199
|
+
{
|
200
|
+
roleName: string;
|
201
|
+
roleDescription: string;
|
202
|
+
clientName: string;
|
203
|
+
clientId: string;
|
204
|
+
}[]
|
205
|
+
>;
|
206
|
+
additionalGrants: string[];
|
207
|
+
clientScopesGranted: string[];
|
208
|
+
effectiveUrl?: string;
|
209
|
+
client: {
|
210
|
+
consentScreenText: string;
|
211
|
+
surrogateAuthRequired: boolean;
|
212
|
+
bearerOnly: boolean;
|
213
|
+
id: string;
|
214
|
+
protocolMappersStream: Record<string, unknown>;
|
215
|
+
includeInTokenScope: boolean;
|
216
|
+
redirectUris: string[];
|
217
|
+
fullScopeAllowed: boolean;
|
218
|
+
registeredNodes: Record<string, unknown>;
|
219
|
+
enabled: boolean;
|
220
|
+
clientAuthenticatorType: string;
|
221
|
+
realmScopeMappingsStream: Record<string, unknown>;
|
222
|
+
scopeMappingsStream: Record<string, unknown>;
|
223
|
+
displayOnConsentScreen: boolean;
|
224
|
+
clientId: string;
|
225
|
+
rootUrl: string;
|
226
|
+
authenticationFlowBindingOverrides: Record<string, unknown>;
|
227
|
+
standardFlowEnabled: boolean;
|
228
|
+
attributes: Record<string, unknown>;
|
229
|
+
publicClient: boolean;
|
230
|
+
alwaysDisplayInConsole: boolean;
|
231
|
+
consentRequired: boolean;
|
232
|
+
notBefore: string;
|
233
|
+
rolesStream: Record<string, unknown>;
|
234
|
+
protocol: string;
|
235
|
+
dynamicScope: boolean;
|
236
|
+
directAccessGrantsEnabled: boolean;
|
237
|
+
name: string;
|
238
|
+
serviceAccountsEnabled: boolean;
|
239
|
+
frontchannelLogout: boolean;
|
240
|
+
nodeReRegistrationTimeout: string;
|
241
|
+
implicitFlowEnabled: boolean;
|
242
|
+
baseUrl: string;
|
243
|
+
webOrigins: string[];
|
244
|
+
realm: Record<string, unknown>;
|
245
|
+
};
|
246
|
+
}[];
|
247
|
+
};
|
248
|
+
};
|
249
|
+
|
250
|
+
export type Log = Common & {
|
251
|
+
pageId: "log.ftl";
|
252
|
+
log: {
|
253
|
+
events: {
|
254
|
+
date: string | number | Date;
|
255
|
+
event: string;
|
256
|
+
ipAddress: string;
|
257
|
+
client: any;
|
258
|
+
details: any[];
|
259
|
+
}[];
|
260
|
+
};
|
261
|
+
};
|
115
262
|
}
|
116
263
|
|
117
264
|
{
|
@@ -156,6 +156,17 @@ export const kcContextCommonMock: KcContext.Common = {
|
|
156
156
|
"css/account.css img/icon-sidebar-active.png img/logo.png resources-common/node_modules/patternfly/dist/css/patternfly.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css",
|
157
157
|
"kcButtonClass": "btn",
|
158
158
|
"kcButtonDefaultClass": "btn-default"
|
159
|
+
},
|
160
|
+
"sessions": {
|
161
|
+
"sessions": [
|
162
|
+
{
|
163
|
+
"ipAddress": "127.0.0.1",
|
164
|
+
"started": new Date().toString(),
|
165
|
+
"lastAccess": new Date().toString(),
|
166
|
+
"expires": new Date().toString(),
|
167
|
+
"clients": ["Chrome", "Firefox"]
|
168
|
+
}
|
169
|
+
]
|
159
170
|
}
|
160
171
|
};
|
161
172
|
|
@@ -182,5 +193,62 @@ export const kcContextMocks: KcContext[] = [
|
|
182
193
|
"editUsernameAllowed": true
|
183
194
|
},
|
184
195
|
"stateChecker": ""
|
196
|
+
}),
|
197
|
+
id<KcContext.Sessions>({
|
198
|
+
...kcContextCommonMock,
|
199
|
+
"pageId": "sessions.ftl",
|
200
|
+
"sessions": {
|
201
|
+
"sessions": [
|
202
|
+
{
|
203
|
+
...kcContextCommonMock.sessions,
|
204
|
+
"ipAddress": "127.0.0.1",
|
205
|
+
"started": new Date().toString(),
|
206
|
+
"lastAccess": new Date().toString(),
|
207
|
+
"expires": new Date().toString(),
|
208
|
+
"clients": ["Chrome", "Firefox"]
|
209
|
+
}
|
210
|
+
]
|
211
|
+
},
|
212
|
+
"stateChecker": "g6WB1FaYnKotTkiy7ZrlxvFztSqS0U8jvHsOOOb2z4g"
|
213
|
+
}),
|
214
|
+
id<KcContext.Totp>({
|
215
|
+
...kcContextCommonMock,
|
216
|
+
"pageId": "totp.ftl",
|
217
|
+
"totp": {
|
218
|
+
"enabled": true,
|
219
|
+
"totpSecretEncoded": "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
|
220
|
+
"qrUrl": "#",
|
221
|
+
"totpSecretQrCode":
|
222
|
+
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=",
|
223
|
+
"manualUrl": "#",
|
224
|
+
"totpSecret": "G4nsI8lQagRMUchH8jEG",
|
225
|
+
"otpCredentials": [],
|
226
|
+
"supportedApplications": ["totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName"],
|
227
|
+
"policy": {
|
228
|
+
"algorithm": "HmacSHA1",
|
229
|
+
"digits": 6,
|
230
|
+
"lookAheadWindow": 1,
|
231
|
+
"type": "totp",
|
232
|
+
"period": 30
|
233
|
+
}
|
234
|
+
},
|
235
|
+
"mode": "qr",
|
236
|
+
"isAppInitiatedAction": false,
|
237
|
+
"stateChecker": ""
|
238
|
+
}),
|
239
|
+
id<KcContext.Log>({
|
240
|
+
...kcContextCommonMock,
|
241
|
+
"pageId": "log.ftl",
|
242
|
+
"log": {
|
243
|
+
"events": [
|
244
|
+
{
|
245
|
+
"date": "2/21/2024, 1:28:39 PM",
|
246
|
+
"event": "login",
|
247
|
+
"ipAddress": "172.17.0.1",
|
248
|
+
"client": "security-admin-console",
|
249
|
+
"details": ["auth_method = openid-connect, username = admin"]
|
250
|
+
}
|
251
|
+
]
|
252
|
+
}
|
185
253
|
})
|
186
254
|
];
|
@@ -6,8 +6,15 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
|
|
6
6
|
"kcHtmlClass": undefined,
|
7
7
|
"kcBodyClass": undefined,
|
8
8
|
"kcButtonClass": "btn",
|
9
|
+
"kcContentWrapperClass": "row",
|
9
10
|
"kcButtonPrimaryClass": "btn-primary",
|
10
11
|
"kcButtonLargeClass": "btn-lg",
|
11
|
-
"kcButtonDefaultClass": "btn-default"
|
12
|
+
"kcButtonDefaultClass": "btn-default",
|
13
|
+
"kcFormClass": "form-horizontal",
|
14
|
+
"kcFormGroupClass": "form-group",
|
15
|
+
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
16
|
+
"kcLabelClass": "control-label",
|
17
|
+
"kcInputClass": "form-control",
|
18
|
+
"kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text"
|
12
19
|
}
|
13
20
|
});
|
@@ -0,0 +1,138 @@
|
|
1
|
+
import { clsx } from "keycloakify/tools/clsx";
|
2
|
+
import type { PageProps } from "keycloakify/account/pages/PageProps";
|
3
|
+
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
|
4
|
+
import type { KcContext } from "../kcContext";
|
5
|
+
import type { I18n } from "../i18n";
|
6
|
+
|
7
|
+
function isArrayWithEmptyObject(variable: any): boolean {
|
8
|
+
return Array.isArray(variable) && variable.length === 1 && typeof variable[0] === "object" && Object.keys(variable[0]).length === 0;
|
9
|
+
}
|
10
|
+
|
11
|
+
export default function Applications(props: PageProps<Extract<KcContext, { pageId: "applications.ftl" }>, I18n>) {
|
12
|
+
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
|
13
|
+
|
14
|
+
const { getClassName } = useGetClassName({
|
15
|
+
doUseDefaultCss,
|
16
|
+
classes
|
17
|
+
});
|
18
|
+
|
19
|
+
const {
|
20
|
+
url,
|
21
|
+
applications: { applications },
|
22
|
+
stateChecker
|
23
|
+
} = kcContext;
|
24
|
+
|
25
|
+
const { msg, advancedMsg } = i18n;
|
26
|
+
|
27
|
+
return (
|
28
|
+
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="applications">
|
29
|
+
<div className="row">
|
30
|
+
<div className="col-md-10">
|
31
|
+
<h2>{msg("applicationsHtmlTitle")}</h2>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<form action={url.applicationsUrl} method="post">
|
35
|
+
<input type="hidden" id="stateChecker" name="stateChecker" value={stateChecker} />
|
36
|
+
<input type="hidden" id="referrer" name="referrer" value={stateChecker} />
|
37
|
+
|
38
|
+
<table className="table table-striped table-bordered">
|
39
|
+
<thead>
|
40
|
+
<tr>
|
41
|
+
<td>{msg("application")}</td>
|
42
|
+
<td>{msg("availableRoles")}</td>
|
43
|
+
<td>{msg("grantedPermissions")}</td>
|
44
|
+
<td>{msg("additionalGrants")}</td>
|
45
|
+
<td>{msg("action")}</td>
|
46
|
+
</tr>
|
47
|
+
</thead>
|
48
|
+
|
49
|
+
<tbody>
|
50
|
+
{applications.map(application => (
|
51
|
+
<tr key={application.client.clientId}>
|
52
|
+
<td>
|
53
|
+
{application.effectiveUrl && (
|
54
|
+
<a href={application.effectiveUrl}>
|
55
|
+
{(application.client.name && advancedMsg(application.client.name)) || application.client.clientId}
|
56
|
+
</a>
|
57
|
+
)}
|
58
|
+
{!application.effectiveUrl &&
|
59
|
+
((application.client.name && advancedMsg(application.client.name)) || application.client.clientId)}
|
60
|
+
</td>
|
61
|
+
|
62
|
+
<td>
|
63
|
+
{!isArrayWithEmptyObject(application.realmRolesAvailable) &&
|
64
|
+
application.realmRolesAvailable.map(role => (
|
65
|
+
<span key={role.name}>
|
66
|
+
{role.description ? advancedMsg(role.description) : advancedMsg(role.name)}
|
67
|
+
{role !== application.realmRolesAvailable[application.realmRolesAvailable.length - 1] && ", "}
|
68
|
+
</span>
|
69
|
+
))}
|
70
|
+
{!isArrayWithEmptyObject(application.realmRolesAvailable) && application.resourceRolesAvailable && ", "}
|
71
|
+
{application.resourceRolesAvailable &&
|
72
|
+
Object.keys(application.resourceRolesAvailable).map(resource => (
|
73
|
+
<span key={resource}>
|
74
|
+
{!isArrayWithEmptyObject(application.realmRolesAvailable) && ", "}
|
75
|
+
{application.resourceRolesAvailable[resource].map(clientRole => (
|
76
|
+
<span key={clientRole.roleName}>
|
77
|
+
{clientRole.roleDescription
|
78
|
+
? advancedMsg(clientRole.roleDescription)
|
79
|
+
: advancedMsg(clientRole.roleName)}{" "}
|
80
|
+
{msg("inResource")}{" "}
|
81
|
+
<strong>
|
82
|
+
{clientRole.clientName ? advancedMsg(clientRole.clientName) : clientRole.clientId}
|
83
|
+
</strong>
|
84
|
+
{clientRole !==
|
85
|
+
application.resourceRolesAvailable[resource][
|
86
|
+
application.resourceRolesAvailable[resource].length - 1
|
87
|
+
] && ", "}
|
88
|
+
</span>
|
89
|
+
))}
|
90
|
+
</span>
|
91
|
+
))}
|
92
|
+
</td>
|
93
|
+
|
94
|
+
<td>
|
95
|
+
{application.client.consentRequired ? (
|
96
|
+
application.clientScopesGranted.map(claim => (
|
97
|
+
<span key={claim}>
|
98
|
+
{advancedMsg(claim)}
|
99
|
+
{claim !== application.clientScopesGranted[application.clientScopesGranted.length - 1] && ", "}
|
100
|
+
</span>
|
101
|
+
))
|
102
|
+
) : (
|
103
|
+
<strong>{msg("fullAccess")}</strong>
|
104
|
+
)}
|
105
|
+
</td>
|
106
|
+
|
107
|
+
<td>
|
108
|
+
{application.additionalGrants.map(grant => (
|
109
|
+
<span key={grant}>
|
110
|
+
{advancedMsg(grant)}
|
111
|
+
{grant !== application.additionalGrants[application.additionalGrants.length - 1] && ", "}
|
112
|
+
</span>
|
113
|
+
))}
|
114
|
+
</td>
|
115
|
+
|
116
|
+
<td>
|
117
|
+
{(application.client.consentRequired && application.clientScopesGranted.length > 0) ||
|
118
|
+
application.additionalGrants.length > 0 ? (
|
119
|
+
<button
|
120
|
+
type="submit"
|
121
|
+
className={clsx(getClassName("kcButtonPrimaryClass"), getClassName("kcButtonClass"))}
|
122
|
+
id={`revoke-${application.client.clientId}`}
|
123
|
+
name="clientId"
|
124
|
+
value={application.client.id}
|
125
|
+
>
|
126
|
+
{msg("revoke")}
|
127
|
+
</button>
|
128
|
+
) : null}
|
129
|
+
</td>
|
130
|
+
</tr>
|
131
|
+
))}
|
132
|
+
</tbody>
|
133
|
+
</table>
|
134
|
+
</form>
|
135
|
+
</div>
|
136
|
+
</Template>
|
137
|
+
);
|
138
|
+
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import type { PageProps } from "keycloakify/account/pages/PageProps";
|
2
|
+
import type { KcContext } from "../kcContext";
|
3
|
+
import type { I18n } from "../i18n";
|
4
|
+
import { Key } from "react";
|
5
|
+
import { useGetClassName } from "../lib/useGetClassName";
|
6
|
+
|
7
|
+
export default function Log(props: PageProps<Extract<KcContext, { pageId: "log.ftl" }>, I18n>) {
|
8
|
+
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
|
9
|
+
|
10
|
+
const { getClassName } = useGetClassName({
|
11
|
+
doUseDefaultCss,
|
12
|
+
classes
|
13
|
+
});
|
14
|
+
|
15
|
+
const { log } = kcContext;
|
16
|
+
|
17
|
+
const { msg } = i18n;
|
18
|
+
|
19
|
+
return (
|
20
|
+
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="log">
|
21
|
+
<div className={getClassName("kcContentWrapperClass")}>
|
22
|
+
<div className="col-md-10">
|
23
|
+
<h2>{msg("accountLogHtmlTitle")}</h2>
|
24
|
+
</div>
|
25
|
+
|
26
|
+
<table className="table table-striped table-bordered">
|
27
|
+
<thead>
|
28
|
+
<tr>
|
29
|
+
<td>{msg("date")}</td>
|
30
|
+
<td>{msg("event")}</td>
|
31
|
+
<td>{msg("ip")}</td>
|
32
|
+
<td>{msg("client")}</td>
|
33
|
+
<td>{msg("details")}</td>
|
34
|
+
</tr>
|
35
|
+
</thead>
|
36
|
+
|
37
|
+
<tbody>
|
38
|
+
{log.events.map(
|
39
|
+
(
|
40
|
+
event: {
|
41
|
+
date: string | number | Date;
|
42
|
+
event: string;
|
43
|
+
ipAddress: string;
|
44
|
+
client: any;
|
45
|
+
details: any[];
|
46
|
+
},
|
47
|
+
index: Key | null | undefined
|
48
|
+
) => (
|
49
|
+
<tr key={index}>
|
50
|
+
<td>{event.date ? new Date(event.date).toLocaleString() : ""}</td>
|
51
|
+
<td>{event.event}</td>
|
52
|
+
<td>{event.ipAddress}</td>
|
53
|
+
<td>{event.client || ""}</td>
|
54
|
+
<td>
|
55
|
+
{event.details.map((detail, detailIndex) => (
|
56
|
+
<span key={detailIndex}>
|
57
|
+
{`${detail.key} = ${detail.value}`}
|
58
|
+
{detailIndex < event.details.length - 1 && ", "}
|
59
|
+
</span>
|
60
|
+
))}
|
61
|
+
</td>
|
62
|
+
</tr>
|
63
|
+
)
|
64
|
+
)}
|
65
|
+
</tbody>
|
66
|
+
</table>
|
67
|
+
</div>
|
68
|
+
</Template>
|
69
|
+
);
|
70
|
+
}
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import { clsx } from "keycloakify/tools/clsx";
|
2
|
+
import type { PageProps } from "keycloakify/account/pages/PageProps";
|
3
|
+
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
|
4
|
+
import type { KcContext } from "../kcContext";
|
5
|
+
import type { I18n } from "../i18n";
|
6
|
+
|
7
|
+
export default function Sessions(props: PageProps<Extract<KcContext, { pageId: "sessions.ftl" }>, I18n>) {
|
8
|
+
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
9
|
+
|
10
|
+
const { getClassName } = useGetClassName({
|
11
|
+
doUseDefaultCss,
|
12
|
+
classes
|
13
|
+
});
|
14
|
+
|
15
|
+
console.log({ kcContext });
|
16
|
+
const { url, stateChecker, sessions } = kcContext;
|
17
|
+
|
18
|
+
const { msg } = i18n;
|
19
|
+
console.log({ sdf: kcContext.locale?.supported });
|
20
|
+
console.log({ asdf: "asdf" });
|
21
|
+
return (
|
22
|
+
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="sessions">
|
23
|
+
<div className={getClassName("kcContentWrapperClass")}>
|
24
|
+
<div className="col-md-10">
|
25
|
+
<h2>{msg("sessionsHtmlTitle")}</h2>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<table className="table table-striped table-bordered">
|
30
|
+
<thead>
|
31
|
+
<tr>
|
32
|
+
<th>{msg("ip")}</th>
|
33
|
+
<th>{msg("started")}</th>
|
34
|
+
<th>{msg("lastAccess")}</th>
|
35
|
+
<th>{msg("expires")}</th>
|
36
|
+
<th>{msg("clients")}</th>
|
37
|
+
</tr>
|
38
|
+
</thead>
|
39
|
+
|
40
|
+
<tbody role="rowgroup">
|
41
|
+
{sessions.sessions.map((session, index: number) => (
|
42
|
+
<tr key={index}>
|
43
|
+
<td>{session.ipAddress}</td>
|
44
|
+
<td>{session?.started}</td>
|
45
|
+
<td>{session?.lastAccess}</td>
|
46
|
+
<td>{session?.expires}</td>
|
47
|
+
<td>
|
48
|
+
{session.clients.map((client: string, clientIndex: number) => (
|
49
|
+
<div key={clientIndex}>
|
50
|
+
{client}
|
51
|
+
<br />
|
52
|
+
</div>
|
53
|
+
))}
|
54
|
+
</td>
|
55
|
+
</tr>
|
56
|
+
))}
|
57
|
+
</tbody>
|
58
|
+
</table>
|
59
|
+
|
60
|
+
<form action={url.sessionsUrl} method="post">
|
61
|
+
<input type="hidden" id="stateChecker" name="stateChecker" value={stateChecker} />
|
62
|
+
<button id="logout-all-sessions" type="submit" className={clsx(getClassName("kcButtonDefaultClass"), getClassName("kcButtonClass"))}>
|
63
|
+
{msg("doLogOutAllSessions")}
|
64
|
+
</button>
|
65
|
+
</form>
|
66
|
+
</Template>
|
67
|
+
);
|
68
|
+
}
|