Skip to content

Commit 566e41c

Browse files
edewityanxch
andauthored
color theme tab (keycloak#35179)
* added a way to customize theme colors fixes: keycloak#33233 Signed-off-by: Erik Jan de Wit <[email protected]> * added preview and grouped vars Signed-off-by: Erik Jan de Wit <[email protected]> * added dark mode Signed-off-by: Erik Jan de Wit <[email protected]> * fixed label Signed-off-by: Erik Jan de Wit <[email protected]> * added empty check Signed-off-by: Erik Jan de Wit <[email protected]> * use json string in attributes Signed-off-by: Erik Jan de Wit <[email protected]> * removed use of not exported type Signed-off-by: Erik Jan de Wit <[email protected]> * output css based on JSON string Signed-off-by: Erik Jan de Wit <[email protected]> * added feature flag Signed-off-by: Erik Jan de Wit <[email protected]> * added a way to customize theme colors fixes: keycloak#33233 Signed-off-by: Erik Jan de Wit <[email protected]> * renamed feature to quick theme Signed-off-by: Erik Jan de Wit <[email protected]> * fixed merge error Signed-off-by: Erik Jan de Wit <[email protected]> * Restore the Cache tab in Realm Settings (keycloak#34311) closes keycloak#17727 Signed-off-by: Christian Janker <[email protected]> Signed-off-by: Erik Jan de Wit <[email protected]> * added a way to customize theme colors fixes: keycloak#33233 Signed-off-by: Erik Jan de Wit <[email protected]> * create a zip file instead Signed-off-by: Erik Jan de Wit <[email protected]> * added themes.json to make jar usable Signed-off-by: Erik Jan de Wit <[email protected]> * use property instead of attribute Signed-off-by: Erik Jan de Wit <[email protected]> * fix the jar file Signed-off-by: Erik Jan de Wit <[email protected]> * fixed header for preview and some text Signed-off-by: Erik Jan de Wit <[email protected]> --------- Signed-off-by: Erik Jan de Wit <[email protected]> Signed-off-by: Christian Janker <[email protected]> Co-authored-by: Christian Ja <[email protected]>
1 parent a1f5234 commit 566e41c

File tree

20 files changed

+879
-37
lines changed

20 files changed

+879
-37
lines changed

common/src/main/java/org/keycloak/common/Profile.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public enum Feature {
6565

6666
LOGIN_V1("Legacy Login Theme", Type.DEPRECATED, 1),
6767

68+
QUICK_THEME("WYSIWYG theme configuration tool", Type.EXPERIMENTAL, 1),
69+
6870
DOCKER("Docker Registry protocol", Type.DISABLED_BY_DEFAULT),
6971

7072
IMPERSONATION("Ability for admins to impersonate users", Type.DEFAULT),

js/apps/account-ui/src/root/Header.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ export const Header = () => {
5151
features={{ hasManageAccount: false }}
5252
brand={{
5353
href: indexHref,
54-
src: joinPath(environment.resourceUrl, brandImage),
54+
src: brandImage.startsWith("/")
55+
? joinPath(environment.resourceUrl, brandImage)
56+
: brandImage,
5557
alt: t("logo"),
5658
className: style.brand,
5759
}}

js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,30 @@ keystorePasswordHelp=Password for the keys
13441344
clientSettings=Client details
13451345
deleteClientPolicyConditionConfirm=This action will permanently delete {{condition}}. This cannot be undone.
13461346
selectATheme=Select a theme
1347+
themeColors=Theme colors
1348+
defaults=Reset to defaults
1349+
themeColorsLight=Theme colors [light]
1350+
themeColorsDark=Theme colors [dark]
1351+
themePreviewInfo=In order to preview the theme colors, the current themen needs to be set to the one you want to preview, so we have automatically switched you to the one you want to preview.
1352+
backgroundImage=Login background image
1353+
errorColor=Error color
1354+
successColor=Success color
1355+
activeColor=Active color
1356+
primaryColor=Primary color
1357+
primaryColorHover=Primary color hover
1358+
secondaryColor=Secondary color
1359+
linkColor=Link color
1360+
linkColorHover=Link color hover
1361+
backgroundColorAccent=Background color accent
1362+
backgroundColor=Background color
1363+
backgroundColorNav=Background color navigation
1364+
backgroundColorHeader=Background color header
1365+
iconColor=Icon color
1366+
textColor=Text color
1367+
lightTextColor=Light text color
1368+
inputBackgroundColor=Input background color
1369+
inputTextColor=Input text color
1370+
defaults=Reset to defaults
13471371
permissionsList=Permission list
13481372
attributeGroupHelp=Specifies the user profile group where this attribute will be added. This allows grouping various similar attributes together on different parts of the screen when creating or updating user.
13491373
createRealm=Create realm
@@ -3306,4 +3330,6 @@ organizationsMembersListError=Could not fetch organization members\: {{error}}
33063330
MANAGED=Managed
33073331
UNMANAGED=Unmanaged
33083332
deleteConfirmUsers_one=Delete user {{name}}?
3309-
deleteConfirmUsers_other=Delete {{count}} users?
3333+
deleteConfirmUsers_other=Delete {{count}} users?
3334+
downloadThemeJar=Download theme JAR
3335+
themeColorInfo=Here you can set the patternfly color variables and create a "theme jar" file that you can download and put in your providers folder to apply the theme to your realm.

js/apps/admin-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
"flat": "^6.0.1",
103103
"i18next": "^24.0.2",
104104
"i18next-http-backend": "^3.0.1",
105+
"jszip": "^3.10.1",
105106
"keycloak-js": "workspace:*",
106107
"lodash-es": "^4.17.21",
107108
"monaco-editor": "^0.52.0",
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
.pf-v5-c-login__container {
2+
grid-template-columns: 34rem;
3+
grid-template-areas: "header"
4+
"main"
5+
}
6+
7+
.login-pf body {
8+
background: var(--keycloak-bg-logo-url) no-repeat center center fixed;
9+
background-size: cover;
10+
height: 100%;
11+
}
12+
13+
div.kc-logo-text {
14+
background-image: var(--keycloak-logo-url);
15+
height: var(--keycloak-logo-height);
16+
width: var(--keycloak-logo-width);
17+
background-repeat: no-repeat;
18+
background-size: contain;
19+
margin: 0 auto;
20+
}
21+
22+
div.kc-logo-text span {
23+
display: none;
24+
}
25+
26+
.kc-login-tooltip {
27+
position: relative;
28+
display: inline-block;
29+
}
30+
31+
.kc-login-tooltip .kc-tooltip-text {
32+
top: -3px;
33+
left: 160%;
34+
background-color: black;
35+
visibility: hidden;
36+
color: #fff;
37+
38+
min-width: 130px;
39+
text-align: center;
40+
border-radius: 2px;
41+
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
42+
padding: 5px;
43+
44+
position: absolute;
45+
opacity: 0;
46+
transition: opacity 0.5s;
47+
}
48+
49+
/* Show tooltip */
50+
.kc-login-tooltip:hover .kc-tooltip-text {
51+
visibility: visible;
52+
opacity: 0.7;
53+
}
54+
55+
/* Arrow for tooltip */
56+
.kc-login-tooltip .kc-tooltip-text::after {
57+
content: " ";
58+
position: absolute;
59+
top: 15px;
60+
right: 100%;
61+
margin-top: -5px;
62+
border-width: 5px;
63+
border-style: solid;
64+
border-color: transparent black transparent transparent;
65+
}
66+
67+
#kc-recovery-codes-list {
68+
columns: 2;
69+
}
70+
71+
#certificate_subjectDN {
72+
overflow-wrap: break-word
73+
}
74+
75+
#kc-header-wrapper {
76+
font-size: 29px;
77+
text-transform: uppercase;
78+
letter-spacing: 3px;
79+
line-height: 1.2em;
80+
white-space: normal;
81+
color: var(--pf-v5-global--Color--light-100) !important;
82+
text-align: center;
83+
}
84+
85+
hr {
86+
margin-top: var(--pf-v5-global--spacer--sm);
87+
margin-bottom: var(--pf-v5-global--spacer--md);
88+
}
89+
90+
@media (min-width: 768px) {
91+
div.pf-v5-c-login__main-header {
92+
grid-template-columns: 70% 30%;
93+
}
94+
}

