You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by la...@apache.org on 2026/07/27 01:20:33 UTC

(airavata-custos) 17/18: Show cluster name and sign-in details for allocations

This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch pearc26
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git

commit da363383e99b820b8faf1bd98f2076d4bffd3aa4
Author: lahiruj <la...@gmail.com>
AuthorDate: Sun Jul 26 20:03:31 2026 -0500

    Show cluster name and sign-in details for allocations
---
 .../migrations/000010_cluster_login_host.down.sql  | 19 +++++++++++++++
 .../db/migrations/000010_cluster_login_host.up.sql | 24 +++++++++++++++++++
 internal/server/allocation.go                      | 20 +++++++++++++---
 .../server/membership_scope_integration_test.go    |  3 +++
 internal/server/responses.go                       |  7 ++++--
 internal/store/compute_cluster_store.go            | 14 +++++------
 pkg/models/allocation.go                           |  2 ++
 .../components/AllocationOverviewTab.tsx           | 28 +++++++++++++++++++++-
 .../allocations/components/AllocationsList.tsx     |  6 +++--
 web/src/features/core/allocations/schemas.ts       |  2 ++
 10 files changed, 110 insertions(+), 15 deletions(-)

diff --git a/internal/db/migrations/000010_cluster_login_host.down.sql b/internal/db/migrations/000010_cluster_login_host.down.sql
new file mode 100644
index 000000000..f62ac6343
--- /dev/null
+++ b/internal/db/migrations/000010_cluster_login_host.down.sql
@@ -0,0 +1,19 @@
+-- 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.
+
+ALTER TABLE compute_clusters
+    DROP COLUMN login_host;
diff --git a/internal/db/migrations/000010_cluster_login_host.up.sql b/internal/db/migrations/000010_cluster_login_host.up.sql
new file mode 100644
index 000000000..5082ecf18
--- /dev/null
+++ b/internal/db/migrations/000010_cluster_login_host.up.sql
@@ -0,0 +1,24 @@
+-- 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.
+
+SET NAMES utf8mb4;
+SET time_zone = '+00:00';
+
+-- Where users SSH in. Empty until an operator sets it; the portal hides
+-- connection help for clusters without one.
+ALTER TABLE compute_clusters
+    ADD COLUMN login_host VARCHAR(255) NOT NULL DEFAULT '';
diff --git a/internal/server/allocation.go b/internal/server/allocation.go
index ca77876de..0bd9155d9 100644
--- a/internal/server/allocation.go
+++ b/internal/server/allocation.go
@@ -66,7 +66,12 @@ func (s *Server) getComputeAllocation(w http.ResponseWriter, r *http.Request) {
 		common.WriteServiceError(w, err)
 		return
 	}
-	common.WriteJSON(w, http.StatusOK, a)
+	caller := requireCaller(w, r)
+	if caller == nil {
+		return
+	}
+	items := s.allocationListItems(r.Context(), caller.UserID, []models.ComputeAllocation{*a})
+	common.WriteJSON(w, http.StatusOK, items[0])
 }
 
 // @Summary	Create a compute allocation diff
@@ -204,8 +209,8 @@ func (s *Server) listComputeAllocations(w http.ResponseWriter, r *http.Request)
 	common.WriteJSON(w, http.StatusOK, ComputeAllocationListResponse{Items: s.allocationListItems(r.Context(), caller.UserID, rows), Total: total})
 }
 
-// allocationListItems joins each allocation with the caller's own account on
-// its cluster, when one exists.
+// allocationListItems joins each allocation with its cluster's display
+// fields and the caller's own account on that cluster, when one exists.
 func (s *Server) allocationListItems(ctx context.Context, userID string, rows []models.ComputeAllocation) []AllocationListItem {
 	items := make([]AllocationListItem, 0, len(rows))
 	usernameByCluster := map[string]string{}
@@ -214,10 +219,19 @@ func (s *Server) allocationListItems(ctx context.Context, userID string, rows []
 			usernameByCluster[a.ComputeClusterID] = a.LocalUsername
 		}
 	}
+	clusterByID := map[string]models.ComputeCluster{}
+	if clusters, err := s.svc.ListComputeClusters(ctx); err == nil {
+		for _, c := range clusters {
+			clusterByID[c.ID] = c
+		}
+	}
 	for _, row := range rows {
+		cluster := clusterByID[row.ComputeClusterID]
 		items = append(items, AllocationListItem{
 			ComputeAllocation: row,
 			LocalUsername:     usernameByCluster[row.ComputeClusterID],
+			ClusterName:       cluster.Name,
+			LoginHost:         cluster.LoginHost,
 		})
 	}
 	return items
