Upgrade Welcome theme to PatternFly 5

Closes #21343

Co-authored-by: Pedro Igor <pigor.craveiro@gmail.com>
Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Jon Koops 2023-12-06 18:13:56 +01:00 committed by Pedro Igor
parent 78d32e6452
commit 07f9ead128
21 changed files with 209 additions and 319 deletions

View file

@ -172,7 +172,7 @@ public class ClusteringTest extends BaseOperatorTest {
.untilAsserted(() -> assertThat(crSelector.scale().getStatus().getReplicas()).isEqualTo(2));
// get the service
String url = "https://" + KeycloakServiceDependentResource.getServiceName(kc) + "." + namespace + ":" + Constants.KEYCLOAK_HTTPS_PORT;
String url = "https://" + KeycloakServiceDependentResource.getServiceName(kc) + "." + namespace + ":" + Constants.KEYCLOAK_HTTPS_PORT + "/admin/master/console/";
Awaitility.await().atMost(5, MINUTES).untilAsserted(() -> {
Log.info("Starting curl Pod to test if the realm is available");

View file

@ -661,7 +661,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
assertThat(k8sclient.resources(Service.class).withName(serviceName).require().getSpec().getPorts()
.stream().map(ServicePort::getName).anyMatch(protocol::equals));
String url = protocol + "://" + serviceName + "." + namespace + ":" + port;
String url = protocol + "://" + serviceName + "." + namespace + ":" + port + "/admin/master/console/";
Log.info("Checking url: " + url);
var curlOutput = K8sUtils.inClusterCurl(k8sclient, namespace, url);

View file

@ -19,13 +19,17 @@ package org.keycloak.it.cli.dist;
import io.quarkus.test.junit.main.Launch;
import io.restassured.RestAssured;
import io.restassured.config.RedirectConfig;
import io.restassured.config.RestAssuredConfig;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import org.keycloak.quarkus.runtime.services.resources.DebugHostnameSettingsResource;
@ -35,12 +39,15 @@ import static io.restassured.RestAssured.when;
import static org.hamcrest.MatcherAssert.assertThat;
@DistributionTest(keepAlive = true, enableTls = true, defaultOptions = { "--http-enabled=true" })
@WithEnvVars({"KEYCLOAK_ADMIN", "admin123", "KEYCLOAK_ADMIN_PASSWORD", "admin123"})
@RawDistOnly(reason = "Containers are immutable")
public class HostnameDistTest {
@BeforeAll
public static void onBeforeAll() {
RestAssured.useRelaxedHTTPSValidation();
RestAssuredConfig config = RestAssured.config;
RestAssured.config = config.redirect(RedirectConfig.redirectConfig().followRedirects(false));
}
@Test
@ -121,10 +128,10 @@ public class HostnameDistTest {
@Test
@Launch({ "start", "--hostname=mykeycloak.org", "--hostname-port=8543" })
public void testWelcomePageAdminUrl() {
when().get("http://mykeycloak.org:8080").then().body(Matchers.containsString("http://mykeycloak.org:8080/admin/"));
when().get("https://mykeycloak.org:8443").then().body(Matchers.containsString("https://mykeycloak.org:8443/admin/"));
when().get("http://localhost:8080").then().body(Matchers.containsString("http://localhost:8080/admin/"));
when().get("https://localhost:8443").then().body(Matchers.containsString("https://localhost:8443/admin/"));
when().get("http://mykeycloak.org:8080").then().header(HttpHeaders.LOCATION, Matchers.containsString("http://mykeycloak.org:8080/admin/"));
when().get("https://mykeycloak.org:8443").then().header(HttpHeaders.LOCATION, Matchers.containsString("https://mykeycloak.org:8443/admin/"));
when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, Matchers.containsString("http://localhost:8080/admin/"));
when().get("https://localhost:8443").then().header(HttpHeaders.LOCATION, Matchers.containsString("https://localhost:8443/admin/"));
}
@Test
@ -162,20 +169,20 @@ public class HostnameDistTest {
@Test
@Launch({ "start", "--hostname=mykeycloak.org", "--hostname-admin=mykeycloakadmin.org" })
public void testHostnameAdminSet() {
when().get("https://mykeycloak.org:8443/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"https://mykeycloakadmin.org:8443\""));
when().get("https://mykeycloak.org:8443/admin/master/console/").then().body(Matchers.containsString("\"authUrl\": \"https://mykeycloakadmin.org:8443\""));
when().get("https://mykeycloak.org:8443/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=https://mykeycloakadmin.org:8443/admin/master/console&state=02234324-d91e-4bf2-8396-57498e96b12a&response_mode=fragment&response_type=code&scope=openid&nonce=f8f3812e-e349-4bbf-8d15-cbba4927f5e5&code_challenge=7qjD_v11WGkt1ig-ZFHxJdrEvuTlzjFRgRGQ_5ADcko&code_challenge_method=S256").then().body(Matchers.containsString("Sign in to your account"));
when().get("http://localhost:8080/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"http://mykeycloakadmin.org:8080\""));
when().get("http://localhost:8080/admin/master/console/").then().body(Matchers.containsString("\"authUrl\": \"http://mykeycloakadmin.org:8080\""));
}
@Test
@Launch({"start", "--hostname=mykeycloak.org", "--hostname-debug=true"})
public void testHostnameAdminFromHeaders() {
when().get("https://mykeycloak.org:8443/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"https://mykeycloak.org:8443\""));
when().get("https://mykeycloak.org:8443/admin/master/console/").then().body(Matchers.containsString("\"authUrl\": \"https://mykeycloak.org:8443\""));
when().get("https://mykeycloak.org:8443/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=https://mykeycloak.org:8443/admin/master/console&state=02234324-d91e-4bf2-8396-57498e96b12a&response_mode=fragment&response_type=code&scope=openid&nonce=f8f3812e-e349-4bbf-8d15-cbba4927f5e5&code_challenge=7qjD_v11WGkt1ig-ZFHxJdrEvuTlzjFRgRGQ_5ADcko&code_challenge_method=S256").then().body(Matchers.containsString("Sign in to your account"));
// Admin URL should be resolved from headers
when().get("http://localhost:8080/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"http://localhost:8080\""));
when().get("http://localhost:8080/admin/master/console/").then().body(Matchers.containsString("\"authUrl\": \"http://localhost:8080\""));
when().get("http://localhost:8080/realms/master/protocol/openid-connect/auth?client_id=security-admin-console&redirect_uri=http://localhost:8080/admin/master/console&state=02234324-d91e-4bf2-8396-57498e96b12a&response_mode=fragment&response_type=code&scope=openid&nonce=f8f3812e-e349-4bbf-8d15-cbba4927f5e5&code_challenge=7qjD_v11WGkt1ig-ZFHxJdrEvuTlzjFRgRGQ_5ADcko&code_challenge_method=S256").then().body(Matchers.containsString("Sign in to your account"));
Consumer<String> assertDebugAdmin = (url) -> {
@ -202,8 +209,8 @@ public class HostnameDistTest {
@Test
@Launch({ "start", "--proxy=edge", "--hostname=mykeycloak.org", "--hostname-admin-url=http://mykeycloakadmin.org:1234" })
public void testAdminUrl() {
when().get("https://mykeycloak.org:8443").then().body(Matchers.containsString("http://mykeycloakadmin.org:1234/admin/"));
when().get("http://localhost:8080/admin/master/console").then().body(Matchers.containsString("\"authUrl\": \"http://mykeycloakadmin.org:1234\""));
when().get("https://mykeycloak.org:8443").then().header(HttpHeaders.LOCATION, Matchers.containsString("http://mykeycloakadmin.org:1234/admin/"));
when().get("http://localhost:8080/admin/master/console/").then().body(Matchers.containsString("\"authUrl\": \"http://mykeycloakadmin.org:1234\""));
}
@Test

View file

@ -17,27 +17,33 @@
package org.keycloak.it.cli.dist;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.containsString;
import io.quarkus.test.junit.main.Launch;
import io.restassured.RestAssured;
import io.restassured.config.RedirectConfig;
import io.restassured.config.RestAssuredConfig;
import org.apache.http.HttpHeaders;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.keycloak.it.junit5.extension.DistributionTest;
import org.keycloak.it.junit5.extension.RawDistOnly;
import org.keycloak.it.junit5.extension.WithEnvVars;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
import io.quarkus.test.junit.main.Launch;
import io.restassured.RestAssured;
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.containsString;
@DistributionTest(keepAlive = true, enableTls = true)
@WithEnvVars({"KEYCLOAK_ADMIN", "admin123", "KEYCLOAK_ADMIN_PASSWORD", "admin123"})
@RawDistOnly(reason = "Containers are immutable")
public class ProxyDistTest {
@BeforeAll
public static void onBeforeAll() {
RestAssured.useRelaxedHTTPSValidation();
RestAssuredConfig config = RestAssured.config;
RestAssured.config = config.redirect(RedirectConfig.redirectConfig().followRedirects(false));
}
@Test
@ -106,33 +112,36 @@ public class ProxyDistTest {
@Test
@Launch({ "start-dev", "--hostname-url=http://mykeycloak.org:1234", "--hostname-admin-url=http://mykeycloakadmin.127.0.0.1.nip.io:1234", "--proxy=edge" })
public void testIgnoreForwardedHeadersWhenFrontendUrlSet() {
given().header("X-Forwarded-Host", "test").when().get("http://mykeycloak.org:8080").then().body(containsString("http://mykeycloakadmin.127.0.0.1.nip.io:1234/admin"));
given().header("X-Forwarded-Proto", "https").when().get("http://localhost:8080").then().body(containsString("http://mykeycloakadmin.127.0.0.1.nip.io:1234/admin"));
given().header("X-Forwarded-Host", "test").when().get("http://mykeycloak.org:8080").then().header(HttpHeaders.LOCATION, containsString("http://mykeycloakadmin.127.0.0.1.nip.io:1234/admin"));
given().header("X-Forwarded-Proto", "https").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("http://mykeycloakadmin.127.0.0.1.nip.io:1234/admin"));
}
private void assertForwardedHeader() {
given().header("Forwarded", "for=12.34.56.78;host=test:1234;proto=https, for=23.45.67.89").when().get("http://mykeycloak.org:8080").then().body(containsString("https://test:1234/admin"));
given()
.header("Forwarded", "for=12.34.56.78;host=test:1234;proto=https, for=23.45.67.89")
.when().get("http://mykeycloak.org:8080")
.then().header(HttpHeaders.LOCATION, containsString("https://test:1234/admin"));
}
private void assertForwardedHeaderIsIgnored() {
given().header("Forwarded", "for=12.34.56.78;host=test:1234;proto=https, for=23.45.67.89").when().get("http://localhost:8080").then().body(containsString("http://localhost:8080"));
given().header("Forwarded", "for=12.34.56.78;host=test:1234;proto=https, for=23.45.67.89").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("http://localhost:8080"));
}
private void assertXForwardedHeaders() {
given().header("X-Forwarded-Host", "test").when().get("http://mykeycloak.org:8080").then().body(containsString("http://test:8080/admin"));
given().header("X-Forwarded-Host", "test").when().get("http://localhost:8080").then().body(containsString("http://test:8080/admin"));
given().header("X-Forwarded-Host", "test").when().get("https://localhost:8443").then().body(containsString("https://test:8443/admin"));
given().header("X-Forwarded-Host", "test").when().get("http://mykeycloak.org:8080").then().header(HttpHeaders.LOCATION, containsString("http://test:8080/admin"));
given().header("X-Forwarded-Host", "test").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("http://test:8080/admin"));
given().header("X-Forwarded-Host", "test").when().get("https://localhost:8443").then().header(HttpHeaders.LOCATION, containsString("https://test:8443/admin"));
//given().header("X-Forwarded-Host", "mykeycloak.org").when().get("https://localhost:8443/admin/master/console").then().body(containsString("<script src=\"/js/keycloak.js?version="));
given().header("X-Forwarded-Proto", "https").when().get("http://localhost:8080").then().body(containsString("https://localhost/admin"));
given().header("X-Forwarded-Proto", "https").header("X-Forwarded-Port", "8443").when().get("http://localhost:8080").then().body(containsString("https://localhost:8443/admin"));
given().header("X-Forwarded-Proto", "https").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("https://localhost/admin"));
given().header("X-Forwarded-Proto", "https").header("X-Forwarded-Port", "8443").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("https://localhost:8443/admin"));
}
private void assertXForwardedHeadersAreIgnored() {
given().header("X-Forwarded-Host", "test").when().get("http://mykeycloak.org:8080").then().body(containsString("http://mykeycloak.org:8080/admin"));
given().header("X-Forwarded-Host", "test").when().get("http://localhost:8080").then().body(containsString("http://localhost:8080/admin"));
given().header("X-Forwarded-Host", "test").when().get("https://localhost:8443").then().body(containsString("https://localhost:8443/admin"));
given().header("X-Forwarded-Proto", "https").when().get("http://localhost:8080").then().body(containsString("http://localhost:8080/admin"));
given().header("X-Forwarded-Proto", "https").header("X-Forwarded-Port", "8443").when().get("http://localhost:8080").then().body(containsString("http://localhost:8080/admin"));
given().header("X-Forwarded-Host", "test").when().get("http://mykeycloak.org:8080").then().header(HttpHeaders.LOCATION, containsString("http://mykeycloak.org:8080/admin"));
given().header("X-Forwarded-Host", "test").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("http://localhost:8080/admin"));
given().header("X-Forwarded-Host", "test").when().get("https://localhost:8443").then().header(HttpHeaders.LOCATION, containsString("https://localhost:8443/admin"));
given().header("X-Forwarded-Proto", "https").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("http://localhost:8080/admin"));
given().header("X-Forwarded-Proto", "https").header("X-Forwarded-Port", "8443").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("http://localhost:8080/admin"));
}
private OIDCConfigurationRepresentation getServerMetadata(String baseUrl) {

View file

@ -16,6 +16,20 @@
*/
package org.keycloak.services.resources;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;
import jakarta.ws.rs.core.Response.Status;
import org.jboss.logging.Logger;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.Profile;
@ -35,20 +49,6 @@ import org.keycloak.theme.freemarker.FreeMarkerProvider;
import org.keycloak.urls.UrlType;
import org.keycloak.utils.MediaType;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;
import jakarta.ws.rs.core.Response.Status;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
@ -58,6 +58,7 @@ import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@ -177,23 +178,32 @@ public class WelcomeResource {
return rb.build();
}
boolean bootstrap = shouldBootstrap();
boolean adminConsoleEnabled = isAdminConsoleEnabled();
Properties themeProperties = theme.getProperties();
boolean redirectToAdmin = Boolean.parseBoolean(themeProperties.getProperty("redirectToAdmin", "false"));
URI adminUrl = session.getContext().getUri(UrlType.ADMIN).getBaseUriBuilder().path("/admin/").build();
// Redirect to the Administration Console if the administrative user already exists.
if (redirectToAdmin && !bootstrap && adminConsoleEnabled && successMessage == null) {
return Response.status(302).location(adminUrl).build();
}
Map<String, Object> map = new HashMap<>();
map.put("adminConsoleEnabled", isAdminConsoleEnabled());
map.put("bootstrap", bootstrap);
map.put("adminConsoleEnabled", adminConsoleEnabled);
map.put("properties", themeProperties);
map.put("adminUrl", adminUrl);
map.put("baseUrl", session.getContext().getUri(UrlType.FRONTEND).getBaseUri());
map.put("productName", Version.NAME);
map.put("properties", theme.getProperties());
map.put("adminUrl", session.getContext().getUri(UrlType.ADMIN).getBaseUriBuilder().path("/admin/").build());
map.put("resourcesPath", "resources/" + Version.RESOURCES_VERSION + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName());
map.put("resourcesCommonPath", "resources/" + Version.RESOURCES_VERSION + "/common/keycloak");
boolean bootstrap = shouldBootstrap();
map.put("bootstrap", bootstrap);
if (bootstrap) {
boolean isLocal = isLocal();
map.put("localUser", isLocal);
if (bootstrap) {
String localAdminUrl = session.getContext().getUri(UrlType.LOCAL_ADMIN).getBaseUri().toString();
String adminCreationMessage = getAdminCreationMessage();
map.put("localAdminUrl", localAdminUrl);

View file

@ -1105,7 +1105,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
URI url = suiteContext.getAuthServerInfo().getUriBuilder().path("/auth").build();
String response = SimpleHttp.doGet(url.toString(), client).asString();
Matcher m = Pattern.compile("resources/([^/]*)/welcome").matcher(response);
Matcher m = Pattern.compile("resources/([^/]*)/common").matcher(response);
assertTrue(m.find());
assertTrue(m.group(1).matches("[a-zA-Z0-9_\\-.~]{5}"));
} catch (IOException e) {

View file

@ -118,7 +118,7 @@ public class ThemeResourceProviderTest extends AbstractTestRealmKeycloakTest {
return deleted;
}, Boolean.class);
assertEncoded(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/resources/" + resourcesVersion + "/welcome/keycloak/css/welcome.css", "body {");
assertEncoded(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/resources/" + resourcesVersion + "/welcome/keycloak/css/welcome.css", ".pf-v5-c-background-image");
assertEncoded(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/js/keycloak.js", "function Keycloak (config)");
// Check no files exists inside "/tmp" directory. We need to skip this test in the rare case when there are thombstone files created by different user

View file

@ -1,129 +1,121 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ 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.
-->
<!DOCTYPE html>
<html>
<!doctype html>
<html lang="en">
<head>
<title>Welcome to ${productName}</title>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="robots" content="noindex, nofollow">
<link rel="shortcut icon" href="${resourcesPath}/img/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome to ${productName}</title>
<link rel="shortcut icon" href="${resourcesCommonPath}/img/favicon.ico">
<#if properties.stylesCommon?has_content>
<#list properties.stylesCommon?split(' ') as style>
<link href="${resourcesCommonPath}/${style}" rel="stylesheet" />
<link rel="stylesheet" href="${resourcesCommonPath}/${style}">
</#list>
</#if>
<#if properties.styles?has_content>
<#list properties.styles?split(' ') as style>
<link href="${resourcesPath}/${style}" rel="stylesheet" />
<link rel="stylesheet" href="${resourcesPath}/${style}">
</#list>
</#if>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2">
<div class="welcome-header">
<img src="${resourcesPath}/logo.png" alt="${productName}" border="0" />
<h1>Welcome to <strong>${productName}</strong></h1>
</div>
<div class="row">
<#if adminConsoleEnabled>
<div class="col-xs-12 col-sm-4">
<div class="card-pf h-l">
<#if successMessage?has_content>
<p class="alert success">${successMessage}</p>
<#elseif errorMessage?has_content>
<p class="alert error">${errorMessage}</p>
<h3><img src="welcome-content/user.png">Administration Console</h3>
<#elseif bootstrap>
<div class="pf-v5-c-background-image" style="--pf-v5-c-background-image--BackgroundImage: url(${baseUrl}${resourcesPath}/background.svg)"></div>
<div class="pf-v5-c-login">
<div class="pf-v5-c-login__container">
<header class="pf-v5-c-login__header">
<img class="pf-v5-c-brand" src="${resourcesPath}/logo.svg" alt="${productName} Logo">
</header>
<#if adminConsoleEnabled && (bootstrap || successMessage?has_content)>
<main class="pf-v5-c-login__main">
<header class="pf-v5-c-login__main-header">
<#if localUser>
<h3><img src="welcome-content/user.png">Administration Console</h3>
<p>Please create an initial admin user to get started.</p>
<h1 class="pf-v5-c-title pf-m-3xl">Create an administrative user</h1>
<#if !successMessage?has_content>
<p class="pf-v5-c-login__main-header-desc">To get started with ${productName}, you first create an administrative user.</p>
</#if>
<#else>
<p class="welcome-message">
<img src="welcome-content/alert.png">You need local access to create the initial admin user. <br><br>Open <a href="${localAdminUrl}">${localAdminUrl}</a>
<br>${adminUserCreationMessage}.
</p>
<h1 class="pf-v5-c-title pf-m-3xl">Local access required</h1>
<p class="pf-v5-c-login__main-header-desc">You will need local access to create the administrative user.</p>
</#if>
</header>
<div class="pf-v5-c-login__main-body">
<#if successMessage?has_content>
<div class="pf-v5-c-alert pf-m-inline pf-m-success pf-v5-u-mb-xl">
<div class="pf-v5-c-alert__icon">
<svg class="pf-v5-svg" viewBox="0 0 512 512" fill="currentColor" aria-hidden="true" role="img" width="1em" height="1em">
<path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path>
</svg>
</div>
<h4 class="pf-v5-c-alert__title">
<span class="pf-v5-screen-reader">Success alert:</span>${successMessage}
</h4>
</div>
<a class="pf-v5-c-button pf-m-primary pf-m-block" href="${adminUrl}">Open Administration Console</a>
</#if>
<#if bootstrap && localUser>
<form method="post" class="welcome-form">
<p>
<label for="username">Username</label>
<input id="username" name="username" />
</p>
<p>
<label for="password">Password</label>
<input id="password" name="password" type="password" />
</p>
<p>
<label for="passwordConfirmation">Password confirmation</label>
<input id="passwordConfirmation" name="passwordConfirmation" type="password" />
</p>
<input id="stateChecker" name="stateChecker" type="hidden" value="${stateChecker}" />
<button id="create-button" type="submit" class="btn btn-primary">Create</button>
<#if bootstrap>
<#if localUser>
<form class="pf-v5-c-form" method="post" novalidate>
<#if errorMessage?has_content>
<div class="pf-v5-c-form__alert">
<div class="pf-v5-c-alert pf-m-inline pf-m-danger">
<div class="pf-v5-c-alert__icon">
<svg class="pf-v5-svg" viewBox="0 0 512 512" fill="currentColor" aria-hidden="true" role="img" width="1em" height="1em">
<path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path>
</svg>
</div>
<h4 class="pf-v5-c-alert__title">
<span class="pf-v5-screen-reader">Danger alert:</span>${errorMessage}
</h4>
</div>
</div>
</#if>
<div class="pf-v5-c-form__group">
<div class="pf-v5-c-form__group-label">
<label class="pf-v5-c-form__label" for="username">
<span class="pf-v5-c-form__label-text">Username</span>&nbsp;<span class="pf-v5-c-form__label-required" aria-hidden="true">&#42;</span>
</label>
</div>
<div class="pf-v5-c-form__group-control">
<span class="pf-v5-c-form-control pf-m-required">
<input id="username" type="text" name="username" autocomplete="username" required>
</span>
</div>
</div>
<div class="pf-v5-c-form__group">
<div class="pf-v5-c-form__group-label">
<label class="pf-v5-c-form__label" for="password">
<span class="pf-v5-c-form__label-text">Password</span>&nbsp;<span class="pf-v5-c-form__label-required" aria-hidden="true">&#42;</span>
</label>
</div>
<div class="pf-v5-c-form__group-control">
<span class="pf-v5-c-form-control pf-m-required">
<input id="password" type="password" name="password" autocomplete="new-password" required>
</span>
</div>
</div>
<div class="pf-v5-c-form__group">
<div class="pf-v5-c-form__group-label">
<label class="pf-v5-c-form__label" for="password-confirmation">
<span class="pf-v5-c-form__label-text">Password confirmation</span>&nbsp;<span class="pf-v5-c-form__label-required" aria-hidden="true">&#42;</span>
</label>
</div>
<div class="pf-v5-c-form__group-control">
<span class="pf-v5-c-form-control pf-m-required">
<input id="password-confirmation" type="password" name="passwordConfirmation" autocomplete="new-password" required>
</span>
</div>
</div>
<input name="stateChecker" type="hidden" value="${stateChecker}">
<div class="pf-v5-c-form__group pf-m-action">
<button class="pf-v5-c-button pf-m-primary pf-m-block" type="submit">Create user</button>
</div>
</form>
<#else>
<p>To create the administrative user open <a href="${localAdminUrl}">${localAdminUrl}</a>, or set the environment variables <code>KEYCLOAK_ADMIN</code> and <code>KEYCLOAK_ADMIN_PASSWORD</code> when starting the server.</p>
</#if>
<div class="welcome-primary-link">
<h3><a href="${adminUrl}"><img src="welcome-content/user.png">Administration Console <i class="fa fa-angle-right link" aria-hidden="true"></i></a></h3>
<div class="description">
Centrally manage all aspects of the ${productName} server
</div>
</div>
</div>
</div>
</#if> <#-- adminConsoleEnabled -->
<div class="col-xs-12 col-sm-4">
<div class="card-pf h-l">
<h3><a href="${properties.documentationUrl}"><img class="doc-img" src="welcome-content/admin-console.png">Documentation <i class="fa fa-angle-right link" aria-hidden="true"></i></a></h3>
<div class="description">
User Guide, Admin REST API and Javadocs
</div>
</div>
</div>
<div class="col-xs-12 col-sm-4">
<#if properties.displayCommunityLinks = "true">
<div class="card-pf h-m">
<h3><a href="http://www.keycloak.org"><img src="welcome-content/keycloak-project.png">Keycloak Project <i class="fa fa-angle-right link" aria-hidden="true"></i></a></h3>
</div>
<div class="card-pf h-m">
<h3><a href="https://groups.google.com/forum/#!forum/keycloak-user"><img src="welcome-content/mail.png">Mailing List <i class="fa fa-angle-right link" aria-hidden="true"></i></a></h3>
</div>
<div class="card-pf h-m">
<h3><a href="https://github.com/keycloak/keycloak/issues"><img src="welcome-content/bug.png">Report an issue <i class="fa fa-angle-right link" aria-hidden="true"></i></a></h3>
</div>
</#if>
</div>
</div>
</div>
</main>
</#if>
</div>
</div>
</body>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,140 +1,10 @@
body {
background: #fff url(../bg.png) no-repeat center bottom fixed;
background-size: cover;
}
.welcome-header {
margin-top: 10px;
margin-bottom: 50px;
margin-left: -10px;
}
.welcome-header img {
width: 150px;
margin-bottom: 40px;
}
.welcome-message {
margin-top: 20px;
}
.h-l {
min-height: 370px;
padding: 10px 20px 10px;
overflow: hidden;
}
.h-l h3 {
margin-bottom: 10px;
}
.h-m {
height: 110px;
padding-top: 23px;
}
.card-pf img {
width: 22px;
margin-right: 10px;
vertical-align: bottom;
}
img.doc-img {
width: auto;
height: 22px;
}
.link {
font-size: 16px;
vertical-align: baseline;
margin-left: 5px;
}
h3 {
font-weight: 550;
}
h3 a:link,
h3 a:visited {
color: #333;
font-weight: 550;
}
h3 a:hover,
h3 a:hover .link {
text-decoration: none;
color: #00659c;
}
.h-l h3 a img {
height: 30px;
width: auto;
.pf-v5-c-background-image {
/* Change background size so that the image covers the entire background. */
--pf-v5-c-background-image--BackgroundSize: cover;
}
.description {
margin-top: 30px;
}
.card-pf {
border-top: 1px solid rgba(3, 3, 3, 0.1);
box-shadow: 0 1px 1px rgba(3, 3, 3, 0.275);
}
.welcome-form label,
.welcome-form input {
display: block;
width: 100%;
}
.welcome-form label {
color: #828486;
font-weight: normal;
margin-top: 18px;
}
.welcome-form input {
border: 0;
border-bottom: solid 1px #cbcbcb;
}
.welcome-form input:focus {
border-bottom: solid 1px #5e99c6;
outline-width: 0;
}
.welcome-form button {
margin-top: 10px;
}
.error {
color: #c00;
border-color: #c00;
padding: 5px 10px;
}
.success {
color: #3f9c35;
border-color: #3f9c35;
padding: 5px 10px;
}
.welcome-form + .welcome-primary-link,
.welcome-message + .welcome-primary-link {
display: none;
}
.footer img {
float: right;
width: 150px;
margin-top: 30px;
}
@media (max-width: 768px) {
.welcome-header {
margin-top: 10px;
margin-bottom: 20px;
}
.welcome-header img {
margin-bottom: 20px;
}
h3 {
margin-top: 10px;
}
.h-l,
.h-m {
height: auto;
min-height: auto;
padding: 5px 10px;
}
.h-l img {
display: inline;
margin-bottom: auto;
}
.description {
display: none;
}
.footer img {
margin-top: 10px;
}
.pf-v5-c-login__container {
/* Change the grid layout so that the header is always above the main area. */
grid-template-areas: "header" "main";
--pf-v5-c-login__container--xl--GridTemplateColumns: minmax(auto, 34rem);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,7 +1,7 @@
styles=css/welcome.css
import=common/keycloak
stylesCommon=node_modules/patternfly/dist/css/patternfly.css node_modules/patternfly/dist/css/patternfly-additions.css
stylesCommon=node_modules/@patternfly-v5/patternfly/patternfly.min.css node_modules/@patternfly-v5/patternfly/patternfly-addons.css
styles=css/welcome.css
documentationUrl=https://www.keycloak.org/documentation.html
displayCommunityLinks=true
# When set to true, the user will be redirected to the Administration Console if an administrative users already exists.
redirectToAdmin=true