Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 2 additions & 2 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# Config service url
REACT_APP_CONFIG_SERVICE_URL=https://safe-config.safe.global

# Shows the Config service url selector if true
REACT_APP_SHOW_CONFIG_SERVICE_SELECTOR=true
# Client Gateway url
VITE_CLIENT_GATEWAY_URL=https://safe-client.safe.global
8 changes: 7 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"
rel="stylesheet"
/>
<title>Safe Tx Status</title>
</head>
<body>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "safe-transaction-service-status-page",
"version": "0.4.0",
"version": "0.5.0",
"private": true,
"type": "module",
"scripts": {
Expand Down
24 changes: 5 additions & 19 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { useState } from "react";
import Container from "@mui/material/Container";
import { ThemeProvider } from "@mui/material/styles";
import { CssBaseline } from "@mui/material";

import useTheme from "src/hooks/useTheme";
import Header from "src/components/header/Header";
import ConfigServiceUrlSelector from "src/components/config-service-url-selector/ConfigServiceUrlSelector";
import ChainStatusTable from "src/components/chain-status-table/ChainStatusTable";

const VITE_CONFIG_SERVICE_URL = import.meta.env.VITE_CONFIG_SERVICE_URL;
const VITE_SHOW_CONFIG_SERVICE_SELECTOR = import.meta.env
.VITE_SHOW_CONFIG_SERVICE_SELECTOR;
const VITE_CLIENT_GATEWAY_URL = import.meta.env.VITE_CLIENT_GATEWAY_URL;

function App() {
const [configServiceUrl, setConfigServiceUrl] = useState<string>(
VITE_CONFIG_SERVICE_URL || "",
);

const { theme, switchThemeMode, isDarkTheme } = useTheme();

return (
Expand All @@ -31,21 +24,14 @@ function App() {
component="main"
sx={{ marginBottom: "36px", marginTop: "42px" }}
>
{/* Config service selector */}
{showConfigServiceSelector && (
<ConfigServiceUrlSelector
configServiceUrl={configServiceUrl}
setConfigServiceUrl={setConfigServiceUrl}
/>
)}

{/* Chain status table */}
<ChainStatusTable configServiceUrl={configServiceUrl} />
<ChainStatusTable
configServiceUrl={VITE_CONFIG_SERVICE_URL}
clientGatewayUrl={VITE_CLIENT_GATEWAY_URL}
/>
</Container>
</ThemeProvider>
);
}

export default App;

const showConfigServiceSelector = VITE_SHOW_CONFIG_SERVICE_SELECTOR === "true";
8 changes: 4 additions & 4 deletions src/api/getChainStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import axios, { RawAxiosRequestConfig } from "axios";
import chain from "src/models/chain";

import chainStatus from "src/models/chainStatus";

const CHAIN_STATUS_PATHNAME = "/api/v1/about/indexing/";

async function getChainStatus(
transactionServiceBaseUrl: string,
clientGatewayUrl: string,
chain: chain,
options?: RawAxiosRequestConfig,
): Promise<chainStatus> {
const endpoint = `${transactionServiceBaseUrl}${CHAIN_STATUS_PATHNAME}`;
const endpoint = `${clientGatewayUrl}/v1/chains/${chain.chainId}/about/indexing`;

const { data } = await axios.get(endpoint, options);

Expand Down
76 changes: 75 additions & 1 deletion src/assets/safe-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 11 additions & 7 deletions src/components/chain-status-table/ChainStatusRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,23 @@ import BlockLabel from "src/components/block-label/BlockLabel";
import SyncedLabel from "src/components/synced-label/SyncedLabel";
import memoizedGetBlock from "src/utils/memoizedGetBlock";

const POLLING_TIME = 5_000; // 5 secs

function ChainStatusRow({ chain }: { chain: chain }) {
const POLLING_TIME = 30_000; // 30 secs

function ChainStatusRow({
chain,
clientGatewayUrl,
}: {
chain: chain;
clientGatewayUrl: string;
}) {
// endpoint to the chain status from the transaction service
const fetchChainStatus = useCallback(
async (signal: AbortSignal) => {
const transactionServiceUrl = chain.transactionService;

return getChainStatus(transactionServiceUrl, {
return getChainStatus(clientGatewayUrl, chain, {
signal,
});
},
[chain],
[clientGatewayUrl, chain],
);

// fetch chain status with a polling (5 secs)
Expand Down
12 changes: 10 additions & 2 deletions src/components/chain-status-table/ChainStatusTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import ChainStatusRow from "src/components/chain-status-table/ChainStatusRow";

type StatusTableProps = {
configServiceUrl: string;
clientGatewayUrl: string;
};

function ChainStatusTable({ configServiceUrl }: StatusTableProps) {
function ChainStatusTable({
configServiceUrl,
clientGatewayUrl,
}: StatusTableProps) {
// endpoint to fetch all chains from the config service
const fetchChains = useCallback(
(signal: AbortSignal) => {
Expand Down Expand Up @@ -76,7 +80,11 @@ function ChainStatusTable({ configServiceUrl }: StatusTableProps) {
<TableBody>
{!isLoading &&
chains?.map((chain) => (
<ChainStatusRow key={chain.chainId} chain={chain} />
<ChainStatusRow
key={chain.chainId}
chain={chain}
clientGatewayUrl={clientGatewayUrl}
/>
))}
</TableBody>
</Table>
Expand Down
32 changes: 2 additions & 30 deletions src/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ function Header({ switchThemeMode, isDarkTheme }: HeaderProps) {
{/* App Logo */}
<AppLogoHeader id="app-logo-header" src={safeLogo} alt="app logo" />

{/* App Title */}
<StyledAppTitle variant="h6" component="h1">
Safe Tx Service Status
</StyledAppTitle>

{/* Navigation links */}
<Box
flexGrow={1}
Expand Down Expand Up @@ -109,32 +104,9 @@ function Header({ switchThemeMode, isDarkTheme }: HeaderProps) {
export default Header;

const AppLogoHeader = styled("img")`
height: 40px;
height: 25px;
pointer-events: none;

animation: app-logo-spin infinite 20s linear;

@keyframes app-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`;

const StyledAppTitle = styled(Typography)<{
variant: string;
component: string;
}>`
font-family: monospace;
margin: 0 12px;
letter-spacing: 0.3rem;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
filter: invert(1) brightness(1);
`;

const NavList = styled("ul")`
Expand Down
64 changes: 41 additions & 23 deletions src/hooks/useApi.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useCallback, useRef } from "react";
import PollingManager from "src/utils/pollingManager";

type apiCallParam<T> = (signal: AbortSignal) => Promise<T>;

Expand All @@ -7,43 +8,60 @@ type useApiHookReturnValue<T> = {
data?: T;
};

const pollingManager = new PollingManager();

function useApi<T>(
apiCall: apiCallParam<T>,
pollingTime?: number,
): useApiHookReturnValue<T> {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [data, setData] = useState<T>();
const taskIdRef = useRef<string | undefined>(undefined);

const stableApiCall = useCallback(apiCall, [apiCall]);

useEffect(() => {
const abortController = new AbortController();

async function performApiCall() {
try {
setIsLoading(true);
const data = await apiCall(abortController.signal);
setData(data);
} catch {
setData(undefined);
} finally {
setIsLoading(false);
}
}
const taskId = `api-task-${Date.now()}-${Math.random()}`;
taskIdRef.current = taskId;

let intervalId: NodeJS.Timeout;
const onSuccess = (result: T) => {
setData(result);
setIsLoading(false);
};

const onError = (error: unknown) => {
console.warn("API call failed:", error);
setData(undefined);
setIsLoading(false);
};

if (pollingTime) {
intervalId = setInterval(() => {
performApiCall();
}, pollingTime);
}
// Use polling manager for rate-limited polling
pollingManager.addTask(
taskId,
stableApiCall,
onSuccess,
onError,
pollingTime,
);
} else {
// For non-polling requests, execute immediately
setIsLoading(true);
const abortController = new AbortController();

performApiCall();
stableApiCall(abortController.signal).then(onSuccess).catch(onError);

return () => {
abortController.abort();
};
}

return () => {
abortController.abort();
clearInterval(intervalId);
if (taskIdRef.current) {
pollingManager.removeTask(taskIdRef.current);
}
};
}, [apiCall, pollingTime]);
}, [stableApiCall, pollingTime]);

return {
isLoading,
Expand Down
5 changes: 4 additions & 1 deletion src/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ export const LIGHT_THEME: PaletteMode = "light";
const themeOptions: ThemeOptions = {
palette: {
primary: {
main: "#2E3B55",
main: "#121212",
},
mode: DARK_THEME,
},
typography: {
fontFamily: '"DM Sans", sans-serif',
},
};

const getTheme = (themeMode: PaletteMode): Theme => {
Expand Down
Loading