diff options
| author | vincent van beek <vincent@vlogic.nl> | 2026-03-26 14:02:54 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-26 13:02:54 +0000 |
| commit | 0ffde21b0337c606e2d0ece5bd5434a930a87dcd (patch) | |
| tree | 4fd071728a8da6dcf3e6fc9fe9dca5a492100d34 /opendc-web/opendc-web-quarkus | |
| parent | 8bb98c773bc982a0dab9cf9fb20d62f60a36a5d7 (diff) | |
Use Quarkus Quinoa for serving web UI (#391)
* refactor(web): Migrate to Quarkus 3
This commit updates the OpenDC web server to use Quarkus 3, which
changes annotations to use the Jakarta namespace for annotations.
* refactor(web): Configure runtime variables for web UI
This commit updates the web UI to propagate runtime variables via the
next-runtime-env package. Before, we would need to replace the variables
in the generated sources by Next.js, next-runtime-env works by loading a
JavaScript file when opening the OpenDC web UI which contains the
configuration of the web app.
* refactor(web): Migrate to Quarkus Quinoa
This commit updates the OpenDC web server to make use of Quarkus Quinoa
for serving the web UI. This allows us to deprecate the complex Quarkus
extension for serving the web UI.
* refactor(web): Move web UI into Quarkus web app
This commit moves the web UI into the Quarkus web server module to
ensure we follow Quarkus Quinoa's conventions.
* refactor(web): Merge Quarkus extension into single module
This commit merges the existing Quarkus extensions into a single module
to prevent build complexity.
* refactor(web): Migrate web proto to Java
This commit migrates the web protocol to Java and removes the dependency
on Jandex Gradle.
* refactor(web): Migrate to Quarkus 3
This commit updates the OpenDC web server to use Quarkus 3, which
changes annotations to use the Jakarta namespace for annotations.
* enable DB schema migration on DEV server
* webui is not needed anymore
* remove MAINTAINERS is depricated
* fix quarkus.quinoa properties
* revert properties change, install npm in docker image to allow building the frontend
* pin postgres version, this is a best practice. Fix some properties the old ones are depricated. Added properties for local testing
* fix build error
* :opendc-web:opendc-web-proto:spotlessApply
* fix database schema
---------
Co-authored-by: Fabian Mastenbroek <mail.fabianm@gmail.com>
Diffstat (limited to 'opendc-web/opendc-web-quarkus')
8 files changed, 429 insertions, 0 deletions
diff --git a/opendc-web/opendc-web-quarkus/build.gradle.kts b/opendc-web/opendc-web-quarkus/build.gradle.kts new file mode 100644 index 00000000..a48c2eb3 --- /dev/null +++ b/opendc-web/opendc-web-quarkus/build.gradle.kts @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Quarkus extension for serving OpenDC web interface" + +plugins { + `java-library-conventions` + id("io.quarkus.extension") +} + +quarkusExtension { + deploymentModule.set("opendc-web-quarkus-deployment") +} + +dependencies { + implementation(platform(libs.quarkus.bom)) + + api(projects.opendcWeb.opendcWebRunner) + + implementation(libs.quarkus.core.runtime) + implementation(libs.quarkus.vertx.http.runtime) + implementation(libs.quarkus.arc.runtime) +} diff --git a/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/runner/OpenDCRunnerRecorder.java b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/runner/OpenDCRunnerRecorder.java new file mode 100644 index 00000000..814ddf15 --- /dev/null +++ b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/runner/OpenDCRunnerRecorder.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.quarkus.runtime.runner; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Recorder; +import jakarta.enterprise.inject.spi.CDI; +import java.io.File; +import org.jboss.logging.Logger; +import org.opendc.web.runner.JobManager; +import org.opendc.web.runner.OpenDCRunner; + +/** + * Helper class for starting the OpenDC web runner. + */ +@Recorder +public class OpenDCRunnerRecorder { + private static final Logger LOGGER = Logger.getLogger(OpenDCRunnerRecorder.class.getName()); + + /** + * Helper method to create an {@link OpenDCRunner} instance. + */ + public RuntimeValue<OpenDCRunner> createRunner(OpenDCRunnerRuntimeConfig config) { + int parallelism = config.parallelism; + if (parallelism < 0) { + throw new IllegalArgumentException("Parallelism must be non-negative"); + } else if (parallelism == 0) { + parallelism = Math.min(1, Runtime.getRuntime().availableProcessors() - 1); + } + + JobManager manager = CDI.current().select(JobManager.class).get(); + OpenDCRunner runner = new OpenDCRunner( + manager, + new File(config.tracePath), + parallelism, + config.jobTimeout, + config.pollInterval, + config.heartbeatInterval); + + return new RuntimeValue<>(runner); + } + + /** + * Helper method to start the OpenDC runner service. + */ + public void startRunner( + RuntimeValue<OpenDCRunner> runner, OpenDCRunnerRuntimeConfig config, ShutdownContext shutdownContext) { + if (config.enable) { + LOGGER.info("Starting OpenDC Runner in background (polling every " + config.pollInterval + ")"); + + Thread thread = new Thread(runner.getValue()); + thread.setName("opendc-runner"); + thread.start(); + + shutdownContext.addShutdownTask(thread::interrupt); + } + } +} diff --git a/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/runner/OpenDCRunnerRuntimeConfig.java b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/runner/OpenDCRunnerRuntimeConfig.java new file mode 100644 index 00000000..fbc56bfa --- /dev/null +++ b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/runner/OpenDCRunnerRuntimeConfig.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.quarkus.runtime.runner; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import java.time.Duration; + +/** + * Configuration for the OpenDC web runner. + */ +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "opendc-runner") +public class OpenDCRunnerRuntimeConfig { + /** + * Flag to indicate whether the runner should be enabled. + */ + @ConfigItem(defaultValue = "true") + public boolean enable; + + /** + * The path where the workload traces are located. + */ + @ConfigItem(defaultValue = "traces") + public String tracePath; + + /** + * The number of concurrent simulations + */ + @ConfigItem(defaultValue = "1") + public int parallelism; + + /** + * The maximum duration of a job. + */ + @ConfigItem(defaultValue = "10m") + public Duration jobTimeout; + + /** + * The interval between successive polls to the API. + */ + @ConfigItem(defaultValue = "30s") + public Duration pollInterval; + + /** + * The interval between successive heartbeats to the API. + */ + @ConfigItem(defaultValue = "1m") + public Duration heartbeatInterval; +} diff --git a/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/AuthConfiguration.java b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/AuthConfiguration.java new file mode 100644 index 00000000..10f91923 --- /dev/null +++ b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/AuthConfiguration.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.quarkus.runtime.ui; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import java.util.Optional; + +/** + * Auth configuration for the OpenDC UI extension. + */ +@ConfigGroup +public class AuthConfiguration { + /** + * The authentication domain. + */ + @ConfigItem + Optional<String> domain; + + /** + * The client identifier used by the OpenDC web ui. + */ + @ConfigItem + Optional<String> clientId; + + /** + * The audience of the OpenDC API. + */ + @ConfigItem + Optional<String> audience; +} diff --git a/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/OpenDCUiConfig.java b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/OpenDCUiConfig.java new file mode 100644 index 00000000..541cfdc1 --- /dev/null +++ b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/OpenDCUiConfig.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.quarkus.runtime.ui; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import java.util.Optional; + +/** + * Configuration for the OpenDC web UI. + */ +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "opendc-ui") +public class OpenDCUiConfig { + /** + * The base URL of the OpenDC API. + */ + @ConfigItem(defaultValue = "/api") + String apiBaseUrl; + + /** + * Configuration properties for web UI authentication. + */ + AuthConfiguration auth; + + /** + * Sentry DSN. + */ + @ConfigItem + Optional<String> sentryDsn; +} diff --git a/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/OpenDCUiRecorder.java b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/OpenDCUiRecorder.java new file mode 100644 index 00000000..5783e431 --- /dev/null +++ b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/OpenDCUiRecorder.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.quarkus.runtime.ui; + +import static io.vertx.ext.web.handler.StaticHandler.DEFAULT_MAX_AGE_SECONDS; + +import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.impl.Utils; + +/** + * Recorder class for the OpenDC web UI. + */ +@Recorder +public class OpenDCUiRecorder { + /** + * Construct a {@link Handler} for serving the configuration of the OpenDC web UI. + */ + public Handler<RoutingContext> configHandler(OpenDCUiConfig config) { + return (event) -> { + HttpServerRequest request = event.request(); + if (request.method() != HttpMethod.GET && request.method() != HttpMethod.HEAD) { + event.next(); + return; + } + + event.response() + .setStatusCode(200) + .putHeader(HttpHeaders.CONTENT_TYPE, "text/javascript") + .putHeader(HttpHeaders.CACHE_CONTROL, "public, immutable, max-age=" + DEFAULT_MAX_AGE_SECONDS) + .putHeader(HttpHeaders.DATE, Utils.formatRFC1123DateTime(System.currentTimeMillis())) + .end(OpenDCUiRecorder.serializeConfig(config)); + }; + } + + /** + * Serialize the configuration of the OpenDC web UI into JSON. + * + * @param config The configuration of the OpenDC web UI specified by the user. + * @return JS serialized version of the OpenDC web UI config. + */ + private static String serializeConfig(OpenDCUiConfig config) { + JsonObject configJson = JsonObject.of("NEXT_PUBLIC_API_BASE_URL", config.apiBaseUrl); + config.auth.domain.ifPresent(s -> configJson.put("NEXT_PUBLIC_AUTH0_DOMAIN", s)); + config.auth.clientId.ifPresent(s -> configJson.put("NEXT_PUBLIC_AUTH0_CLIENT_ID", s)); + config.auth.audience.ifPresent(s -> configJson.put("NEXT_PUBLIC_AUTH0_AUDIENCE", s)); + config.sentryDsn.ifPresent(s -> configJson.put("NEXT_PUBLIC_SENTRY_DSN", s)); + + return "window.__ENV = " + configJson.encode() + ";"; + } +} diff --git a/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/QuinoaNextRoutingRecorder.java b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/QuinoaNextRoutingRecorder.java new file mode 100644 index 00000000..c511220d --- /dev/null +++ b/opendc-web/opendc-web-quarkus/src/main/java/org/opendc/web/quarkus/runtime/ui/QuinoaNextRoutingRecorder.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.quarkus.runtime.ui; + +import io.quarkus.runtime.annotations.Recorder; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +/** + * Recorder class for building route handlers for Next.js pages and redirects. + */ +@Recorder +public class QuinoaNextRoutingRecorder { + /** + * Construct a {@link Handler} for serving a dynamic route of a Next.js application. + */ + public Handler<RoutingContext> pageHandler(String basePath, String page) { + return (event) -> event.reroute(basePath + page + ".html"); + } + + /** + * Construct a {@link Handler} for handling redirects of a Next.js application. + */ + public Handler<RoutingContext> redirectHandler(String destination, int statusCode) { + return (event) -> { + String query = event.request().query(); + String fullDestination = query != null ? destination + "?" + query : destination; + + event.response() + .setStatusCode(statusCode) + .putHeader("Location", fullDestination) + .end(); + }; + } +} diff --git a/opendc-web/opendc-web-quarkus/src/main/resources/META-INF/quarkus-extension.yaml b/opendc-web/opendc-web-quarkus/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000..581a1779 --- /dev/null +++ b/opendc-web/opendc-web-quarkus/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,5 @@ +--- +name: "OpenDC Web UI" +metadata: + status: "preview" + unlisted: true |
