diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ErrorPageAction.java b/core/runtime/src/main/java/io/quarkus/runtime/ErrorPageAction.java
new file mode 100644
index 0000000000000..4dc3967abf23a
--- /dev/null
+++ b/core/runtime/src/main/java/io/quarkus/runtime/ErrorPageAction.java
@@ -0,0 +1,5 @@
+package io.quarkus.runtime;
+
+public record ErrorPageAction(String name, String url) {
+
+}
diff --git a/core/runtime/src/main/java/io/quarkus/runtime/TemplateHtmlBuilder.java b/core/runtime/src/main/java/io/quarkus/runtime/TemplateHtmlBuilder.java
index 4d8b4b9315749..22b3cce91e9f2 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/TemplateHtmlBuilder.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/TemplateHtmlBuilder.java
@@ -115,6 +115,7 @@ public class TemplateHtmlBuilder {
+ "\n" +
" \n" +
"
%2$s
\n" +
+ "
%3$s
\n" +
"
\n" +
"\n" +
"
\n";
@@ -176,25 +177,37 @@ public class TemplateHtmlBuilder {
private String baseUrl;
public TemplateHtmlBuilder(String title, String subTitle, String details) {
- this(null, title, subTitle, details, null, Collections.emptyList());
+ this(null, title, subTitle, details, Collections.emptyList(), null, Collections.emptyList());
}
- public TemplateHtmlBuilder(String baseUrl, String title, String subTitle, String details) {
- this(baseUrl, title, subTitle, details, null, Collections.emptyList());
+ public TemplateHtmlBuilder(String title, String subTitle, String details, List
actions) {
+ this(null, title, subTitle, details, actions, null, Collections.emptyList());
}
- public TemplateHtmlBuilder(String title, String subTitle, String details, String redirect,
+ public TemplateHtmlBuilder(String baseUrl, String title, String subTitle, String details, List actions) {
+ this(baseUrl, title, subTitle, details, actions, null, Collections.emptyList());
+ }
+
+ public TemplateHtmlBuilder(String title, String subTitle, String details, List actions, String redirect,
List config) {
- this(null, title, subTitle, details, null, Collections.emptyList());
+ this(null, title, subTitle, details, actions, null, Collections.emptyList());
}
- public TemplateHtmlBuilder(String baseUrl, String title, String subTitle, String details, String redirect,
+ public TemplateHtmlBuilder(String baseUrl, String title, String subTitle, String details, List actions,
+ String redirect,
List config) {
this.baseUrl = baseUrl;
+
loadCssFile();
+
+ StringBuilder actionLinks = new StringBuilder();
+ for (ErrorPageAction epa : actions) {
+ actionLinks.append(buildLink(epa.name(), epa.url()));
+ }
+
result = new StringBuilder(String.format(HTML_TEMPLATE_START, escapeHtml(title),
subTitle == null || subTitle.isEmpty() ? "" : " - " + escapeHtml(subTitle), CSS));
- result.append(String.format(HEADER_TEMPLATE, escapeHtml(title), escapeHtml(details)));
+ result.append(String.format(HEADER_TEMPLATE, escapeHtml(title), escapeHtml(details), actionLinks.toString()));
if (!config.isEmpty()) {
result.append(String.format(CONFIG_EDITOR_HEAD, redirect));
for (CurrentConfig i : config) {
@@ -379,4 +392,8 @@ public void loadCssFile() {
}
}
}
+
+ private String buildLink(String name, String url) {
+ return "" + name + "";
+ }
}
diff --git a/core/runtime/src/main/resources/META-INF/template-html-builder.css b/core/runtime/src/main/resources/META-INF/template-html-builder.css
index 2d76259b11b96..7760fd6de8743 100644
--- a/core/runtime/src/main/resources/META-INF/template-html-builder.css
+++ b/core/runtime/src/main/resources/META-INF/template-html-builder.css
@@ -34,6 +34,25 @@ ul {
.exception-message {
background: #960031;
font-size: 1.5rem;
+ display: flex;
+ align-items: center;
+ padding-right: 50px;
+}
+
+.actions {
+ font-size: 1.1rem;
+ display: flex;
+ gap: 10px;
+}
+
+.actions a {
+
+ padding: 1px 6px;
+ border: 1px outset #180011;
+ border-radius: 4px;
+ color: white !important;
+ background-color: #041437;
+ text-decoration: none !important;
}
h1, h2 {
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/ErrorPageActionsBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/ErrorPageActionsBuildItem.java
new file mode 100644
index 0000000000000..fe1f1bc70ddae
--- /dev/null
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/ErrorPageActionsBuildItem.java
@@ -0,0 +1,29 @@
+package io.quarkus.vertx.http.deployment;
+
+import java.util.List;
+
+import io.quarkus.builder.item.MultiBuildItem;
+import io.quarkus.runtime.ErrorPageAction;
+
+/**
+ * Allows extensions to contribute an action (button) to the error page
+ */
+public final class ErrorPageActionsBuildItem extends MultiBuildItem {
+ private final List actions;
+
+ public ErrorPageActionsBuildItem(String name, String url) {
+ this(new ErrorPageAction(name, url));
+ }
+
+ public ErrorPageActionsBuildItem(ErrorPageAction errorPageAction) {
+ this(List.of(errorPageAction));
+ }
+
+ public ErrorPageActionsBuildItem(List errorPageAction) {
+ this.actions = errorPageAction;
+ }
+
+ public List getActions() {
+ return actions;
+ }
+}
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java
index 495e331d6257a..53d8bfb19f20f 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java
@@ -51,6 +51,7 @@
import io.quarkus.deployment.pkg.steps.NoopNativeImageBuildRunner;
import io.quarkus.kubernetes.spi.KubernetesPortBuildItem;
import io.quarkus.netty.runtime.virtual.VirtualServerChannel;
+import io.quarkus.runtime.ErrorPageAction;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.LiveReloadConfig;
import io.quarkus.runtime.RuntimeValue;
@@ -338,6 +339,7 @@ ServiceStartBuildItem finalizeRouter(
HttpBuildTimeConfig httpBuildTimeConfig,
List requireBodyHandlerBuildItems,
BodyHandlerBuildItem bodyHandlerBuildItem,
+ List errorPageActionsBuildItems,
BuildProducer shutdownListenerBuildItemBuildProducer,
ShutdownConfig shutdownConfig,
LiveReloadConfig lrc,
@@ -391,6 +393,12 @@ ServiceStartBuildItem finalizeRouter(
}
}
+ // Combine all error actions from exceptions
+ List combinedActions = new ArrayList<>();
+ for (ErrorPageActionsBuildItem errorPageActionsBuildItem : errorPageActionsBuildItems) {
+ combinedActions.addAll(errorPageActionsBuildItem.getActions());
+ }
+
recorder.finalizeRouter(beanContainer.getValue(),
defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null),
listOfFilters, listOfManagementInterfaceFilters,
@@ -401,7 +409,8 @@ ServiceStartBuildItem finalizeRouter(
nonApplicationRootPathBuildItem.getNonApplicationRootPath(),
launchMode.getLaunchMode(),
getBodyHandlerRequiredConditions(requireBodyHandlerBuildItems), bodyHandlerBuildItem.getHandler(),
- gracefulShutdownFilter, shutdownConfig, executorBuildItem.getExecutorProxy());
+ gracefulShutdownFilter, shutdownConfig, executorBuildItem.getExecutorProxy(),
+ combinedActions);
return new ServiceStartBuildItem("vertx-http");
}
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/QuarkusErrorHandler.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/QuarkusErrorHandler.java
index 20c10be2eb202..8405fe52bfe00 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/QuarkusErrorHandler.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/QuarkusErrorHandler.java
@@ -18,6 +18,7 @@
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
+import io.quarkus.runtime.ErrorPageAction;
import io.quarkus.runtime.TemplateHtmlBuilder;
import io.quarkus.security.AuthenticationException;
import io.quarkus.security.ForbiddenException;
@@ -43,12 +44,20 @@ public class QuarkusErrorHandler implements Handler {
private final boolean showStack;
private final boolean decorateStack;
private final Optional contentTypeDefault;
+ private final List actions;
public QuarkusErrorHandler(boolean showStack, boolean decorateStack,
Optional contentTypeDefault) {
+ this(showStack, decorateStack, contentTypeDefault, List.of());
+ }
+
+ public QuarkusErrorHandler(boolean showStack, boolean decorateStack,
+ Optional contentTypeDefault,
+ List actions) {
this.showStack = showStack;
this.decorateStack = decorateStack;
this.contentTypeDefault = contentTypeDefault;
+ this.actions = actions;
}
@Override
@@ -200,10 +209,12 @@ private void jsonResponse(RoutingContext event, String contentType, String detai
private void htmlResponse(RoutingContext event, String details, Throwable exception) {
event.response().headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8");
- final TemplateHtmlBuilder htmlBuilder = new TemplateHtmlBuilder("Internal Server Error", details, details);
+ final TemplateHtmlBuilder htmlBuilder = new TemplateHtmlBuilder("Internal Server Error", details, details,
+ this.actions);
if (showStack && exception != null) {
htmlBuilder.stack(exception);
}
+
writeResponse(event, htmlBuilder.toString());
}
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
index 0a221237122a8..156aabc83880f 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
@@ -54,6 +54,7 @@
import io.quarkus.netty.runtime.virtual.VirtualAddress;
import io.quarkus.netty.runtime.virtual.VirtualChannel;
import io.quarkus.netty.runtime.virtual.VirtualServerChannel;
+import io.quarkus.runtime.ErrorPageAction;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.LiveReloadConfig;
import io.quarkus.runtime.QuarkusBindException;
@@ -377,7 +378,8 @@ public void finalizeRouter(BeanContainer container, Consumer defaultRoute
LaunchMode launchMode, BooleanSupplier[] requireBodyHandlerConditions,
Handler bodyHandler,
GracefulShutdownFilter gracefulShutdownFilter, ShutdownConfig shutdownConfig,
- Executor executor) {
+ Executor executor,
+ List actions) {
HttpConfiguration httpConfiguration = this.httpConfiguration.getValue();
// install the default route at the end
Router httpRouteRouter = httpRouterRuntimeValue.getValue();
@@ -415,8 +417,7 @@ public void finalizeRouter(BeanContainer container, Consumer defaultRoute
applyCompression(httpBuildTimeConfig.enableCompression, httpRouteRouter);
httpRouteRouter.route().last().failureHandler(
new QuarkusErrorHandler(launchMode.isDevOrTest(), decorateStacktrace(launchMode, httpConfiguration),
- httpConfiguration.unhandledErrorContentTypeDefault));
-
+ httpConfiguration.unhandledErrorContentTypeDefault, actions));
for (BooleanSupplier requireBodyHandlerCondition : requireBodyHandlerConditions) {
if (requireBodyHandlerCondition.getAsBoolean()) {
//if this is set then everything needs the body handler installed
@@ -535,7 +536,7 @@ public void handle(RoutingContext event) {
mr.route().last().failureHandler(
new QuarkusErrorHandler(launchMode.isDevOrTest(), decorateStacktrace(launchMode, httpConfiguration),
- httpConfiguration.unhandledErrorContentTypeDefault));
+ httpConfiguration.unhandledErrorContentTypeDefault, actions));
mr.route().order(RouteConstants.ROUTE_ORDER_BODY_HANDLER_MANAGEMENT)
.handler(createBodyHandlerForManagementInterface());
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ReplacementDebugPage.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ReplacementDebugPage.java
index 1ad26a274de46..5cc7647580bfc 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ReplacementDebugPage.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ReplacementDebugPage.java
@@ -64,7 +64,7 @@ public static String generateHtml(final Throwable exception, String currentUri)
}
TemplateHtmlBuilder builder = new TemplateHtmlBuilder("Error restarting Quarkus", exception.getClass().getName(),
- generateHeaderMessage(exception), currentUri, toEdit);
+ generateHeaderMessage(exception), List.of(), currentUri, toEdit);
builder.stack(exception);
return builder.toString();
}
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ResourceNotFoundData.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ResourceNotFoundData.java
index ad0e6bab207f5..b9f28a85dee5a 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ResourceNotFoundData.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/ResourceNotFoundData.java
@@ -75,7 +75,7 @@ public String getHTMLContent() {
List combinedRoutes = getCombinedRoutes();
TemplateHtmlBuilder builder = new TemplateHtmlBuilder(this.baseUrl,
- HEADING, "", "Resources overview");
+ HEADING, "", "Resources overview", List.of());
builder.resourcesStart(RESOURCE_ENDPOINTS);