Skip to content

Commit 1bc5208

Browse files
authored
chore(api-web): Add VPC peerings table to VPC detail page (#3077)
Adding a table of peer VPCs on the VPC detail page of web UI. The table shows each peering ID and links tot he peer VPC's own detail page. When VPC peerings found: <img width="1186" height="225" alt="Screenshot 2026-07-01 at 4 19 26 PM" src="https://github.com/user-attachments/assets/c34a3cdf-8578-4ade-948e-2d8860346c05" /> When no VPC peerings found: <img width="1214" height="116" alt="Screenshot 2026-07-01 at 4 19 48 PM" src="https://github.com/user-attachments/assets/d32bc24c-7eff-4718-960e-e0a4813be080" /> ## Related issues ## Type of Change <!-- Check one that best describes this PR --> - [x] **Add** - New feature or capability - [ ] **Change** - Changes in existing functionality - [ ] **Fix** - Bug fixes - [ ] **Remove** - Removed features or deprecated functionality - [ ] **Internal** - Internal changes (refactoring, tests, docs, etc.) ## Breaking Changes <!-- If checked, describe the breaking changes and migration steps --> <!-- Breaking changes are not generally permitted, please discuss on a GitHub discussion or with the development team if you believe you need to break a backward compatibility guarantee --> - [ ] **This PR contains breaking changes** ## Testing <!-- How was this tested? Check all that apply --> - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [x] Manual testing performed - [ ] No testing required (docs, internal refactor, etc.) ## Additional Notes
1 parent b1a3418 commit 1bc5208

2 files changed

Lines changed: 96 additions & 1 deletion

File tree

crates/api-web/src/vpc.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ async fn fetch_vpcs(api: Arc<Api>) -> Result<Vec<forgerpc::Vpc>, tonic::Status>
174174
Ok(vpcs)
175175
}
176176

177+
struct VpcPeeringRow {
178+
id: String,
179+
peer_vpc_id: String,
180+
}
181+
177182
#[derive(Template)]
178183
#[template(path = "vpc_detail.html")]
179184
struct VpcDetail {
@@ -184,6 +189,7 @@ struct VpcDetail {
184189
routing_profile_type: String,
185190
vni: String,
186191
metadata_detail: super::MetadataDetail,
192+
peerings: Vec<VpcPeeringRow>,
187193
}
188194

189195
impl From<forgerpc::Vpc> for VpcDetail {
@@ -205,6 +211,7 @@ impl From<forgerpc::Vpc> for VpcDetail {
205211
metadata: vpc.metadata.clone().unwrap_or_default(),
206212
metadata_version: vpc.version,
207213
},
214+
peerings: Vec::new(),
208215
}
209216
}
210217
}
@@ -259,9 +266,81 @@ pub async fn detail(
259266
return (StatusCode::OK, Json(vpc)).into_response();
260267
}
261268

262-
let tmpl: VpcDetail = vpc.into();
269+
let mut tmpl: VpcDetail = vpc.into();
270+
271+
let peerings = fetch_vpc_peerings(state, vpc_id_string).await;
272+
tmpl.peerings = peerings;
273+
263274
(StatusCode::OK, Html(tmpl.render().unwrap())).into_response()
264275
}
265276

277+
async fn fetch_vpc_peerings(state: Arc<Api>, vpc_id_string: String) -> Vec<VpcPeeringRow> {
278+
let Ok(vpc_id) = vpc_id_string.parse() else {
279+
return Vec::new();
280+
};
281+
282+
// Get the peering IDs for the VPC
283+
let peering_ids = match state
284+
.find_vpc_peering_ids(tonic::Request::new(forgerpc::VpcPeeringSearchFilter {
285+
vpc_id: Some(vpc_id),
286+
}))
287+
.await
288+
.map(|response| response.into_inner())
289+
{
290+
Ok(id_list) => id_list.vpc_peering_ids,
291+
Err(err) => {
292+
tracing::error!(%err, "find_vpc_peering_ids");
293+
return Vec::new();
294+
}
295+
};
296+
297+
// Short circuit if there are no peerings
298+
if peering_ids.is_empty() {
299+
return Vec::new();
300+
}
301+
302+
// Get the peerings by IDs
303+
let peerings = match state
304+
.find_vpc_peerings_by_ids(tonic::Request::new(forgerpc::VpcPeeringsByIdsRequest {
305+
vpc_peering_ids: peering_ids,
306+
}))
307+
.await
308+
.map(|response| response.into_inner())
309+
{
310+
Ok(peerings) => peerings.vpc_peerings,
311+
Err(err) => {
312+
tracing::error!(%err, "find_vpc_peerings_by_ids");
313+
Vec::new()
314+
}
315+
};
316+
317+
peerings
318+
.into_iter()
319+
.map(|p| {
320+
let vpc_id_str = p
321+
.vpc_id
322+
.as_ref()
323+
.map(|id| id.to_string())
324+
.unwrap_or_default();
325+
let peer_vpc_id_str = p
326+
.peer_vpc_id
327+
.as_ref()
328+
.map(|id| id.to_string())
329+
.unwrap_or_default();
330+
// The search filter may return peerings where the current VPC is on
331+
// either side; resolve to whichever side is not the current VPC.
332+
let peer_vpc_id = if vpc_id_str == vpc_id_string {
333+
peer_vpc_id_str
334+
} else {
335+
vpc_id_str
336+
};
337+
VpcPeeringRow {
338+
id: p.id.map(|id| id.to_string()).unwrap_or_default(),
339+
peer_vpc_id,
340+
}
341+
})
342+
.collect()
343+
}
344+
266345
impl super::Base for VpcShow {}
267346
impl super::Base for VpcDetail {}

crates/api-web/templates/vpc_detail.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,20 @@ <h1>VPC Detail</h1>
1818
<h2>Metadata</h2>
1919
{{ metadata_detail|safe }}
2020

21+
<h2>VPC Peerings</h2>
22+
{% if peerings.is_empty() %}
23+
<p>No VPC peerings found.</p>
24+
{% else %}
25+
<table class="sortable overview">
26+
<thead>
27+
<tr><th>Peering ID</th><th>Peer VPC</th></tr>
28+
</thead>
29+
<tbody>
30+
{% for peering in peerings %}
31+
<tr><td>{{ peering.id }}</td><td><a href="/admin/vpc/{{ peering.peer_vpc_id }}">{{ peering.peer_vpc_id }}</a></td></tr>
32+
{% endfor %}
33+
</tbody>
34+
</table>
35+
{% endif %}
36+
2137
{% endblock %}

0 commit comments

Comments
 (0)