kunwp1 commented on code in PR #5577:
URL: https://github.com/apache/texera/pull/5577#discussion_r3391715679
##########
amber/src/main/scala/org/apache/texera/web/resource/pythonvirtualenvironment/PveManager.scala:
##########
@@ -47,10 +51,15 @@ object PveManager {
userPackages: Seq[String]
)
+ case class StoredPve(veid: Int, name: String, packagesJson: String)
+
private val VenvRoot: Path = Paths.get("/tmp/texera-pve/venvs")
private val SafePveName = "^[A-Za-z0-9._-]+$".r
+ def isValidPveName(name: String): Boolean =
+ name != null && SafePveName.pattern.matcher(name).matches()
Review Comment:
The database column is `VARCHAR(128)` but `isValidPveName` only checks the
charset. Add a length guard here (e.g. `name.length <= 128`).
##########
frontend/src/app/dashboard/component/user/user-venv/user-venv.component.html:
##########
@@ -0,0 +1,183 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+-->
+
+<div class="section-container subsection-grid-container">
+ <nz-card class="section-title">
+ <h2 class="page-title">Environments</h2>
+ <div class="button-group">
+ <button
+ nz-button
+ class="create-btn"
+ (click)="showPveModal()"
+ title="Create Environment">
+ <i
+ nz-icon
+ nzType="file-add"
+ nzTheme="outline"></i>
+ <span>Create Environment</span>
+ </button>
+ </div>
+ </nz-card>
+
+ <nz-card
+ class="section-list-container"
+ [nzBodyStyle]="{ height: '100%' }">
+ <div class="python-env-page">
+ <div
+ *ngIf="pves.length === 0"
+ class="python-env-page-empty">
+ No environments yet. Click <strong>Create Python Venv</strong> to
create one.
+ </div>
+
+ <ul
+ *ngIf="pves.length > 0"
+ class="python-env-page-list">
+ <li
+ *ngFor="let pve of pves; let i = index; trackBy: trackByVeid"
+ class="python-env-page-item"
+ (click)="openExistingPve(i)">
+ <span class="python-env-name">{{ pve.name || "(unnamed)" }}</span>
+ <i
+ nz-icon
+ nzType="delete"
+ class="python-env-delete-icon"
+ nz-tooltip
+ nzTooltipTitle="Delete environment"
+ (click)="confirmDeletePve(i); $event.stopPropagation()"
+ role="button"
+ aria-label="Delete Python environment">
+ </i>
+ </li>
+ </ul>
+ </div>
+ </nz-card>
+</div>
+
+<nz-modal
+ nzWrapClassName="pve-modal"
+ nzClassName="pve-modal"
+ [nzVisible]="pveModalVisible"
+ nzTitle="Python Environments"
Review Comment:
Ditto.
##########
amber/src/main/scala/org/apache/texera/web/resource/pythonvirtualenvironment/PveResource.scala:
##########
@@ -47,13 +62,91 @@ class PveResource {
Map("system" -> systemPkgs).asJava
} catch {
case e: Exception =>
- e.printStackTrace()
+ logger.error("Failed to get system packages", e)
throw new InternalServerErrorException(
"Failed to get system packages."
)
}
}
+ // --------------------------------------------------
+ // List all PVEs for the current user from the database
+ // --------------------------------------------------
+ @GET
+ @Path("/db")
+ @Produces(Array(MediaType.APPLICATION_JSON))
+ def listPves(@Auth sessionUser: SessionUser): java.util.List[PveListItem] = {
+ PveManager
+ .listPvesForUser(sessionUser.getUid.intValue())
+ .map { stored =>
+ val packages: Map[String, String] =
+ try mapper.readValue(stored.packagesJson, packagesType).asScala.toMap
+ catch { case _: Throwable => Map.empty[String, String] }
+ PveListItem(stored.veid, stored.name, packages)
+ }
+ .asJava
+ }
+
+ // --------------------------------------------------
+ // Update a PVE row owned by the current user
+ // --------------------------------------------------
+ @PUT
+ @Path("/db/{veid}")
+ @Produces(Array(MediaType.APPLICATION_JSON))
+ def updatePve(
+ @PathParam("veid") veid: Int,
+ payload: SavePvePayload,
+ @Auth sessionUser: SessionUser
+ ): Response = {
+ val name = Option(payload.name).map(_.trim).getOrElse("")
+ if (!PveManager.isValidPveName(name)) {
+ return Response.status(Response.Status.BAD_REQUEST).entity("invalid
name").build()
+ }
+ try {
+ val packagesJson = mapper.writeValueAsString(payload.packages)
+ val updated = PveManager.updatePve(veid, sessionUser.getUid.intValue(),
name, packagesJson)
+ if (updated) Response.ok(Map("veid" -> veid).asJava).build()
+ else Response.status(Response.Status.NOT_FOUND).build()
+ } catch {
+ case e: Exception =>
+ logger.error("Failed to update PVE", e)
+ throw new InternalServerErrorException(s"Failed to update PVE:
${e.getMessage}")
+ }
+ }
+
+ // --------------------------------------------------
+ // Delete a PVE row owned by the current user
+ // --------------------------------------------------
+ @DELETE
+ @Path("/db/{veid}")
+ def deletePveFromDb(@PathParam("veid") veid: Int, @Auth sessionUser:
SessionUser): Response = {
+ val deleted = PveManager.deletePveFromDb(veid,
sessionUser.getUid.intValue())
+ if (deleted) Response.noContent().build()
+ else Response.status(Response.Status.NOT_FOUND).build()
+ }
+
+ // --------------------------------------------------
+ // Save a PVE (name + packages) to the database for the current user
+ // --------------------------------------------------
+ @POST
+ @Path("/db")
+ @Produces(Array(MediaType.APPLICATION_JSON))
+ def savePve(payload: SavePvePayload, @Auth sessionUser: SessionUser):
Response = {
+ val name = Option(payload.name).map(_.trim).getOrElse("")
+ if (!PveManager.isValidPveName(name)) {
+ return Response.status(Response.Status.BAD_REQUEST).entity("invalid
name").build()
+ }
+ try {
+ val packagesJson = mapper.writeValueAsString(payload.packages)
+ val veid = PveManager.savePve(sessionUser.getUid.intValue(), name,
packagesJson)
+ Response.ok(Map("veid" -> veid).asJava).build()
+ } catch {
+ case e: Exception =>
+ logger.error("Failed to save PVE", e)
+ throw new InternalServerErrorException(s"Failed to save PVE:
${e.getMessage}")
+ }
Review Comment:
Try to return 409 error for unique constraint violations instead of 500
error. Same for `updatePve`. Also, better to return 201 instead of 200 for
success.
##########
frontend/src/app/dashboard/component/user/user-venv/user-venv.component.html:
##########
@@ -0,0 +1,183 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+-->
+
+<div class="section-container subsection-grid-container">
+ <nz-card class="section-title">
+ <h2 class="page-title">Environments</h2>
+ <div class="button-group">
+ <button
+ nz-button
+ class="create-btn"
+ (click)="showPveModal()"
+ title="Create Environment">
+ <i
+ nz-icon
+ nzType="file-add"
+ nzTheme="outline"></i>
+ <span>Create Environment</span>
+ </button>
+ </div>
+ </nz-card>
+
+ <nz-card
+ class="section-list-container"
+ [nzBodyStyle]="{ height: '100%' }">
+ <div class="python-env-page">
+ <div
+ *ngIf="pves.length === 0"
+ class="python-env-page-empty">
+ No environments yet. Click <strong>Create Python Venv</strong> to
create one.
Review Comment:
This says **“Create Python Venv”**, but the actual button (and the page
title) is **“Create Environment”** / **“Environments”**. Suggest aligning the
names.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]