Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions members/nullnet-server/src/http_server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ pub async fn serve(state: AppState) {
.route("/api/health", get(health::health))
.route("/api/stacks", get(stacks::stacks_handler))
.route("/api/services/{stack}", get(services::services_handler))
.route("/api/nodes", get(nodes::nodes_handler))
.route("/api/nodes/{stack}", get(nodes::nodes_handler))
.route("/api/pool", get(pool::pool_handler))
.route("/api/config/{stack}", get(config::config_handler))
.route("/api/graph/{stack}", get(graph::graph_handler))
.route("/api/sessions", get(sessions::list_handler))
.route("/api/sessions/{id}", delete(sessions::teardown_handler))
.route("/api/sessions/{stack}", get(sessions::list_handler))
.route(
"/api/sessions/{stack}/{id}",
delete(sessions::teardown_handler),
)
.route("/api/chains/{stack}", get(chains::chains_handler))
.route("/api/certificates", get(certificates::list_handler))
.route(
Expand Down
9 changes: 6 additions & 3 deletions members/nullnet-server/src/http_server/nodes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::AppState;
use crate::services::service_info::ServiceInfo;
use axum::extract::State;
use axum::extract::{Path, State};
use axum::response::IntoResponse;
use serde::Serialize;
use std::collections::HashMap;
Expand All @@ -18,14 +18,17 @@ struct NodeJson {
hosted_services: Vec<HostedServiceJson>,
}

pub(super) async fn nodes_handler(State(state): State<AppState>) -> impl IntoResponse {
pub(super) async fn nodes_handler(
Path(stack): Path<String>,
State(state): State<AppState>,
) -> impl IntoResponse {
let connected_ips = state.orchestrator.connected_node_ips().await;
let services = state.services.read().await;

let mut ip_services: HashMap<IpAddr, Vec<HostedServiceJson>> =
connected_ips.iter().map(|ip| (*ip, vec![])).collect();

for (stack, stack_map) in services.iter() {
if let Some(stack_map) = services.get(&stack) {
for (name, info) in stack_map {
if let ServiceInfo::Registered(reg) = info {
for replica in reg.replicas() {
Expand Down
36 changes: 24 additions & 12 deletions members/nullnet-server/src/http_server/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ struct ErrorJson {
error: &'static str,
}

pub(super) async fn list_handler(State(state): State<AppState>) -> impl IntoResponse {
pub(super) async fn list_handler(
Path(stack): Path<String>,
State(state): State<AppState>,
) -> impl IntoResponse {
let services = state.services.read().await;
let mut sessions: Vec<SessionJson> = services
.values()
.flat_map(|stack_map| stack_map.iter())
let Some(stack_map) = services.get(&stack) else {
return axum::Json(Vec::<SessionJson>::new()).into_response();
};
let mut sessions: Vec<SessionJson> = stack_map
.iter()
.flat_map(|(name, info)| {
if let ServiceInfo::Registered(reg) = info {
reg.all_clients_owned()
Expand Down Expand Up @@ -57,31 +62,38 @@ pub(super) async fn list_handler(State(state): State<AppState>) -> impl IntoResp
})
.collect();
sessions.sort_by_key(|s| s.id);
axum::Json(sessions)
axum::Json(sessions).into_response()
}

pub(super) async fn teardown_handler(
State(state): State<AppState>,
Path(id): Path<u32>,
Path((stack, id)): Path<(String, u32)>,
) -> impl IntoResponse {
let mut services = state.services.write().await;

// Sessions span every stack; locate the (stack, service, client) triple
// that owns this NET id.
let found = services.iter().find_map(|(stack, stack_map)| {
let found = {
let Some(stack_map) = services.get(&stack) else {
return (
StatusCode::NOT_FOUND,
axum::Json(ErrorJson {
error: "session not found",
}),
)
.into_response();
};
stack_map.iter().find_map(|(name, info)| {
if let ServiceInfo::Registered(reg) = info {
reg.all_clients_owned()
.into_iter()
.find(|(c, ci, _, _)| c.is_proxy().is_some() && ci.net_id() == id)
.map(|(client, _, _, _)| (stack.clone(), name.clone(), client))
.map(|(client, _, _, _)| (name.clone(), client))
} else {
None
}
})
});
};

let Some((stack, name, client)) = found else {
let Some((name, client)) = found else {
return (
StatusCode::NOT_FOUND,
axum::Json(ErrorJson {
Expand Down
2 changes: 1 addition & 1 deletion members/nullnet-server/ui/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const NAV = [

export default function Layout({ page, topbarRight, children }: Props) {
const { stack, setStack, editing, setEditing } = useStack();
const { data: sessions } = useApi<SessionJson[]>('/api/sessions', 5000);
const { data: sessions } = useApi<SessionJson[]>(`/api/sessions/${stack}`, 5000);
const { data: availableStacks } = useApi<string[]>('/api/stacks', 10000);
const [dropdownOpen, setDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export function TopologyProvider({
}) {
const { data: graph, refetch } = useApi<GraphJson>(`/api/graph/${stack}`);
const { data: services } = useApi<ServiceJson[]>(`/api/services/${stack}`, 5000);
const { data: sessions } = useApi<SessionJson[]>('/api/sessions', 5000);
const { data: sessions } = useApi<SessionJson[]>(`/api/sessions/${stack}`, 5000);
const { data: chains, refetch: refetchChains } = useApi<ChainJson[]>(`/api/chains/${stack}`);

const [uiState, dispatch] = useReducer(uiReducer, initialUIState);
Expand Down
4 changes: 2 additions & 2 deletions members/nullnet-server/ui/src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import ZoomFrame from '../components/topology/ZoomFrame';

export default function Dashboard() {
const { stack } = useStack();
const { data: sessions } = useApi<SessionJson[]>('/api/sessions', 5000);
const { data: sessions } = useApi<SessionJson[]>(`/api/sessions/${stack}`, 5000);
const { data: services } = useApi<ServiceJson[]>(`/api/services/${stack}`, 5000);
const { data: nodes } = useApi<NodeJson[]>('/api/nodes', 5000);
const { data: nodes } = useApi<NodeJson[]>(`/api/nodes/${stack}`, 5000);
const { data: pool } = useApi<PoolJson>('/api/pool', 5000);
const { data: graph } = useApi<GraphJson>(`/api/graph/${stack}`, 5000);

Expand Down
10 changes: 3 additions & 7 deletions members/nullnet-server/ui/src/pages/Nodes.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useState } from 'react';
import Layout from '../components/Layout';
import { useApi } from '../hooks/useApi';
import { useStack } from '../StackContext';
import type { NodeJson } from '../types';

export default function Nodes() {
const { data: nodes, loading } = useApi<NodeJson[]>('/api/nodes', 5000);
const { stack } = useStack();
const { data: nodes, loading } = useApi<NodeJson[]>(`/api/nodes/${stack}`, 5000);
const [selected, setSelected] = useState<string | null>(null);

const selectedNode = nodes?.find(n => n.ip === selected) ?? null;
Expand Down Expand Up @@ -56,12 +58,6 @@ export default function Nodes() {
<div className="nc-stat-k">Services</div>
<div className="nc-stat-v">{node.hosted_services.length}</div>
</div>
<div className="nc-stat">
<div className="nc-stat-k">Stacks</div>
<div className="nc-stat-v" style={{ color: 'var(--blue)' }}>
{new Set(node.hosted_services.map(s => s.stack)).size}
</div>
</div>
</div>

{node.hosted_services.length > 0 && (
Expand Down
6 changes: 4 additions & 2 deletions members/nullnet-server/ui/src/pages/Sessions.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { useState } from 'react';
import Layout from '../components/Layout';
import { useApi } from '../hooks/useApi';
import { useStack } from '../StackContext';
import type { SessionJson } from '../types';

export default function Sessions() {
const { data: sessions, loading, refetch } = useApi<SessionJson[]>('/api/sessions', 5000);
const { stack } = useStack();
const { data: sessions, loading, refetch } = useApi<SessionJson[]>(`/api/sessions/${stack}`, 5000);
const [tearing, setTearing] = useState<Set<number>>(new Set());

async function teardown(id: number) {
if (!confirm(`Force teardown session ${id}?`)) return;
setTearing(prev => new Set(prev).add(id));
try {
await fetch(`/api/sessions/${id}`, { method: 'DELETE' });
await fetch(`/api/sessions/${stack}/${id}`, { method: 'DELETE' });
refetch();
} finally {
setTearing(prev => { const next = new Set(prev); next.delete(id); return next; });
Expand Down