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>;