js/apps/admin-ui/src/PageHeader.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEnvironment, useHelp } from "@keycloak/keycloak-ui-shared";
12
import {
23
Avatar,
34
Divider,
@@ -18,14 +19,15 @@ import { BarsIcon, EllipsisVIcon, HelpIcon } from "@patternfly/react-icons";
1819
import { useState } from "react";
1920
import { useTranslation } from "react-i18next";
2021
import { Link, useHref } from "react-router-dom";
21-
import { useEnvironment, useHelp } from "@keycloak/keycloak-ui-shared";
22+
import { PageHeaderClearCachesModal } from "./PageHeaderClearCachesModal";
2223
import { HelpHeader } from "./components/help-enabler/HelpHeader";
24+
import { useAccess } from "./context/access/Access";
2325
import { useRealm } from "./context/realm-context/RealmContext";
2426
import { useWhoAmI } from "./context/whoami/WhoAmI";
2527
import { toDashboard } from "./dashboard/routes/Dashboard";
28+
import { usePreviewLogo } from "./realm-settings/themes/LogoContext";
29+
import { joinPath } from "./utils/joinPath";
2630
import useToggle from "./utils/useToggle";
27-
import { PageHeaderClearCachesModal } from "./PageHeaderClearCachesModal";
28-
import { useAccess } from "./context/access/Access";
2931

3032
const ManageAccountDropdownItem = () => {
3133
const { keycloak } = useEnvironment();
@@ -184,9 +186,11 @@ export const Header = () => {
184186
const { environment, keycloak } = useEnvironment();
185187
const { t } = useTranslation();
186188
const { realm } = useRealm();
189+
const contextLogo = usePreviewLogo();
190+
const customLogo = contextLogo?.logo;
187191

188192
const picture = keycloak.tokenParsed?.picture;
189-
const logo = environment.logo ? environment.logo : "/logo.svg";
193+
const logo = customLogo || environment.logo || "/logo.svg";
190194
const url = useHref(toDashboard({ realm }));
191195
const logoUrl = environment.logoUrl ? environment.logoUrl : url;
192196

@@ -199,7 +203,11 @@ export const Header = () => {
199203
</MastheadToggle>
200204
<MastheadBrand href={logoUrl}>
201205
<img
202-
src={environment.resourceUrl + logo}
206+
src={
207+
logo.startsWith("/")
208+
? joinPath(environment.resourceUrl, logo)
209+
: logo
210+
}
203211
id="masthead-logo"
204212
alt={t("logo")}
205213
aria-label={t("logo")}

js/apps/admin-ui/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,9 @@ export { PoliciesTab } from "./realm-settings/PoliciesTab";
253253
export * as RealmSettingsSection from "./realm-settings/RealmSettingsSection";
254254
export { RealmSettingsTabs } from "./realm-settings/RealmSettingsTabs";
255255
export { RealmSettingsSessionsTab } from "./realm-settings/SessionsTab";
256-
export { RealmSettingsThemesTab } from "./realm-settings/ThemesTab";
256+
export * as ThemesTab from "./realm-settings/themes/ThemesTab";
257+
export { ThemeColors } from "./realm-settings/themes/ThemeColors";
258+
export { ThemeSettingsTab } from "./realm-settings/themes/ThemeSettings";
257259
export { RealmSettingsTokensTab } from "./realm-settings/TokensTab";
258260
export { UserRegistration } from "./realm-settings/UserRegistration";
259261
export { RevocationModal } from "./sessions/RevocationModal";

js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { PartialImportDialog } from "./PartialImport";
4242
import { PoliciesTab } from "./PoliciesTab";
4343
import ProfilesTab from "./ProfilesTab";
4444
import { RealmSettingsSessionsTab } from "./SessionsTab";
45-
import { RealmSettingsThemesTab } from "./ThemesTab";
45+
import ThemesTab from "./themes/ThemesTab";
4646
import { RealmSettingsTokensTab } from "./TokensTab";
4747
import { UserRegistration } from "./UserRegistration";
4848
import { EventsTab } from "./event-config/EventsTab";
@@ -355,7 +355,7 @@ export const RealmSettingsTabs = () => {
355355
data-testid="rs-themes-tab"
356356
{...themesTab}
357357
>
358-
<RealmSettingsThemesTab realm={realm!} save={save} />
358+
<ThemesTab realm={realm!} save={save} />
359359
</Tab>
360360
<Tab
361361
title={<TabTitleText>{t("keys")}</TabTitleText>}
Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
import type { AppRouteObject } from "../routes";
2-
import { KeyProviderFormRoute } from "./routes/KeyProvider";
3-
import {
4-
RealmSettingsRoute,
5-
RealmSettingsRouteWithTab,
6-
} from "./routes/RealmSettings";
7-
import { ClientPoliciesRoute } from "./routes/ClientPolicies";
2+
import { AddAttributeRoute } from "./routes/AddAttribute";
3+
import { AddClientPolicyRoute } from "./routes/AddClientPolicy";
84
import { AddClientProfileRoute } from "./routes/AddClientProfile";
9-
import { ClientProfileRoute } from "./routes/ClientProfile";
5+
import { NewClientPolicyConditionRoute } from "./routes/AddCondition";
106
import { AddExecutorRoute } from "./routes/AddExecutor";
11-
import { ExecutorRoute } from "./routes/Executor";
12-
import { AddClientPolicyRoute } from "./routes/AddClientPolicy";
7+
import { AttributeRoute } from "./routes/Attribute";
8+
import { ClientPoliciesRoute } from "./routes/ClientPolicies";
9+
import { ClientProfileRoute } from "./routes/ClientProfile";
10+
import { EditAttributesGroupRoute } from "./routes/EditAttributesGroup";
1311
import { EditClientPolicyRoute } from "./routes/EditClientPolicy";
14-
import { NewClientPolicyConditionRoute } from "./routes/AddCondition";
1512
import { EditClientPolicyConditionRoute } from "./routes/EditCondition";
16-
import { UserProfileRoute } from "./routes/UserProfile";
17-
import { AddAttributeRoute } from "./routes/AddAttribute";
13+
import { ExecutorRoute } from "./routes/Executor";
14+
import { KeyProviderFormRoute } from "./routes/KeyProvider";
1815
import { KeysRoute } from "./routes/KeysTab";
19-
import { AttributeRoute } from "./routes/Attribute";
2016
import { NewAttributesGroupRoute } from "./routes/NewAttributesGroup";
21-
import { EditAttributesGroupRoute } from "./routes/EditAttributesGroup";
17+
import {
18+
RealmSettingsRoute,
19+
RealmSettingsRouteWithTab,
20+
} from "./routes/RealmSettings";
21+
import { ThemeTabRoute } from "./routes/ThemesTab";
22+
import { UserProfileRoute } from "./routes/UserProfile";
2223

2324
const routes: AppRouteObject[] = [
2425
RealmSettingsRoute,
@@ -39,6 +40,7 @@ const routes: AppRouteObject[] = [
3940
AttributeRoute,
4041
NewAttributesGroupRoute,
4142
EditAttributesGroupRoute,
43+
ThemeTabRoute,
4244
];
4345

4446
export default routes;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { lazy } from "react";
2+
import type { Path } from "react-router-dom";
3+
import { generateEncodedPath } from "../../utils/generateEncodedPath";
4+
import type { AppRouteObject } from "../../routes";
5+
6+
export type ThemesTabType = "settings" | "lightColors" | "darkColors";
7+
8+
export type ThemesParams = {
9+
realm: string;
10+
tab: ThemesTabType;
11+
};
12+
13+
const RealmSettingsSection = lazy(() => import("../RealmSettingsSection"));
14+
15+
export const ThemeTabRoute: AppRouteObject = {
16+
path: "/:realm/realm-settings/themes/:tab",
17+
element: <RealmSettingsSection />,
18+
breadcrumb: (t) => t("themes"),
19+
handle: {
20+
access: "view-realm",
21+
},
22+
};
23+
24+
export const toThemesTab = (params: ThemesParams): Partial<Path> => ({
25+
pathname: generateEncodedPath(ThemeTabRoute.path, params),
26+
});

0 commit comments

Comments
 (0)