diff --git a/internal/server/membership_scope_integration_test.go b/internal/server/membership_scope_integration_test.go
index 2a4ab4a1e..bf7ee0fec 100644
--- a/internal/server/membership_scope_integration_test.go
+++ b/internal/server/membership_scope_integration_test.go
@@ -149,6 +149,9 @@ func TestListComputeAllocations_CarriesCallerUsername(t *testing.T) {
 	if len(body.Items) != 1 || body.Items[0].LocalUsername != "memberlocal" {
 		t.Fatalf("local_username: got %+v, want memberlocal on the row", body.Items)
 	}
+	if body.Items[0].ClusterName == "" {
+		t.Errorf("cluster_name missing from list item")
+	}
 }
 
 func TestListComputeAllocations_GovernanceRoleSeesProjectAllocations(t *testing.T) {
diff --git a/internal/server/responses.go b/internal/server/responses.go
index ff9f86d24..1494d9400 100644
--- a/internal/server/responses.go
+++ b/internal/server/responses.go
@@ -94,11 +94,14 @@ type AccessRequestListItem struct {
 	AllocationID string     `json:"allocation_id,omitempty"`
 }
 
-// AllocationListItem is an allocation plus the caller's account name on its
-// cluster, so the list can show where and as whom the caller signs in.
+// AllocationListItem is an allocation plus its cluster's display fields and
+// the caller's account name, so views can show where and as whom the caller
+// signs in.
 type AllocationListItem struct {
 	models.ComputeAllocation
 	LocalUsername string `json:"local_username,omitempty"`
+	ClusterName   string `json:"cluster_name,omitempty"`
+	LoginHost     string `json:"login_host,omitempty"`
 }
 
 // ComputeAllocationListResponse is the paginated list envelope for compute
diff --git a/internal/store/compute_cluster_store.go b/internal/store/compute_cluster_store.go
index c27b77bae..3446281d4 100644
--- a/internal/store/compute_cluster_store.go
+++ b/internal/store/compute_cluster_store.go
@@ -39,7 +39,7 @@ func NewComputeClusterStore(db *sqlx.DB) ComputeClusterStore {
 func (s *mysqlComputeClusterStore) FindByID(ctx context.Context, id string) (*models.ComputeCluster, error) {
 	var c models.ComputeCluster
 	err := s.db.GetContext(ctx, &c,
-		`SELECT id, name FROM compute_clusters WHERE id = ?`, id)
+		`SELECT id, name, login_host FROM compute_clusters WHERE id = ?`, id)
 	if err != nil {
 		if errors.Is(err, sql.ErrNoRows) {
 			return nil, nil
@@ -52,7 +52,7 @@ func (s *mysqlComputeClusterStore) FindByID(ctx context.Context, id string) (*mo
 func (s *mysqlComputeClusterStore) FindByName(ctx context.Context, name string) (*models.ComputeCluster, error) {
 	var c models.ComputeCluster
 	err := s.db.GetContext(ctx, &c,
-		`SELECT id, name FROM compute_clusters WHERE name = ?`, name)
+		`SELECT id, name, login_host FROM compute_clusters WHERE name = ?`, name)
 	if err != nil {
 		if errors.Is(err, sql.ErrNoRows) {
 			return nil, nil
@@ -65,7 +65,7 @@ func (s *mysqlComputeClusterStore) FindByName(ctx context.Context, name string)
 func (s *mysqlComputeClusterStore) List(ctx context.Context) ([]models.ComputeCluster, error) {
 	var clusters []models.ComputeCluster
 	err := s.db.SelectContext(ctx, &clusters,
-		`SELECT id, name FROM compute_clusters ORDER BY name`)
+		`SELECT id, name, login_host FROM compute_clusters ORDER BY name`)
 	if err != nil {
 		return nil, err
 	}
@@ -74,15 +74,15 @@ func (s *mysqlComputeClusterStore) List(ctx context.Context) ([]models.ComputeCl
 
 func (s *mysqlComputeClusterStore) Create(ctx context.Context, tx *sql.Tx, c *models.ComputeCluster) error {
 	_, err := tx.ExecContext(ctx,
-		`INSERT INTO compute_clusters (id, name) VALUES (?, ?)`,
-		c.ID, c.Name)
+		`INSERT INTO compute_clusters (id, name, login_host) VALUES (?, ?, ?)`,
+		c.ID, c.Name, c.LoginHost)
 	return err
 }
 
 func (s *mysqlComputeClusterStore) Update(ctx context.Context, tx *sql.Tx, c *models.ComputeCluster) error {
 	_, err := tx.ExecContext(ctx,
-		`UPDATE compute_clusters SET name = ? WHERE id = ?`,
-		c.Name, c.ID)
+		`UPDATE compute_clusters SET name = ?, login_host = ? WHERE id = ?`,
+		c.Name, c.LoginHost, c.ID)
 	return err
 }
 
diff --git a/pkg/models/allocation.go b/pkg/models/allocation.go
index 741442825..9fc7a4063 100644
--- a/pkg/models/allocation.go
+++ b/pkg/models/allocation.go
@@ -30,6 +30,8 @@ const (
 type ComputeCluster struct {
 	ID   string `json:"id"   db:"id"`
 	Name string `json:"name" db:"name"` // A human-readable name for the compute cluster, e.g., "Cluster A", "Cluster B", etc.
+	// Where users SSH in; empty hides connection help in the portal.
+	LoginHost string `json:"login_host" db:"login_host"`
 }
 
 type ComputeClusterUser struct {
diff --git a/web/src/features/core/allocations/components/AllocationOverviewTab.tsx b/web/src/features/core/allocations/components/AllocationOverviewTab.tsx
index 337f83f61..321330970 100644
--- a/web/src/features/core/allocations/components/AllocationOverviewTab.tsx
+++ b/web/src/features/core/allocations/components/AllocationOverviewTab.tsx
@@ -70,7 +70,9 @@ export function AllocationOverviewTab({ allocation }: AllocationOverviewTabProps
         <dd className="text-foreground before:content-[':_']">{allocation.status}</dd>
 
         <dt className="text-muted-foreground">Cluster</dt>
-        <dd className="font-mono text-foreground before:font-sans before:content-[':_']">{allocation.compute_cluster_id}</dd>
+        <dd className="text-foreground before:content-[':_']">
+          {allocation.cluster_name || allocation.compute_cluster_id}
+        </dd>
 
         <dt className="text-muted-foreground">Initial SUs</dt>
         <dd className="tabular-nums text-foreground before:content-[':_']">{formatNumber(allocation.initial_su_amount)}</dd>
@@ -82,6 +84,30 @@ export function AllocationOverviewTab({ allocation }: AllocationOverviewTabProps
         <dd className="text-foreground before:content-[':_']">{allocation.end_time}</dd>
       </dl>
 
+      {allocation.local_username ? (
+        <section className="space-y-2">
+          <h2 className="text-sm font-semibold text-foreground">Your access</h2>
+          <div className="rounded-lg border border-border bg-card p-4 text-sm">
+            <div className="flex flex-wrap items-baseline gap-x-8 gap-y-2">
+              <div>
+                <span className="text-muted-foreground">Username: </span>
+                <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
+                  {allocation.local_username}
+                </code>
+              </div>
+              {allocation.login_host ? (
+                <div>
+                  <span className="text-muted-foreground">Sign in: </span>
+                  <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
+                    ssh {allocation.local_username}@{allocation.login_host}
+                  </code>
+                </div>
+              ) : null}
+            </div>
+          </div>
+        </section>
+      ) : null}
+
       <section className="space-y-2">
         <h2 className="text-sm font-semibold text-foreground">Resources</h2>
         {resourcesQuery.isLoading ? (
diff --git a/web/src/features/core/allocations/components/AllocationsList.tsx b/web/src/features/core/allocations/components/AllocationsList.tsx
index a25567b62..d4f13bada 100644
--- a/web/src/features/core/allocations/components/AllocationsList.tsx
+++ b/web/src/features/core/allocations/components/AllocationsList.tsx
@@ -131,9 +131,11 @@ export function AllocationsList({
       key: "cluster",
       header: "Cluster",
       sortable: true,
-      sortValue: (row) => row.compute_cluster_id,
+      sortValue: (row) => row.cluster_name ?? row.compute_cluster_id,
       width: "12rem",
-      cell: (row) => <span className="break-all text-sm">{row.compute_cluster_id}</span>,
+      cell: (row) => (
+        <span className="break-all text-sm">{row.cluster_name || row.compute_cluster_id}</span>
+      ),
     },
     {
       key: "username",
diff --git a/web/src/features/core/allocations/schemas.ts b/web/src/features/core/allocations/schemas.ts
index 159b228e0..1fa94a3b2 100644
--- a/web/src/features/core/allocations/schemas.ts
+++ b/web/src/features/core/allocations/schemas.ts
@@ -44,6 +44,8 @@ export const computeAllocationSchema = z.object({
   start_time: z.string(),
   end_time: z.string(),
   local_username: z.string().nullish(),
+  cluster_name: z.string().nullish(),
+  login_host: z.string().nullish(),
 });
 export type ComputeAllocation = z.infer<typeof computeAllocationSchema>;