Skip to content

Commit 441f443

Browse files
committed
VDisk list page added
1 parent afde88d commit 441f443

File tree

16 files changed

+382
-47
lines changed

16 files changed

+382
-47
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ Bob Management GUI changelog
1717
- Node list page, backend (#19)
1818
- Home page, frontend (#22)
1919
- Node list page, frontend (#23)
20+
- VDisk list page, backend (#20)

backend/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ impl Modify for SecurityAddon {
4545
services::api::raw_configuration_by_node,
4646
services::api::get_node_info,
4747
services::api::get_nodes_list,
48+
services::api::get_vdisk_info,
49+
services::api::get_vdisks_list,
4850
),
4951
components(
5052
schemas(models::shared::Credentials, models::shared::Hostname, models::shared::BobConnectionData,

backend/src/models/api.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,8 @@ pub enum DiskProblem {
5151
#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))]
5252
#[tsync]
5353
pub enum DiskStatus {
54-
#[serde(rename = "good")]
5554
Good,
56-
#[serde(rename = "bad")]
5755
Bad { problems: Vec<DiskProblem> },
58-
#[serde(rename = "offline")]
5956
Offline,
6057
}
6158

@@ -82,7 +79,6 @@ impl DiskStatus {
8279

8380
/// Defines disk status names
8481
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Hash, EnumIter)]
85-
#[serde(rename_all = "camelCase")]
8682
#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))]
8783
#[tsync]
8884
pub enum DiskStatusName {
@@ -190,11 +186,8 @@ impl NodeProblem {
190186
#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))]
191187
#[tsync]
192188
pub enum NodeStatus {
193-
#[serde(rename = "good")]
194189
Good,
195-
#[serde(rename = "bad")]
196190
Bad { problems: Vec<NodeProblem> },
197-
#[serde(rename = "offline")]
198191
Offline,
199192
}
200193

@@ -227,7 +220,6 @@ impl TypedMetrics {
227220

228221
/// Defines node status names
229222
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Hash, EnumIter)]
230-
#[serde(rename_all = "camelCase")]
231223
#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))]
232224
#[tsync]
233225
pub enum NodeStatusName {
@@ -273,9 +265,7 @@ pub enum ReplicaProblem {
273265
#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))]
274266
#[tsync]
275267
pub enum ReplicaStatus {
276-
#[serde(rename = "good")]
277268
Good,
278-
#[serde(rename = "offline")]
279269
Offline { problems: Vec<ReplicaProblem> },
280270
}
281271

@@ -330,6 +320,7 @@ impl Add for SpaceInfo {
330320
/// Virtual disk Component
331321
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
332322
#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))]
323+
#[tsync]
333324
pub struct VDisk {
334325
pub id: u64,
335326

@@ -347,17 +338,14 @@ pub struct VDisk {
347338
/// Variants - Virtual Disk status
348339
/// status == 'bad' when at least one of its replicas has problems
349340
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
350-
#[serde(tag = "status")]
341+
// #[serde(tag = "status")]
351342
#[cfg_attr(all(feature = "swagger", debug_assertions), derive(ToSchema))]
352343
#[tsync]
353344
// #[cfg_attr(all(feature = "swagger", debug_assertions),
354345
// schema(example = json!({"status": "good"})))]
355346
pub enum VDiskStatus {
356-
#[serde(rename = "good")]
357347
Good,
358-
#[serde(rename = "bad")]
359348
Bad,
360-
#[serde(rename = "offline")]
361349
Offline,
362350
}
363351

backend/src/services/api.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use super::prelude::*;
1+
use super::{auth::HttpClient, prelude::*};
2+
3+
// TODO: For methods, that requires information from all nodes (/disks/count, /nodes/rps, etc.),
4+
// think of better method of returning info
5+
// another thread that constantly updates info in period and cache the results?
26

37
// TODO: For methods, that requires information from all nodes (/disks/count, /nodes/rps, etc.),
48
// think of better method of returning info
@@ -458,3 +462,27 @@ pub async fn raw_configuration_by_node(
458462
.await?,
459463
))
460464
}
465+
466+
async fn get_client_by_node(
467+
client: &HttpBobClient,
468+
node_name: NodeName,
469+
) -> AxumResult<Arc<HttpClient>> {
470+
let nodes = fetch_nodes(client.api_main()).await?;
471+
472+
let node = nodes
473+
.iter()
474+
.find(|node| node.name == node_name)
475+
.ok_or_else(|| {
476+
tracing::error!("Couldn't find specified node");
477+
APIError::RequestFailed
478+
})?;
479+
480+
client
481+
.cluster_with_addr()
482+
.get(&node.name)
483+
.ok_or_else(|| {
484+
tracing::error!("Couldn't find specified node");
485+
APIError::RequestFailed.into()
486+
})
487+
.cloned()
488+
}

backend/src/services/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ use api::{
3232
use auth::{login, logout, require_auth, AuthState, BobUser, HttpBobClient, InMemorySessionStore};
3333
use prelude::*;
3434

35+
use self::api::{get_vdisk_info, get_vdisks_list};
36+
3537
type BobAuthState = AuthState<
3638
BobUser,
3739
Uuid,
@@ -54,6 +56,8 @@ pub fn api_router_v1(auth_state: BobAuthState) -> Result<Router<BobAuthState>, R
5456
.api_route("/nodes/space", &Method::GET, get_space)
5557
.api_route("/nodes/list", &Method::GET, get_nodes_list)
5658
.api_route("/nodes/:node_name", &Method::GET, get_node_info)
59+
.api_route("/vdisks/list", &Method::GET, get_vdisks_list)
60+
.api_route("/vdisks/:vdisk_id", &Method::GET, get_vdisk_info)
5761
.api_route(
5862
"/nodes/:node_name/metrics",
5963
&Method::GET,
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Context } from '@appTypes/context.ts';
2+
import defaultTheme from '@layouts/DefaultTheme.ts';
3+
import { Box, ThemeProvider } from '@mui/system';
4+
import { useStore } from '@nanostores/react';
5+
import axios from 'axios';
6+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
7+
8+
import FetchingBackdrop from '../backdrop/backdrop.tsx';
9+
import VDiskTable from '../VDiskTable/VDiskTable.tsx';
10+
11+
const stubVDisk: VDisk = {
12+
id: 0,
13+
status: 'Offline',
14+
partition_count: 0,
15+
replicas: [],
16+
};
17+
18+
axios.defaults.withCredentials = true;
19+
20+
const VDiskPage = () => {
21+
const [vdisks, setVdisks] = useState<VDisk[]>([]);
22+
const [vdiskList, setVdiskList] = useState<DTOVDisk[]>([]);
23+
const [isPageLoaded, setIsPageLoaded] = useState(false);
24+
const context = useStore(Context);
25+
26+
const fetchVdiskList = useMemo(
27+
() => async () => {
28+
try {
29+
const [res] = await Promise.all([axios.get<DTOVDisk[]>('/api/v1/vdisks/list')]);
30+
setVdisks(
31+
res.data
32+
.map((dtoVdisk: DTOVDisk) => {
33+
return {
34+
...stubVDisk,
35+
id: dtoVdisk.id,
36+
} as VDisk;
37+
})
38+
.sort((a, b) => (a.id < b.id ? -1 : 1)),
39+
);
40+
setVdiskList(res.data);
41+
} catch (err) {
42+
console.log(err);
43+
}
44+
},
45+
[],
46+
);
47+
48+
const fetchVdisk = useCallback(
49+
(vdisk: number) => async () => {
50+
try {
51+
const [res] = await Promise.all([axios.get<VDisk>('/api/v1/vdisks/' + vdisk)]);
52+
return res.data;
53+
} catch (err) {
54+
console.log(err);
55+
}
56+
},
57+
[],
58+
);
59+
useEffect(() => {
60+
fetchVdiskList();
61+
}, [fetchVdiskList]);
62+
63+
useEffect(() => {
64+
const fetchNodes = async () => {
65+
const res = (
66+
await Promise.all(
67+
vdiskList.map(async (vdisk) => {
68+
return fetchVdisk(vdisk.id)()
69+
.catch(console.error)
70+
.then((resultVdisk) => resultVdisk);
71+
}),
72+
)
73+
).filter((vdisk): vdisk is VDisk => {
74+
return typeof vdisk !== undefined;
75+
});
76+
setVdisks(res.concat(vdisks.filter((item) => !res.find((n) => (n?.id || '') == item.id))));
77+
};
78+
if (!isPageLoaded && vdiskList.length !== 0) {
79+
fetchNodes();
80+
setIsPageLoaded(true);
81+
}
82+
const interval = setInterval(() => {
83+
fetchNodes();
84+
}, context.refreshTime * 1000);
85+
86+
return () => clearInterval(interval);
87+
}, [isPageLoaded, fetchVdisk, context.enabled, context.refreshTime, vdiskList, vdisks]);
88+
89+
if (!isPageLoaded) {
90+
return <FetchingBackdrop />;
91+
}
92+
return (
93+
<ThemeProvider theme={defaultTheme}>
94+
<Box
95+
sx={{
96+
marginLeft: '52px',
97+
marginRight: '52px',
98+
marginTop: '38px',
99+
'&:hover': {
100+
color: '#282A2F',
101+
},
102+
height: '820px',
103+
backgroundColor: '#1F2125',
104+
borderColor: '#2E2E33',
105+
border: '1',
106+
}}
107+
>
108+
<VDiskTable vdisks={vdisks} />
109+
</Box>
110+
</ThemeProvider>
111+
);
112+
};
113+
114+
export default VDiskPage;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.greendot {
2+
height: 16px;
3+
width: 16px;
4+
background-color: #34b663;
5+
border-radius: 50%;
6+
display: inline-block;
7+
}
8+
9+
.reddot {
10+
height: 16px;
11+
width: 16px;
12+
background-color: #c3234b;
13+
border-radius: 50%;
14+
display: inline-block;
15+
}
16+
17+
.graydot {
18+
height: 16px;
19+
width: 16px;
20+
background-color: #7b817e;
21+
border-radius: 50%;
22+
display: inline-block;
23+
}
24+
25+
.greyHeader {
26+
background-color: #35373c;
27+
}

0 commit comments

Comments
 (0)