kopia lustrzana https://github.com/bugout-dev/moonstream
Merge branch 'main' into moonstream-api-docs-endpoint
commit
969c6a76df
|
@ -21,6 +21,8 @@ from ..settings import ETHTXPOOL_HUMBUG_CLIENT_ID
|
|||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.WARN)
|
||||
|
||||
allowed_tags = ["tag:erc721"]
|
||||
|
||||
|
||||
class BugoutEventProviderError(Exception):
|
||||
"""
|
||||
|
@ -315,14 +317,17 @@ class EthereumTXPoolProvider(BugoutEventProvider):
|
|||
]
|
||||
subscriptions_filters = []
|
||||
for address in addresses:
|
||||
subscriptions_filters.extend(
|
||||
[f"?#from_address:{address}", f"?#to_address:{address}"]
|
||||
)
|
||||
if address in allowed_tags:
|
||||
subscriptions_filters.append(address)
|
||||
else:
|
||||
subscriptions_filters.extend(
|
||||
[f"?#from_address:{address}", f"?#to_address:{address}"]
|
||||
)
|
||||
|
||||
return subscriptions_filters
|
||||
|
||||
|
||||
class NftProvider(BugoutEventProvider):
|
||||
class PublicDataProvider(BugoutEventProvider):
|
||||
def __init__(
|
||||
self,
|
||||
event_type: str,
|
||||
|
@ -359,7 +364,7 @@ Shows the top 10 addresses active on the Ethereum blockchain over the last hour
|
|||
4. Amount (in WEI) received
|
||||
|
||||
To restrict your queries to this provider, add a filter of \"type:ethereum_whalewatch\" to your query (query parameter: \"q\") on the /streams endpoint."""
|
||||
whalewatch_provider = BugoutEventProvider(
|
||||
whalewatch_provider = PublicDataProvider(
|
||||
event_type="ethereum_whalewatch",
|
||||
description=whalewatch_description,
|
||||
default_time_interval_seconds=310,
|
||||
|
@ -389,7 +394,7 @@ Currently, it summarizes the activities on the following NFT markets:
|
|||
|
||||
This provider is currently not accessible for subscription. The data from this provider is publicly
|
||||
available at the /nft endpoint."""
|
||||
nft_summary_provider = NftProvider(
|
||||
nft_summary_provider = PublicDataProvider(
|
||||
event_type="nft_summary",
|
||||
description=nft_summary_description,
|
||||
# 40 blocks per summary, 15 seconds per block + 2 seconds wiggle room.
|
||||
|
|
|
@ -8,11 +8,14 @@ from bugout.data import BugoutResource
|
|||
from moonstreamdb.models import (
|
||||
EthereumBlock,
|
||||
EthereumTransaction,
|
||||
EthereumAddress,
|
||||
EthereumLabel,
|
||||
)
|
||||
from sqlalchemy import or_, and_, text
|
||||
from sqlalchemy.orm import Session, Query
|
||||
from sqlalchemy.sql.functions import user
|
||||
|
||||
|
||||
from .. import data
|
||||
from ..stream_boundaries import validate_stream_boundary
|
||||
from ..stream_queries import StreamQuery
|
||||
|
@ -23,6 +26,7 @@ logger.setLevel(logging.WARN)
|
|||
|
||||
|
||||
event_type = "ethereum_blockchain"
|
||||
allowed_tags = ["tag:erc721"]
|
||||
|
||||
description = f"""Event provider for transactions from the Ethereum blockchain.
|
||||
|
||||
|
@ -79,6 +83,7 @@ class Filters:
|
|||
|
||||
from_addresses: List[str] = field(default_factory=list)
|
||||
to_addresses: List[str] = field(default_factory=list)
|
||||
labels: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def default_filters(subscriptions: List[BugoutResource]) -> Filters:
|
||||
|
@ -91,8 +96,11 @@ def default_filters(subscriptions: List[BugoutResource]) -> Filters:
|
|||
Optional[str], subscription.resource_data.get("address")
|
||||
)
|
||||
if subscription_address is not None:
|
||||
filters.from_addresses.append(subscription_address)
|
||||
filters.to_addresses.append(subscription_address)
|
||||
if subscription_address in allowed_tags:
|
||||
filters.labels.append(subscription_address.split(":")[1])
|
||||
else:
|
||||
filters.from_addresses.append(subscription_address)
|
||||
filters.to_addresses.append(subscription_address)
|
||||
else:
|
||||
logger.warn(
|
||||
f"Could not find subscription address for subscription with resource id: {subscription.id}"
|
||||
|
@ -157,14 +165,20 @@ def parse_filters(
|
|||
parsed_filters.from_addresses.append(address)
|
||||
parsed_filters.to_addresses.append(address)
|
||||
|
||||
if not (parsed_filters.from_addresses or parsed_filters.to_addresses):
|
||||
if not (
|
||||
parsed_filters.from_addresses
|
||||
or parsed_filters.to_addresses
|
||||
or parsed_filters.labels
|
||||
):
|
||||
return None
|
||||
|
||||
return parsed_filters
|
||||
|
||||
|
||||
def query_ethereum_transactions(
|
||||
db_session: Session, stream_boundary: data.StreamBoundary, parsed_filters: Filters
|
||||
db_session: Session,
|
||||
stream_boundary: data.StreamBoundary,
|
||||
parsed_filters: Filters,
|
||||
) -> Query:
|
||||
"""
|
||||
Builds a database query for Ethereum transactions that occurred within the window of time that
|
||||
|
@ -198,15 +212,41 @@ def query_ethereum_transactions(
|
|||
query = query.filter(EthereumBlock.timestamp <= stream_boundary.end_time)
|
||||
|
||||
# We want to take a big disjunction (OR) over ALL the filters, be they on "from" address or "to" address
|
||||
address_clauses = [
|
||||
EthereumTransaction.from_address == address
|
||||
for address in parsed_filters.from_addresses
|
||||
] + [
|
||||
EthereumTransaction.to_address == address
|
||||
for address in parsed_filters.to_addresses
|
||||
]
|
||||
if address_clauses:
|
||||
query = query.filter(or_(*address_clauses))
|
||||
address_clauses = []
|
||||
|
||||
address_clauses.extend(
|
||||
[
|
||||
EthereumTransaction.from_address == address
|
||||
for address in parsed_filters.from_addresses
|
||||
]
|
||||
+ [
|
||||
EthereumTransaction.to_address == address
|
||||
for address in parsed_filters.to_addresses
|
||||
]
|
||||
)
|
||||
|
||||
labels_clause = []
|
||||
|
||||
if parsed_filters.labels:
|
||||
label_clause = (
|
||||
db_session.query(EthereumAddress)
|
||||
.join(EthereumLabel, EthereumAddress.id == EthereumLabel.address_id)
|
||||
.filter(
|
||||
or_(
|
||||
*[
|
||||
EthereumLabel.label.contains(label)
|
||||
for label in list(set(parsed_filters.labels))
|
||||
]
|
||||
)
|
||||
)
|
||||
.exists()
|
||||
)
|
||||
labels_clause.append(label_clause)
|
||||
|
||||
subscriptions_clause = address_clauses + labels_clause
|
||||
|
||||
if subscriptions_clause:
|
||||
query = query.filter(or_(*subscriptions_clause))
|
||||
|
||||
return query
|
||||
|
||||
|
@ -353,8 +393,7 @@ def next_event(
|
|||
query_ethereum_transactions(db_session, next_stream_boundary, parsed_filters)
|
||||
.order_by(text("timestamp asc"))
|
||||
.limit(1)
|
||||
.one_or_none()
|
||||
)
|
||||
).one_or_none()
|
||||
|
||||
if maybe_ethereum_transaction is None:
|
||||
return None
|
||||
|
@ -394,9 +433,7 @@ def previous_event(
|
|||
)
|
||||
.order_by(text("timestamp desc"))
|
||||
.limit(1)
|
||||
.one_or_none()
|
||||
)
|
||||
|
||||
).one_or_none()
|
||||
if maybe_ethereum_transaction is None:
|
||||
return None
|
||||
return ethereum_transaction_event(maybe_ethereum_transaction)
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
"""
|
||||
Moonstream's /whales endpoints.
|
||||
|
||||
These endpoints provide public access to whale watch summaries. No authentication required.
|
||||
"""
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from bugout.data import BugoutResource
|
||||
|
||||
from fastapi import Depends, FastAPI, Query
|
||||
from moonstreamdb import db
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import data
|
||||
from ..providers.bugout import whalewatch_provider
|
||||
from ..settings import (
|
||||
bugout_client,
|
||||
DOCS_TARGET_PATH,
|
||||
MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
MOONSTREAM_DATA_JOURNAL_ID,
|
||||
ORIGINS,
|
||||
)
|
||||
from ..stream_queries import StreamQuery
|
||||
from ..version import MOONSTREAM_VERSION
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
tags_metadata = [
|
||||
{"name": "whales", "description": "Whales summaries"},
|
||||
]
|
||||
|
||||
app = FastAPI(
|
||||
title=f"Moonstream /whales API",
|
||||
description="User, token and password handlers.",
|
||||
version=MOONSTREAM_VERSION,
|
||||
openapi_tags=tags_metadata,
|
||||
openapi_url="/openapi.json",
|
||||
docs_url=None,
|
||||
redoc_url=f"/{DOCS_TARGET_PATH}",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.get("/", tags=["whales"], response_model=data.GetEventsResponse)
|
||||
async def stream_handler(
|
||||
start_time: int = Query(0),
|
||||
end_time: Optional[int] = Query(None),
|
||||
include_start: bool = Query(False),
|
||||
include_end: bool = Query(False),
|
||||
db_session: Session = Depends(db.yield_db_session),
|
||||
) -> data.GetEventsResponse:
|
||||
"""
|
||||
Retrieves the list of whales spotted over given stream boundary
|
||||
|
||||
- **start_time**: Timestamp. Must be provided otherwise this request will hang
|
||||
- **end_time**: Timestamp. Optional.
|
||||
- **include_start** (string): is start_time inclusive or not
|
||||
- **include_end** (string): is end_time inclusive or not
|
||||
"""
|
||||
stream_boundary = data.StreamBoundary(
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
include_start=include_start,
|
||||
include_end=include_end,
|
||||
)
|
||||
|
||||
result = whalewatch_provider.get_events(
|
||||
db_session=db_session,
|
||||
bugout_client=bugout_client,
|
||||
data_journal_id=MOONSTREAM_DATA_JOURNAL_ID,
|
||||
data_access_token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
stream_boundary=stream_boundary,
|
||||
user_subscriptions={whalewatch_provider.event_type: []},
|
||||
query=StreamQuery(subscription_types=[whalewatch_provider.event_type]),
|
||||
)
|
||||
|
||||
if result is None:
|
||||
return data.GetEventsResponse(stream_boundary=stream_boundary, events=[])
|
||||
|
||||
provider_stream_boundary, events = result
|
||||
return data.GetEventsResponse(
|
||||
stream_boundary=provider_stream_boundary, events=events
|
||||
)
|
|
@ -13,6 +13,7 @@ import (
|
|||
)
|
||||
|
||||
var MOONSTREAM_IPC_PATH = os.Getenv("MOONSTREAM_IPC_PATH")
|
||||
var MOONSTREAM_CORS_ALLOWED_ORIGINS = os.Getenv("MOONSTREAM_CORS_ALLOWED_ORIGINS")
|
||||
|
||||
type GethResponse struct {
|
||||
Result string `json:"result"`
|
||||
|
@ -26,8 +27,25 @@ type PingResponse struct {
|
|||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// Extends handler with allowed CORS policies
|
||||
func setupCorsResponse(w *http.ResponseWriter, req *http.Request) {
|
||||
for _, allowedOrigin := range strings.Split(MOONSTREAM_CORS_ALLOWED_ORIGINS, ",") {
|
||||
for _, reqOrigin := range req.Header["Origin"] {
|
||||
if reqOrigin == allowedOrigin {
|
||||
(*w).Header().Set("Access-Control-Allow-Origin", allowedOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
(*w).Header().Set("Access-Control-Allow-Methods", "GET,OPTIONS")
|
||||
}
|
||||
|
||||
func ping(w http.ResponseWriter, req *http.Request) {
|
||||
setupCorsResponse(&w, req)
|
||||
log.Printf("%s, %s, %q", req.RemoteAddr, req.Method, req.URL.String())
|
||||
if (*req).Method == "OPTIONS" {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
response := PingResponse{Status: "ok"}
|
||||
|
@ -35,7 +53,11 @@ func ping(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
func pingGeth(w http.ResponseWriter, req *http.Request) {
|
||||
setupCorsResponse(&w, req)
|
||||
log.Printf("%s, %s, %q", req.RemoteAddr, req.Method, req.URL.String())
|
||||
if (*req).Method == "OPTIONS" {
|
||||
return
|
||||
}
|
||||
|
||||
postBody, err := json.Marshal(map[string]interface{}{
|
||||
"jsonrpc": "2.0",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export MOONSTREAM_CRAWLERS_SERVER_PORT="8080"
|
||||
export MOONSTREAM_IPC_PATH=null
|
||||
export MOONSTREAM_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://moonstream.to,https://www.moonstream.to,https://alpha.moonstream.to"
|
||||
|
|
|
@ -6,12 +6,14 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var MOONSTREAM_DB_URI = os.Getenv("MOONSTREAM_DB_URI")
|
||||
var MOONSTREAM_CORS_ALLOWED_ORIGINS = os.Getenv("MOONSTREAM_CORS_ALLOWED_ORIGINS")
|
||||
|
||||
type PingResponse struct {
|
||||
Status string `json:"status"`
|
||||
|
@ -21,8 +23,36 @@ type BlockResponse struct {
|
|||
BlockNumber uint64 `json:"block_number"`
|
||||
}
|
||||
|
||||
var dbConnection *gorm.DB
|
||||
|
||||
func InitDB() *gorm.DB {
|
||||
db, err := gorm.Open(postgres.Open(MOONSTREAM_DB_URI), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Println("Database unavailable")
|
||||
return nil
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// Extends handler with allowed CORS policies
|
||||
func setupCorsResponse(w *http.ResponseWriter, req *http.Request) {
|
||||
for _, allowedOrigin := range strings.Split(MOONSTREAM_CORS_ALLOWED_ORIGINS, ",") {
|
||||
for _, reqOrigin := range req.Header["Origin"] {
|
||||
if reqOrigin == allowedOrigin {
|
||||
(*w).Header().Set("Access-Control-Allow-Origin", allowedOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
(*w).Header().Set("Access-Control-Allow-Methods", "GET,OPTIONS")
|
||||
}
|
||||
|
||||
func ping(w http.ResponseWriter, req *http.Request) {
|
||||
setupCorsResponse(&w, req)
|
||||
log.Printf("%s, %s, %q", req.RemoteAddr, req.Method, req.URL.String())
|
||||
if (*req).Method == "OPTIONS" {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
response := PingResponse{Status: "ok"}
|
||||
|
@ -30,19 +60,22 @@ func ping(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
func blockLatest(w http.ResponseWriter, req *http.Request) {
|
||||
setupCorsResponse(&w, req)
|
||||
log.Printf("%s, %s, %q", req.RemoteAddr, req.Method, req.URL.String())
|
||||
if (*req).Method == "OPTIONS" {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
var latestBlock BlockResponse
|
||||
db, err := gorm.Open(postgres.Open(MOONSTREAM_DB_URI), &gorm.Config{})
|
||||
if err != nil {
|
||||
|
||||
if dbConnection == nil {
|
||||
http.Error(w, http.StatusText(500), 500)
|
||||
return
|
||||
}
|
||||
|
||||
query := "SELECT block_number FROM ethereum_blocks ORDER BY block_number DESC LIMIT 1"
|
||||
db.Raw(query, 1).Scan(&latestBlock.BlockNumber)
|
||||
dbConnection.Raw(query, 1).Scan(&latestBlock.BlockNumber)
|
||||
|
||||
json.NewEncoder(w).Encode(latestBlock)
|
||||
}
|
||||
|
@ -54,6 +87,8 @@ func main() {
|
|||
flag.StringVar(&listenPort, "port", "8080", "Server listen port")
|
||||
flag.Parse()
|
||||
|
||||
dbConnection = InitDB()
|
||||
|
||||
address := listenAddr + ":" + listenPort
|
||||
log.Printf("Starting server at %s\n", address)
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export MOONSTREAM_DB_SERVER_PORT="8080"
|
||||
export MOONSTREAM_DB_URI="postgresql://<username>:<password>@<db_host>:<db_port>/<db_name>"
|
||||
export MOONSTREAM_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://moonstream.to,https://www.moonstream.to,https://alpha.moonstream.to"
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
import React, { useEffect, useState, useLayoutEffect } from "react";
|
||||
import { useStatus } from "../../src/core/hooks";
|
||||
import {
|
||||
Heading,
|
||||
Text,
|
||||
Flex,
|
||||
Spacer,
|
||||
Stack,
|
||||
chakra,
|
||||
useMediaQuery,
|
||||
useBreakpointValue,
|
||||
} from "@chakra-ui/react";
|
||||
import { AWS_ASSETS_PATH } from "../../src/core/constants";
|
||||
|
||||
const assets = {
|
||||
background720: `${AWS_ASSETS_PATH}/product-background-720x405.png`,
|
||||
background1920: `${AWS_ASSETS_PATH}/product-background-720x405.png`,
|
||||
background2880: `${AWS_ASSETS_PATH}/product-background-720x405.png`,
|
||||
background3840: `${AWS_ASSETS_PATH}/product-background-720x405.png`,
|
||||
};
|
||||
|
||||
const Status = () => {
|
||||
const healthyStatusText = "Available";
|
||||
const downStatusText = "Unavailable";
|
||||
const healthyStatusColor = "green.900";
|
||||
const downStatusColor = "red.600";
|
||||
|
||||
const shortTimestamp = (rawTimestamp) => {
|
||||
return rawTimestamp.replace(/^.+T/, "").replace(/\..+/, "");
|
||||
};
|
||||
|
||||
const {
|
||||
apiServerStatusCache,
|
||||
ethereumClusterServerStatusCache,
|
||||
gethStatusCache,
|
||||
crawlersStatusCache,
|
||||
dbServerStatusCache,
|
||||
latestBlockDBStatusCache,
|
||||
} = useStatus();
|
||||
|
||||
const [background, setBackground] = useState("background720");
|
||||
const [backgroundLoaded720, setBackgroundLoaded720] = useState(false);
|
||||
const [backgroundLoaded1920, setBackgroundLoaded1920] = useState(false);
|
||||
const [backgroundLoaded2880, setBackgroundLoaded2880] = useState(false);
|
||||
const [backgroundLoaded3840, setBackgroundLoaded3840] = useState(false);
|
||||
|
||||
const [
|
||||
isLargerThan720px,
|
||||
isLargerThan1920px,
|
||||
isLargerThan2880px,
|
||||
isLargerThan3840px,
|
||||
] = useMediaQuery([
|
||||
"(min-width: 720px)",
|
||||
"(min-width: 1920px)",
|
||||
"(min-width: 2880px)",
|
||||
"(min-width: 3840px)",
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
assets[
|
||||
"background720"
|
||||
] = `${AWS_ASSETS_PATH}/product-background-720x405.png`;
|
||||
assets[
|
||||
"background1920"
|
||||
] = `${AWS_ASSETS_PATH}/product-background-1920x1080.png`;
|
||||
assets[
|
||||
"background2880"
|
||||
] = `${AWS_ASSETS_PATH}/product-background-2880x1620.png`;
|
||||
assets[
|
||||
"background3840"
|
||||
] = `${AWS_ASSETS_PATH}/product-background-3840x2160.png`;
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (backgroundLoaded3840) {
|
||||
setBackground("background3840");
|
||||
} else if (backgroundLoaded2880) {
|
||||
setBackground("background2880");
|
||||
} else if (backgroundLoaded1920) {
|
||||
setBackground("background1920");
|
||||
} else {
|
||||
setBackground("background720");
|
||||
}
|
||||
}, [
|
||||
isLargerThan720px,
|
||||
isLargerThan1920px,
|
||||
isLargerThan2880px,
|
||||
isLargerThan3840px,
|
||||
backgroundLoaded720,
|
||||
backgroundLoaded1920,
|
||||
backgroundLoaded2880,
|
||||
backgroundLoaded3840,
|
||||
]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const imageLoader720 = new Image();
|
||||
imageLoader720.src = `${AWS_ASSETS_PATH}/product-background-720x405.png`;
|
||||
imageLoader720.onload = () => {
|
||||
setBackgroundLoaded720(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const imageLoader1920 = new Image();
|
||||
imageLoader1920.src = `${AWS_ASSETS_PATH}/product-background-1920x1080.png`;
|
||||
imageLoader1920.onload = () => {
|
||||
setBackgroundLoaded1920(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const imageLoader2880 = new Image();
|
||||
imageLoader2880.src = `${AWS_ASSETS_PATH}/product-background-2880x1620.png`;
|
||||
imageLoader2880.onload = () => {
|
||||
setBackgroundLoaded2880(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const imageLoader3840 = new Image();
|
||||
imageLoader3840.src = `${AWS_ASSETS_PATH}/product-background-3840x2160.png`;
|
||||
imageLoader3840.onload = () => {
|
||||
setBackgroundLoaded3840(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const margin = useBreakpointValue({
|
||||
base: "1%",
|
||||
sm: "2%",
|
||||
md: "3%",
|
||||
lg: "15%",
|
||||
xl: "20%",
|
||||
"2xl": "25%",
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex
|
||||
bgPos="bottom"
|
||||
bgColor="transparent"
|
||||
backgroundImage={`url(${assets[`${background}`]})`}
|
||||
bgSize="cover"
|
||||
// boxSize="full"
|
||||
minH="100vh"
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
pb={24}
|
||||
>
|
||||
<Stack mx={margin} my={12} maxW="1700px">
|
||||
<Heading
|
||||
as="h2"
|
||||
size="md"
|
||||
placeSelf="center"
|
||||
px={12}
|
||||
py={2}
|
||||
borderTopRadius="xl"
|
||||
>
|
||||
{`Status page`}
|
||||
</Heading>
|
||||
<chakra.span pl={2} px={12} py={2} width="400px">
|
||||
<Flex mb={3}>
|
||||
<Text>Backend server</Text>
|
||||
<Spacer />
|
||||
<Text
|
||||
color={
|
||||
!apiServerStatusCache.isLoading &&
|
||||
apiServerStatusCache?.data?.status == "ok"
|
||||
? healthyStatusColor
|
||||
: downStatusColor
|
||||
}
|
||||
>
|
||||
{!apiServerStatusCache.isLoading &&
|
||||
apiServerStatusCache?.data?.status == "ok"
|
||||
? healthyStatusText
|
||||
: downStatusText}
|
||||
</Text>
|
||||
</Flex>
|
||||
<br />
|
||||
<Flex mb={3}>
|
||||
<Text>Crawlers server</Text>
|
||||
<Spacer />
|
||||
<Text
|
||||
color={
|
||||
!ethereumClusterServerStatusCache.isLoading &&
|
||||
ethereumClusterServerStatusCache?.data?.status == "ok"
|
||||
? healthyStatusColor
|
||||
: downStatusColor
|
||||
}
|
||||
>
|
||||
{!ethereumClusterServerStatusCache.isLoading &&
|
||||
ethereumClusterServerStatusCache?.data
|
||||
? healthyStatusText
|
||||
: downStatusText}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex mb={3}>
|
||||
<Text>Latest block in Geth</Text>
|
||||
<Spacer />
|
||||
<Text>
|
||||
{!gethStatusCache.isLoading &&
|
||||
gethStatusCache?.data?.current_block
|
||||
? gethStatusCache.data.current_block
|
||||
: 0}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex mb={3}>
|
||||
<Text>Txpool latest record ts</Text>
|
||||
<Spacer />
|
||||
<Text>
|
||||
{!crawlersStatusCache.isLoading &&
|
||||
crawlersStatusCache?.data?.ethereum_txpool_timestamp
|
||||
? shortTimestamp(
|
||||
crawlersStatusCache?.data?.ethereum_txpool_timestamp
|
||||
)
|
||||
: downStatusText}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex mb={3}>
|
||||
<Text>Trending latest record ts</Text>
|
||||
<Spacer />
|
||||
<Text>
|
||||
{!crawlersStatusCache.isLoading &&
|
||||
crawlersStatusCache?.data?.ethereum_trending_timestamp
|
||||
? shortTimestamp(
|
||||
crawlersStatusCache?.data?.ethereum_trending_timestamp
|
||||
)
|
||||
: downStatusText}
|
||||
</Text>
|
||||
</Flex>
|
||||
<br />
|
||||
<Flex mb={3}>
|
||||
<Text>Database server</Text>
|
||||
<Spacer />
|
||||
<Text
|
||||
color={
|
||||
!dbServerStatusCache.isLoading &&
|
||||
dbServerStatusCache?.data?.status == "ok"
|
||||
? healthyStatusColor
|
||||
: downStatusColor
|
||||
}
|
||||
>
|
||||
{!dbServerStatusCache.isLoading &&
|
||||
dbServerStatusCache?.data?.status == "ok"
|
||||
? healthyStatusText
|
||||
: downStatusText}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex mb={3}>
|
||||
<Text>Latest block in Database</Text>
|
||||
<Spacer />
|
||||
<Text>
|
||||
{!latestBlockDBStatusCache.isLoading &&
|
||||
latestBlockDBStatusCache?.data?.block_number
|
||||
? latestBlockDBStatusCache.data.block_number
|
||||
: 0}
|
||||
</Text>
|
||||
</Flex>
|
||||
</chakra.span>
|
||||
</Stack>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Status;
|
|
@ -1,3 +1,5 @@
|
|||
export NEXT_PUBLIC_MIXPANEL_TOKEN="<YOUR MIXPANEL TOKEN HERE>"
|
||||
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="<stripe publishable key>"
|
||||
export NEXT_PUBLIC_MOONSTREAM_API_URL=http://localhost:7481
|
||||
export NEXT_PUBLIC_MOONSTREAM_API_URL="<moonstream_api_url>"
|
||||
export NEXT_PUBLIC_MOONSTREAM_ETHEREUM_CLUSTER_URL="<moonstream_crawlers_url>"
|
||||
export NEXT_PUBLIC_MOONSTREAM_DB_URL="<moonstream_db_url>"
|
||||
|
|
|
@ -14,6 +14,7 @@ export { default as useQuery } from "./useQuery";
|
|||
export { default as useResetPassword } from "./useResetPassword";
|
||||
export { default as useRouter } from "./useRouter";
|
||||
export { default as useSignUp } from "./useSignUp";
|
||||
export { default as useStatus } from "./useStatus";
|
||||
export { default as useStorage } from "./useStorage";
|
||||
export { default as useStream } from "./useStream";
|
||||
export { default as useStripe } from "./useStripe";
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { useQuery } from "react-query";
|
||||
import { queryCacheProps } from "./hookCommon";
|
||||
import { StatusService } from "../../core/services";
|
||||
|
||||
const useStatus = () => {
|
||||
const getAPIServerStatus = async () => {
|
||||
const response = await StatusService.apiServerStatus();
|
||||
return response.data;
|
||||
};
|
||||
const getEthereumClusterServerStatus = async () => {
|
||||
const response = await StatusService.ethereumClusterServerStatus();
|
||||
return response.data;
|
||||
};
|
||||
const getGethStatus = async () => {
|
||||
const response = await StatusService.gethStatus();
|
||||
return response.data;
|
||||
};
|
||||
const getCrawlersStatus = async () => {
|
||||
const response = await StatusService.crawlersStatus();
|
||||
return response.data;
|
||||
};
|
||||
const getDBServerStatus = async () => {
|
||||
const response = await StatusService.dbServerStatus();
|
||||
return response.data;
|
||||
};
|
||||
const getLatestBlockDBStatus = async () => {
|
||||
const response = await StatusService.latestBlockDBStatus();
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const apiServerStatusCache = useQuery("apiServer", getAPIServerStatus, {
|
||||
...queryCacheProps,
|
||||
});
|
||||
const ethereumClusterServerStatusCache = useQuery(
|
||||
"ethereumClusterServer",
|
||||
getEthereumClusterServerStatus,
|
||||
{
|
||||
...queryCacheProps,
|
||||
}
|
||||
);
|
||||
const gethStatusCache = useQuery("geth", getGethStatus, {
|
||||
...queryCacheProps,
|
||||
});
|
||||
const crawlersStatusCache = useQuery("crawlers", getCrawlersStatus, {
|
||||
...queryCacheProps,
|
||||
});
|
||||
const dbServerStatusCache = useQuery("dbServer", getDBServerStatus, {
|
||||
...queryCacheProps,
|
||||
});
|
||||
const latestBlockDBStatusCache = useQuery(
|
||||
"latestBlockDB",
|
||||
getLatestBlockDBStatus,
|
||||
{
|
||||
...queryCacheProps,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
apiServerStatusCache,
|
||||
ethereumClusterServerStatusCache,
|
||||
gethStatusCache,
|
||||
crawlersStatusCache,
|
||||
dbServerStatusCache,
|
||||
latestBlockDBStatusCache,
|
||||
};
|
||||
};
|
||||
|
||||
export default useStatus;
|
|
@ -6,6 +6,7 @@ import * as GroupService from "./group.service";
|
|||
import * as PreferencesService from "./preferences.service";
|
||||
import * as HumbugService from "./humbug.service";
|
||||
import * as InvitesService from "./invites.service";
|
||||
import * as StatusService from "./status.service";
|
||||
import * as SubscriptionsService from "./subscriptions.service";
|
||||
import * as StreamService from "./stream.service";
|
||||
import * as TxInfoService from "./txinfo.service";
|
||||
|
@ -18,6 +19,7 @@ export {
|
|||
PreferencesService,
|
||||
HumbugService,
|
||||
InvitesService,
|
||||
StatusService,
|
||||
SubscriptionsService,
|
||||
StreamService,
|
||||
TxInfoService,
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import { http } from "../utils";
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_MOONSTREAM_API_URL;
|
||||
const DB_URL = process.env.NEXT_PUBLIC_MOONSTREAM_DB_URL;
|
||||
const ETHEREUM_CLUSTER_URL =
|
||||
process.env.NEXT_PUBLIC_MOONSTREAM_ETHEREUM_CLUSTER_URL;
|
||||
|
||||
export const apiServerStatus = () => {
|
||||
return http({
|
||||
method: "GET",
|
||||
url: `${API_URL}/ping`,
|
||||
});
|
||||
};
|
||||
|
||||
export const ethereumClusterServerStatus = () => {
|
||||
return http({
|
||||
method: "GET",
|
||||
url: `${ETHEREUM_CLUSTER_URL}/ping`,
|
||||
});
|
||||
};
|
||||
|
||||
export const gethStatus = () => {
|
||||
return http({
|
||||
method: "GET",
|
||||
url: `${ETHEREUM_CLUSTER_URL}/status`,
|
||||
});
|
||||
};
|
||||
|
||||
export const crawlersStatus = () => {
|
||||
return http({
|
||||
method: "GET",
|
||||
url: `${API_URL}/status`,
|
||||
});
|
||||
};
|
||||
|
||||
export const dbServerStatus = () => {
|
||||
return http({
|
||||
method: "GET",
|
||||
url: `${DB_URL}/ping`,
|
||||
});
|
||||
};
|
||||
|
||||
export const latestBlockDBStatus = () => {
|
||||
return http({
|
||||
method: "GET",
|
||||
url: `${DB_URL}/block/latest`,
|
||||
});
|
||||
};
|
Ładowanie…
Reference in New Issue