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-server/src/main/java/org/opendc/web/server | |
| 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-server/src/main/java/org/opendc/web/server')
12 files changed, 62 insertions, 85 deletions
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java index a0ac390f..ef342e5f 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java @@ -28,6 +28,17 @@ import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import io.quarkus.hibernate.orm.panache.PanacheQuery; import io.quarkus.panache.common.Parameters; import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedQueries; +import jakarta.persistence.NamedQuery; +import jakarta.persistence.Table; import java.time.Instant; import java.util.Map; import org.hibernate.annotations.Type; diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Portfolio.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Portfolio.java index c2695192..80031c0a 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Portfolio.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Portfolio.java @@ -51,6 +51,7 @@ import org.opendc.web.proto.Targets; */ @Entity @Table( + name = "portfolios", uniqueConstraints = { @UniqueConstraint( name = "uk_portfolios_number", diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java index f4e5305d..ca032e21 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java @@ -44,7 +44,7 @@ import java.util.Set; * A project in OpenDC encapsulates all the datacenter designs and simulation runs for a set of users. */ @Entity -@Table +@Table(name = "projects") @NamedQueries({ @NamedQuery( name = "Project.findByUser", @@ -52,7 +52,7 @@ import java.util.Set; """ SELECT a FROM ProjectAuthorization a - WHERE a.key.userName = :userName + WHERE a.key.userId = :userId """), @NamedQuery( name = "Project.allocatePortfolio", diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/ProjectAuthorization.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/ProjectAuthorization.java index 3776ae12..ad94ad29 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/ProjectAuthorization.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/ProjectAuthorization.java @@ -47,7 +47,7 @@ import org.opendc.web.proto.user.ProjectRole; * An authorization for some user to participate in a project. */ @Entity -@Table +@Table(name = "project_authorizations") @NamedQueries({ @NamedQuery( name = "ProjectAuthorization.findByUser", @@ -55,7 +55,7 @@ import org.opendc.web.proto.user.ProjectRole; """ SELECT a FROM ProjectAuthorization a - WHERE a.key.userName = :userName + WHERE a.key.userId = :userId """), }) public class ProjectAuthorization extends PanacheEntityBase { @@ -88,8 +88,8 @@ public class ProjectAuthorization extends PanacheEntityBase { /** * Construct a {@link ProjectAuthorization} object. */ - public ProjectAuthorization(Project project, String userName, ProjectRole role) { - this.key = new ProjectAuthorization.Key(project.id, userName); + public ProjectAuthorization(Project project, String userId, ProjectRole role) { + this.key = new ProjectAuthorization.Key(project.id, userId); this.project = project; this.role = role; } @@ -100,25 +100,25 @@ public class ProjectAuthorization extends PanacheEntityBase { protected ProjectAuthorization() {} /** - * List all projects for the user with the specified <code>userName</code>. + * List all projects for the user with the specified <code>userId</code>. * - * @param userName The identifier of the user that is requesting the list of projects. + * @param userId The identifier of the user that is requesting the list of projects. * @return A query returning projects that the user has received authorization for. */ - public static PanacheQuery<ProjectAuthorization> findByUser(String userName) { - return find("#ProjectAuthorization.findByUser", Parameters.with("userName", userName)); + public static PanacheQuery<ProjectAuthorization> findByUser(String userId) { + return find("#ProjectAuthorization.findByUser", Parameters.with("userId", userId)); } /** - * Find the project with <code>id</code> for the user with the specified <code>userName</code>. + * Find the project with <code>id</code> for the user with the specified <code>userId</code>. * - * @param userName The identifier of the user that is requesting the list of projects. - * @param project_id The unique identifier of the project. + * @param userId The identifier of the user that is requesting the list of projects. + * @param id The unique identifier of the project. * @return The project with the specified identifier or <code>null</code> if it does not exist or is not accessible * to the user with the specified identifier. */ - public static ProjectAuthorization findByUser(String userName, long project_id) { - return findById(new ProjectAuthorization.Key(project_id, userName)); + public static ProjectAuthorization findByUser(String userId, long id) { + return findById(new ProjectAuthorization.Key(id, userId)); } /** @@ -146,12 +146,12 @@ public class ProjectAuthorization extends PanacheEntityBase { @Column(name = "project_id", nullable = false) public long projectId; - @Column(name = "user_name", nullable = false) - public String userName; + @Column(name = "user_id", nullable = false) + public String userId; - public Key(long projectId, String userName) { + public Key(long projectId, String userId) { this.projectId = projectId; - this.userName = userName; + this.userId = userId; } protected Key() {} @@ -161,12 +161,12 @@ public class ProjectAuthorization extends PanacheEntityBase { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Key key = (Key) o; - return projectId == key.projectId && userName.equals(key.userName); + return projectId == key.projectId && userId.equals(key.userId); } @Override public int hashCode() { - return Objects.hash(projectId, userName); + return Objects.hash(projectId, userId); } } } diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Scenario.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Scenario.java index c79ef5bb..0224ae43 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Scenario.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Scenario.java @@ -27,6 +27,20 @@ import io.quarkus.hibernate.orm.panache.PanacheEntityBase; import io.quarkus.hibernate.orm.panache.PanacheQuery; import io.quarkus.panache.common.Parameters; import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedQueries; +import jakarta.persistence.NamedQuery; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import java.util.ArrayList; import java.util.List; import org.hibernate.annotations.Type; @@ -37,6 +51,7 @@ import org.opendc.web.proto.OperationalPhenomena; */ @Entity @Table( + name = "scenarios", uniqueConstraints = { @UniqueConstraint( name = "uk_scenarios_number", @@ -109,13 +124,10 @@ public class Scenario extends PanacheEntityBase { /** * Operational phenomena activated in the scenario. - * @Column(columnDefinition = "jsonb", nullable = false, updatable = false) - * @Type(JsonType.class) */ @Column(columnDefinition = "jsonb", nullable = false, updatable = false) @Type(JsonType.class) public OperationalPhenomena phenomena; - /** * The name of the VM scheduler used in the scenario. */ diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java index 8a4e2ae2..ff8b4416 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java @@ -41,13 +41,14 @@ import jakarta.persistence.UniqueConstraint; import java.time.Instant; import java.util.List; import org.hibernate.annotations.Type; -import org.opendc.web.proto.Room; +import org.opendc.web.proto.topology.Room; /** * A datacenter design in OpenDC. */ @Entity @Table( + name = "topologies", uniqueConstraints = { @UniqueConstraint( name = "uk_topologies_number", @@ -103,8 +104,6 @@ public class Topology extends PanacheEntityBase { /** * Datacenter design in JSON - * @Column(columnDefinition = "jsonb", nullable = false) - * @Type(JsonType.class) */ @Column(columnDefinition = "jsonb", nullable = false) @Type(JsonType.class) diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java deleted file mode 100644 index 345acdfe..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.server.rest.error; - -import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.ext.ExceptionMapper; -import jakarta.ws.rs.ext.Provider; -import org.opendc.web.proto.ProtocolError; - -/** - * An [ExceptionMapper] for [MissingKotlinParameterException] thrown by Jackson. - */ -@Provider -public final class MissingKotlinParameterExceptionMapper implements ExceptionMapper<MissingKotlinParameterException> { - @Override - public Response toResponse(MissingKotlinParameterException exception) { - return Response.status(Response.Status.BAD_REQUEST) - .entity(new ProtocolError( - Response.Status.BAD_REQUEST.getStatusCode(), - "Field " + exception.getParameter().getName() + " is missing from body.")) - .type(MediaType.APPLICATION_JSON) - .build(); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java index 4dde8654..2b774082 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java @@ -98,7 +98,7 @@ public final class JobResource { } try { - jobService.updateJob(job, update.getState(), update.getRuntime(), update.getResults()); + jobService.updateJob(job, update.state(), update.runtime(), update.results()); } catch (IllegalArgumentException e) { throw new WebApplicationException(e, 400); } catch (IllegalStateException e) { diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java index 2a3a40f4..e4d5362c 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java @@ -100,7 +100,7 @@ public final class PortfolioResource { var project = auth.project; int number = project.allocatePortfolio(now); - Portfolio portfolio = new Portfolio(project, number, request.getName(), request.getTargets()); + Portfolio portfolio = new Portfolio(project, number, request.name(), request.targets()); project.portfolios.add(portfolio); portfolio.persist(); diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java index 789808c8..ea87a7ad 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java @@ -118,12 +118,12 @@ public final class PortfolioScenarioResource { throw new WebApplicationException("Portfolio not found", 404); } - Topology topology = Topology.findByProject(projectId, (int) request.getTopology()); + Topology topology = Topology.findByProject(projectId, (int) request.topology()); if (topology == null) { throw new WebApplicationException("Referred topology does not exist", 400); } - Trace trace = Trace.findById(request.getWorkload().getTrace()); + Trace trace = Trace.findById(request.workload().trace()); if (trace == null) { throw new WebApplicationException("Referred trace does not exist", 400); } @@ -136,14 +136,14 @@ public final class PortfolioScenarioResource { project, portfolio, number, - request.getName(), - new Workload(trace, request.getWorkload().getSamplingFraction()), + request.name(), + new Workload(trace, request.workload().samplingFraction()), topology, - request.getPhenomena(), - request.getSchedulerName()); + request.phenomena(), + request.schedulerName()); scenario.persist(); - Job job = new Job(scenario, userId, now, portfolio.targets.getRepeats()); + Job job = new Job(scenario, userId, now, portfolio.targets.repeats()); job.persist(); // Fail the job if there is not enough budget for the simulation diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java index ae1c959e..40ebc666 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java @@ -79,7 +79,7 @@ public final class ProjectResource { @Consumes("application/json") public org.opendc.web.proto.user.Project create(@Valid org.opendc.web.proto.user.Project.Create request) { Instant now = Instant.now(); - Project entity = new Project(request.getName(), now); + Project entity = new Project(request.name(), now); entity.persist(); ProjectAuthorization authorization = diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java index b8c542d3..25819e32 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java @@ -104,7 +104,7 @@ public final class TopologyResource { Project project = auth.project; int number = project.allocateTopology(now); - Topology topology = new Topology(project, number, request.getName(), now, request.getRooms()); + Topology topology = new Topology(project, number, request.name(), now, request.rooms()); project.topologies.add(topology); topology.persist(); @@ -164,7 +164,7 @@ public final class TopologyResource { } entity.updatedAt = Instant.now(); - entity.rooms = request.getRooms(); + entity.rooms = request.rooms(); return UserProtocol.toDto(entity, auth); } |
