Skip to content

Commit 1126838

Browse files
Merge pull request #228 from canopy-network/issue-#212
closes Issue #212
2 parents e001bb4 + 46b7e43 commit 1126838

8 files changed

Lines changed: 209 additions & 69 deletions

File tree

cmd/rpc/web/explorer/components/cards.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ function getCardSubHeader(props, consensusDuration, idx) {
9494
return "blk size: " + convertBytes(v.results[0].meta.size);
9595
case 3:
9696
if (!props.canopyCommittee.results) {
97-
return 0 + " unique vals";
97+
return 0 + " vals";
9898
}
99-
return props.canopyCommittee.results.length + " unique vals";
99+
return props.canopyCommittee.results.length + " vals";
100100
}
101101
}
102102

cmd/rpc/web/explorer/components/modal.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,14 @@ function convertTabData(state, v, tab) {
9595
return v;
9696
}
9797
} else if ("validator" in v && !state.modalState.accOnly) {
98-
return cpyObj(v.validator);
98+
let validator = cpyObj(v.validator);
99+
if (validator.committees && Array.isArray(validator.committees)) {
100+
validator.committees = validator.committees.join(",");
101+
}
102+
if (validator.stakedAmount) {
103+
validator.stakedAmount = toCNPY(validator.stakedAmount);
104+
}
105+
return validator;
99106
} else if ("account" in v) {
100107
let txs = v.sent_transactions.results.length > 0 ? v.sent_transactions.results : v.rec_transactions.results;
101108
switch (tab) {

cmd/rpc/web/explorer/components/table.jsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
function convertValue(k, v, openModal) {
1818
if (k === "Id" || k === "Data") return v;
1919
if (k === "publicKey") return <Truncate text={v} />;
20+
if (k === "netAddress") return <span className="net-address">{v}</span>;
2021
if (isHex(v) || k === "height") {
2122
const content = isNumber(v) ? v : <Truncate text={v} />;
2223
return (
@@ -80,7 +81,7 @@ function convertBlock(v) {
8081
function convertValidator(v) {
8182
let value = Object.assign({}, v);
8283
value.stakedAmount = toCNPY(value.stakedAmount);
83-
value.committees = value.committees.toString();
84+
value.committees = value.committees.join(",");
8485
return value;
8586
}
8687

@@ -182,7 +183,7 @@ function getTableBody(v) {
182183

183184
// DTable() renders the main data table with sorting, filtering, and pagination
184185
export default function DTable(props) {
185-
const { filterText, sortColumn, sortDirection, category, committee, tableData } = props.state;
186+
const { filterText, sortColumn, sortDirection, category, committee, tableData, tableLoading } = props.state;
186187
const sortedData = sortData(filterData(getTableBody(tableData), filterText), sortColumn, sortDirection);
187188
return (
188189
<div className="data-table">
@@ -205,18 +206,20 @@ export default function DTable(props) {
205206
<h5 className="data-table-head">{getHeader(tableData)}</h5>
206207
</div>
207208

208-
<Table responsive bordered hover size="sm" className="table">
209+
<Table responsive bordered hover size="sm" className="table" style={{ opacity: tableLoading ? 0.6 : 1, transition: 'opacity 0.2s' }}>
209210
<thead>
210211
<tr>
211212
{Object.keys(getTableBody(tableData)[0]).map((s, i) => (
212213
<th
213214
key={i}
214215
className="table-head"
215216
onClick={() => {
216-
const direction = sortColumn === s && sortDirection === "asc" ? "desc" : "asc";
217-
props.setState({ ...props.state, sortColumn: s, sortDirection: direction });
217+
if (!tableLoading) {
218+
const direction = sortColumn === s && sortDirection === "asc" ? "desc" : "asc";
219+
props.setState({ ...props.state, sortColumn: s, sortDirection: direction });
220+
}
218221
}}
219-
style={{ cursor: "pointer" }}
222+
style={{ cursor: tableLoading ? "wait" : "pointer" }}
220223
>
221224
{upperCaseAndRepUnderscore(s)}
222225
{sortColumn === s && (sortDirection === "asc" ? " ↑" : " ↓")}
@@ -228,7 +231,7 @@ export default function DTable(props) {
228231
{sortedData.map((val, idx) => (
229232
<tr key={idx}>
230233
{Object.keys(val).map((k, i) => (
231-
<td key={i} className={k === 'Id' ? 'large-table-col' : 'table-col'}>
234+
<td key={i} className={k === 'Id' ? 'large-table-col' : k === 'netAddress' ? 'net-address-col' : 'table-col'}>
232235
{convertValue(k, val[k], props.openModal)}
233236
</td>
234237
))}

cmd/rpc/web/explorer/pages/index.js

Lines changed: 116 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import Sidebar from "@/components/sidebar";
33
import DTable from "@/components/table";
44
import DetailModal from "@/components/modal";
55
import Cards from "@/components/cards";
6-
import { useEffect, useState } from "react";
6+
import { useEffect, useState, useRef } from "react";
77
import { Spinner, Toast, ToastContainer } from "react-bootstrap";
88
import { getCardData, getTableData, getModalData, Config } from "@/components/api";
99

1010
export default function Home() {
1111
const [state, setState] = useState({
1212
loading: true,
13+
tableLoading: false,
1314
cardData: {},
1415
category: 0,
1516
tablePage: 0,
@@ -31,8 +32,15 @@ export default function Home() {
3132
},
3233
});
3334

34-
function getCardAndTableData(setLoading) {
35-
Promise.allSettled([getTableData(state.tablePage, state.category, state.committee), getCardData(), Config()]).then(
35+
const autoRefreshingRef = useRef(false);
36+
37+
const stateRef = useRef(state);
38+
const lastUserActionRef = useRef(0);
39+
const autoRefreshVersionRef = useRef(0);
40+
stateRef.current = state;
41+
42+
function getCardAndTableData(setLoading, currentState = state) {
43+
Promise.allSettled([getTableData(currentState.tablePage, currentState.category, currentState.committee), getCardData(), Config()]).then(
3644
(values) => {
3745
let settledValues = [];
3846
for (const v of values) {
@@ -53,21 +61,44 @@ export default function Home() {
5361
settledValues[2].precommitVoteTimeoutMS +
5462
settledValues[2].commitTimeoutMS;
5563

56-
if (setLoading) {
57-
return setState({
58-
...state,
59-
loading: false,
60-
tableData: settledValues[0],
61-
cardData: settledValues[1],
62-
consensusDuration: consensusDuration,
63-
});
64-
}
65-
return setState({
66-
...state,
64+
setState(prevState => ({
65+
...prevState,
66+
loading: setLoading ? false : prevState.loading,
6767
tableData: settledValues[0],
6868
cardData: settledValues[1],
6969
consensusDuration: consensusDuration,
70-
});
70+
}));
71+
},
72+
);
73+
}
74+
75+
function getCardDataOnly() {
76+
Promise.allSettled([getCardData(), Config()]).then(
77+
(values) => {
78+
let settledValues = [];
79+
for (const v of values) {
80+
if (v.status === "rejected") {
81+
settledValues.push({});
82+
continue;
83+
}
84+
settledValues.push(v.value);
85+
}
86+
87+
const consensusDuration =
88+
settledValues[1].newHeightTimeoutMS +
89+
settledValues[1].electionTimeoutMS +
90+
settledValues[1].electionVoteTimeoutMS +
91+
settledValues[1].proposeTimeoutMS +
92+
settledValues[1].proposeVoteTimeoutMS +
93+
settledValues[1].precommitTimeoutMS +
94+
settledValues[1].precommitVoteTimeoutMS +
95+
settledValues[1].commitTimeoutMS;
96+
97+
setState(prevState => ({
98+
...prevState,
99+
cardData: settledValues[0],
100+
consensusDuration: consensusDuration,
101+
}));
71102
},
72103
);
73104
}
@@ -90,21 +121,84 @@ export default function Home() {
90121
if (committee == null) {
91122
committee = state.committee;
92123
}
93-
setState({
94-
...state,
124+
125+
// Mark user action timestamp and invalidate auto-refresh promises
126+
lastUserActionRef.current = Date.now();
127+
autoRefreshVersionRef.current += 1;
128+
autoRefreshingRef.current = false; // Cancel any ongoing auto-refresh
129+
130+
// Set table loading state and update page/category immediately
131+
setState(prevState => ({
132+
...prevState,
95133
committee: committee,
96134
category: category,
97135
tablePage: page,
98-
tableData: await getTableData(page, category, committee),
99-
});
136+
tableLoading: true,
137+
}));
138+
139+
try {
140+
const tableData = await getTableData(page, category, committee);
141+
setState(prevState => ({
142+
...prevState,
143+
tableData: tableData,
144+
tableLoading: false,
145+
}));
146+
} catch (error) {
147+
console.error('Error loading table data:', error);
148+
setState(prevState => ({
149+
...prevState,
150+
tableLoading: false,
151+
}));
152+
}
153+
}
154+
155+
async function refreshCurrentTable() {
156+
const currentState = stateRef.current;
157+
if (currentState.tableLoading || autoRefreshingRef.current) return; // Prevent conflicts
158+
159+
// Capture version and current state to detect if user navigated during this auto-refresh
160+
const refreshVersion = autoRefreshVersionRef.current;
161+
const { tablePage, category, committee } = currentState;
162+
163+
// Set invisible auto-refresh flag (doesn't affect UI)
164+
autoRefreshingRef.current = true;
165+
166+
try {
167+
const tableData = await getTableData(tablePage, category, committee);
168+
169+
// Only update state if no user navigation happened during this request
170+
if (autoRefreshVersionRef.current === refreshVersion) {
171+
setState(prevState => ({
172+
...prevState,
173+
tableData: tableData,
174+
}));
175+
}
176+
} catch (error) {
177+
console.error('Error refreshing table data:', error);
178+
} finally {
179+
// Always clear auto-refresh flag
180+
autoRefreshingRef.current = false;
181+
}
100182
}
101183

102184
useEffect(() => {
103185
const interval = setInterval(() => {
104-
getCardAndTableData(false);
186+
const currentState = stateRef.current;
187+
const timeSinceUserAction = Date.now() - lastUserActionRef.current;
188+
189+
// Auto-refresh cards always
190+
getCardDataOnly();
191+
192+
// Auto-refresh table data only if:
193+
// 1. Not currently loading from user interaction
194+
// 2. Not already auto-refreshing
195+
// 3. At least 2 seconds since last user action (prevents interference)
196+
if (!currentState.tableLoading && !autoRefreshingRef.current && timeSinceUserAction > 2000) {
197+
refreshCurrentTable();
198+
}
105199
}, 4000);
106200
return () => clearInterval(interval);
107-
});
201+
}, []);
108202
if (state.loading || !state.cardData.blocks) {
109203
getCardAndTableData(true);
110204
return (
@@ -120,7 +214,7 @@ export default function Home() {
120214
</>
121215
);
122216
} else {
123-
const props = { state, setState, openModal, selectTable };
217+
const props = { state, setState, openModal, selectTable, refreshCurrentTable };
124218
const onToastClose = () => setState({ ...state, showToast: false });
125219
return (
126220
<>

cmd/rpc/web/explorer/styles/globals.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,15 @@ a:active {
266266
letter-spacing: 1.5px;
267267
padding-top: 10px;
268268
font-family: var(--font-heading);
269+
overflow: hidden;
270+
text-overflow: ellipsis;
271+
white-space: nowrap;
269272
}
270273

271274
.card-body {
272275
text-align: left;
273276
cursor: pointer;
277+
overflow: hidden;
274278
}
275279

276280
h5 {
@@ -281,22 +285,35 @@ h5 {
281285

282286
.card-info-2 {
283287
font-size: 14px;
288+
overflow: hidden;
289+
text-overflow: ellipsis;
290+
white-space: nowrap;
284291
}
285292

286293
.card-info-3 {
287294
float: right;
288295
font-size: 10px;
289296
padding-top: 5px;
297+
overflow: hidden;
298+
text-overflow: ellipsis;
299+
white-space: nowrap;
300+
max-width: 50%;
290301
}
291302

292303
.card-info-4 {
293304
font-size: 9px;
294305
color: #222222;
295306
font-weight: 600;
307+
overflow: hidden;
308+
text-overflow: ellipsis;
309+
white-space: nowrap;
296310
}
297311

298312
.card-footer {
299313
font-size: 12px;
314+
overflow: hidden;
315+
text-overflow: ellipsis;
316+
white-space: nowrap;
300317
}
301318

302319
.modal-body {
@@ -428,6 +445,21 @@ td, th {
428445
padding-bottom: 15px !important;
429446
}
430447

448+
.net-address-col {
449+
min-width: 250px !important;
450+
max-width: 500px !important;
451+
font-size: 13px !important;
452+
padding-left: 10px !important;
453+
padding-top: 15px !important;
454+
padding-bottom: 15px !important;
455+
}
456+
457+
.net-address {
458+
white-space: nowrap !important;
459+
overflow: hidden !important;
460+
text-overflow: ellipsis !important;
461+
}
462+
431463
.table-head {
432464
padding-left: 10px !important;
433465
padding-top: 15px !important;

0 commit comments

Comments
 (0)