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
6 changes: 3 additions & 3 deletions example.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

# Config service url
REACT_APP_CONFIG_SERVICE_URL=https://safe-config.safe.global
VITE_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
3 changes: 2 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<!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="stylesheet" href="/fonts/fonts.css" />
<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
Binary file added public/fonts/DMSans600.woff2
Binary file not shown.
Binary file added public/fonts/DMSans700.woff2
Binary file not shown.
Binary file added public/fonts/DMSansRegular.woff2
Binary file not shown.
23 changes: 23 additions & 0 deletions public/fonts/fonts.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@font-face {
font-family: "DM Sans";
font-display: swap;
font-weight: 400;
/** check that the font is loaded on the website. IDEs fail to find the file */
src: url("/fonts/DMSansRegular.woff2") format("woff2");
}

@font-face {
font-family: "DM Sans";
font-display: swap;
font-weight: bold;
/** check that the font is loaded on the website. IDEs fail to find the file */
src: url("/fonts/DMSans700.woff2") format("woff2");
}

@font-face {
font-family: "DM Sans";
font-display: swap;
font-weight: 500;
/** check that the font is loaded on the website. IDEs fail to find the file */
src: url("/fonts/DMSans600.woff2") format("woff2");
}
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
72 changes: 71 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 = 60_000; // 60 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