Merge branch 'main' into crawlers-access-id

pull/567/head
kompotkot 2022-05-16 11:20:09 +00:00
commit a25e149e1d
75 zmienionych plików z 2237 dodań i 1331 usunięć

Wyświetl plik

@ -6,6 +6,7 @@ on:
- "main" - "main"
paths: paths:
- "backend/**" - "backend/**"
- "!backend/deploy/**"
jobs: jobs:
build: build:

Wyświetl plik

@ -6,6 +6,7 @@ on:
- "main" - "main"
paths: paths:
- "crawlers/**" - "crawlers/**"
- "!crawlers/deploy/**"
jobs: jobs:
build: build:

Wyświetl plik

@ -6,6 +6,7 @@ on:
- "main" - "main"
paths: paths:
- "db/**" - "db/**"
- "!db/deploy/**"
jobs: jobs:
build: build:

Wyświetl plik

@ -25,7 +25,7 @@ CANONICAL_SUBSCRIPTION_TYPES = {
icon_url="https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/ethereum/eth-diamond-purple.png", icon_url="https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/ethereum/eth-diamond-purple.png",
stripe_product_id=None, stripe_product_id=None,
stripe_price_id=None, stripe_price_id=None,
active=False, active=True,
), ),
"polygon_smartcontract": SubscriptionTypeResourceData( "polygon_smartcontract": SubscriptionTypeResourceData(
id="polygon_smartcontract", id="polygon_smartcontract",

Wyświetl plik

@ -8,4 +8,5 @@ User=ubuntu
Group=www-data Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
ExecStart=/home/ubuntu/moonstream-env/bin/python -m mooncrawl.crawler blocks missing --blockchain ethereum -n ExecStart=/home/ubuntu/moonstream-env/bin/python -m mooncrawl.crawler blocks missing --blockchain ethereum -n
SyslogIdentifier=ethereum-missing

Wyświetl plik

@ -8,4 +8,5 @@ User=ubuntu
Group=www-data Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
ExecStart=/home/ubuntu/moonstream-env/bin/python -m mooncrawl.crawler trending ExecStart=/home/ubuntu/moonstream-env/bin/python -m mooncrawl.crawler trending
SyslogIdentifier=ethereum-trending

Wyświetl plik

@ -1,12 +1,16 @@
[Unit] [Unit]
Description=Ethereum txpool crawler Description=Ethereum txpool crawler
After=network.target After=network.target
StartLimitIntervalSec=300
StartLimitBurst=3
[Service] [Service]
User=ubuntu User=ubuntu
Group=www-data Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/txpool WorkingDirectory=/home/ubuntu/moonstream/crawlers/txpool
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
Restart=on-failure
RestartSec=15s
ExecStart=/home/ubuntu/moonstream/crawlers/txpool/txpool -blockchain ethereum -access-id "${NB_CONTROLLER_ACCESS_ID}" ExecStart=/home/ubuntu/moonstream/crawlers/txpool/txpool -blockchain ethereum -access-id "${NB_CONTROLLER_ACCESS_ID}"
SyslogIdentifier=ethereum-txpool SyslogIdentifier=ethereum-txpool

Wyświetl plik

@ -8,4 +8,5 @@ User=ubuntu
Group=www-data Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
ExecStart=/home/ubuntu/moonstream-env/bin/python -m mooncrawl.crawler blocks missing --blockchain polygon -n ExecStart=/home/ubuntu/moonstream-env/bin/python -m mooncrawl.crawler blocks missing --blockchain polygon -n
SyslogIdentifier=polygon-missing

Wyświetl plik

@ -9,3 +9,4 @@ Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
ExecStart=/home/ubuntu/moonstream-env/bin/python -m mooncrawl.stats_worker.dashboard generate --blockchain polygon ExecStart=/home/ubuntu/moonstream-env/bin/python -m mooncrawl.stats_worker.dashboard generate --blockchain polygon
SyslogIdentifier=polygon-statistics

Wyświetl plik

@ -1,12 +1,16 @@
[Unit] [Unit]
Description=Polygon txpool crawler Description=Polygon txpool crawler
After=network.target After=network.target
StartLimitIntervalSec=300
StartLimitBurst=3
[Service] [Service]
User=ubuntu User=ubuntu
Group=www-data Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/txpool WorkingDirectory=/home/ubuntu/moonstream/crawlers/txpool
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
Restart=on-failure
RestartSec=15s
ExecStart=/home/ubuntu/moonstream/crawlers/txpool/txpool -blockchain polygon -access-id "${NB_CONTROLLER_ACCESS_ID}" ExecStart=/home/ubuntu/moonstream/crawlers/txpool/txpool -blockchain polygon -access-id "${NB_CONTROLLER_ACCESS_ID}"
SyslogIdentifier=polygon-txpool SyslogIdentifier=polygon-txpool

Wyświetl plik

@ -151,7 +151,7 @@ def main() -> None:
"--max-blocks-batch", "--max-blocks-batch",
"-m", "-m",
type=int, type=int,
default=100, default=80,
help="Maximum number of blocks to crawl in a single batch", help="Maximum number of blocks to crawl in a single batch",
) )
@ -159,7 +159,7 @@ def main() -> None:
"--min-blocks-batch", "--min-blocks-batch",
"-n", "-n",
type=int, type=int,
default=40, default=20,
help="Minimum number of blocks to crawl in a single batch", help="Minimum number of blocks to crawl in a single batch",
) )
@ -175,7 +175,7 @@ def main() -> None:
"--min-sleep-time", "--min-sleep-time",
"-t", "-t",
type=float, type=float,
default=0.01, default=0.1,
help="Minimum time to sleep between crawl step", help="Minimum time to sleep between crawl step",
) )
@ -191,7 +191,7 @@ def main() -> None:
"--new-jobs-refetch-interval", "--new-jobs-refetch-interval",
"-r", "-r",
type=float, type=float,
default=120, default=180,
help="Time to wait before refetching new jobs", help="Time to wait before refetching new jobs",
) )

Wyświetl plik

@ -172,14 +172,14 @@ def continuous_crawler(
) )
last_heartbeat_time = datetime.utcnow() last_heartbeat_time = datetime.utcnow()
blocks_cache: Dict[int, int] = {} blocks_cache: Dict[int, int] = {}
current_sleep_time = min_sleep_time
failed_count = 0 failed_count = 0
try: try:
while True: while True:
try: try:
# query db with limit 1, to avoid session closing # query db with limit 1, to avoid session closing
db_session.execute("SELECT 1") db_session.execute("SELECT 1")
time.sleep(min_sleep_time) time.sleep(current_sleep_time)
end_block = min( end_block = min(
web3.eth.blockNumber - confirmations, web3.eth.blockNumber - confirmations,
@ -187,12 +187,12 @@ def continuous_crawler(
) )
if start_block + min_blocks_batch > end_block: if start_block + min_blocks_batch > end_block:
min_sleep_time += 0.1 current_sleep_time += 0.1
logger.info( logger.info(
f"Sleeping for {min_sleep_time} seconds because of low block count" f"Sleeping for {current_sleep_time} seconds because of low block count"
) )
continue continue
min_sleep_time = max(0, min_sleep_time - 0.1) current_sleep_time = max(min_sleep_time, current_sleep_time - 0.1)
logger.info(f"Crawling events from {start_block} to {end_block}") logger.info(f"Crawling events from {start_block} to {end_block}")
all_events = _crawl_events( all_events = _crawl_events(

Wyświetl plik

@ -206,18 +206,27 @@ def make_function_call_crawl_jobs(
""" """
crawl_job_by_address: Dict[str, FunctionCallCrawlJob] = {} crawl_job_by_address: Dict[str, FunctionCallCrawlJob] = {}
method_signature_by_address: Dict[str, List[str]] = {}
for entry in entries: for entry in entries:
contract_address = Web3().toChecksumAddress(_get_tag(entry, "address")) contract_address = Web3().toChecksumAddress(_get_tag(entry, "address"))
abi = cast(str, entry.content) abi = json.loads(cast(str, entry.content))
method_signature = encode_function_signature(abi)
if method_signature is None:
raise ValueError(f"{abi} is not a function ABI")
if contract_address not in crawl_job_by_address: if contract_address not in crawl_job_by_address:
crawl_job_by_address[contract_address] = FunctionCallCrawlJob( crawl_job_by_address[contract_address] = FunctionCallCrawlJob(
contract_abi=[json.loads(abi)], contract_abi=[abi],
contract_address=contract_address, contract_address=contract_address,
created_at=int(datetime.fromisoformat(entry.created_at).timestamp()), created_at=int(datetime.fromisoformat(entry.created_at).timestamp()),
) )
method_signature_by_address[contract_address] = [method_signature]
else: else:
crawl_job_by_address[contract_address].contract_abi.append(json.loads(abi))
if method_signature not in method_signature_by_address[contract_address]:
crawl_job_by_address[contract_address].contract_abi.append(abi)
method_signature_by_address[contract_address].append(method_signature)
return [crawl_job for crawl_job in crawl_job_by_address.values()] return [crawl_job for crawl_job in crawl_job_by_address.values()]

Wyświetl plik

@ -91,6 +91,3 @@ def function_call_crawler(
i, i,
min(i + batch_size - 1, end_block), min(i + batch_size - 1, end_block),
) )
logger.info(f"Crawled {len(crawled_functions)} functions")
for function_call in crawled_functions:
print(function_call)

4
db/.gitignore vendored
Wyświetl plik

@ -186,3 +186,7 @@ alembic.dev.ini
alembic.prod.ini alembic.prod.ini
alembic.moonstreamdb.ini alembic.moonstreamdb.ini
alembic.docker.ini alembic.docker.ini
# Schematic
srv/
.schematic.env

Wyświetl plik

@ -0,0 +1,32 @@
"""Added index for address type and name of event
Revision ID: 5f5b8f19570f
Revises: f991fc7493c8
Create Date: 2022-05-04 11:32:42.309322
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "5f5b8f19570f"
down_revision = "f991fc7493c8"
branch_labels = None
depends_on = None
def upgrade():
op.execute(
"""
CREATE INDEX ix_polygon_labels_address_label_label_data_type_and_name ON polygon_labels USING BTREE (address,label,(label_data->>'type'),(label_data->>'name'));
"""
)
def downgrade():
op.execute(
"""
DROP INDEX ix_polygon_labels_address_label_label_data_type_and_name;
"""
)

Wyświetl plik

@ -260,6 +260,15 @@ class PolygonLabel(Base): # type: ignore
DateTime(timezone=True), server_default=utcnow(), nullable=False DateTime(timezone=True), server_default=utcnow(), nullable=False
) )
# Undescribed indexes
"""
Migration: alembic\versions\5f5b8f19570f_added_index_for_address_type_and_name_.py
Index: "ix_polygon_labels_address_label_label_data_type_and_name" created manually.
By fields: (address, label, (label_data->>'type'), (label_data->>'name'))
Reason: https://github.com/sqlalchemy/alembic/issues/469#issuecomment-441887478
"""
class ESDFunctionSignature(Base): # type: ignore class ESDFunctionSignature(Base): # type: ignore
""" """

28
db/scm.nix 100644
Wyświetl plik

@ -0,0 +1,28 @@
with builtins;
let
scm_repos = [
(getEnv "SCM_GIT")
(fetchGit {
url = "git@gitlab.com:deltaex/schematic.git";
rev = "0d9227335ad83e0ed9a62d82375c1e85aadcc08d";
})
];
scm_repo = head (filter (x: x != "") scm_repos);
scm = (import scm_repo {
verbose = true;
repos = [
"."
(getEnv "MDP_GIT")
(fetchGit {
url = "git@gitlab.com:mixrank/mdp.git";
rev = "76707e9d08178633471fa3c98ef1b08e1e7bbb1c";
})
] ++ scm_repos;
});
in rec {
schematic = scm.shell.overrideAttrs ( oldAttrs : {
shellHook = oldAttrs.shellHook + ''
[ -n "$ENV" -a "$ENV" != "dev" ] && export BUGSNAG=2b987ca13cd93a4931bb746aace204fb
'';
});
}

Wyświetl plik

@ -4,6 +4,8 @@ type PingResponse struct {
Status string `json:"status"` Status string `json:"status"`
} }
type BlockNumberResponse struct { type BlockLatestResponse struct {
BlockNumber uint64 `json:"block_number"` EthereumBlockLatest uint64 `json:"ethereum_block_latest"`
PolygonBlockLatest uint64 `json:"polygon_block_latest"`
PolygonBlockLatestLabelsMoonwormAlpha uint64 `json:"polygon_block_latest_label_moonworm_alpha"`
} }

Wyświetl plik

@ -10,7 +10,7 @@ import (
) )
func InitDB() *sql.DB { func InitDB() *sql.DB {
db, err := sql.Open("postgres", settings.MOONSTREAM_DB_URI) db, err := sql.Open("postgres", settings.MOONSTREAM_DB_URI_READ_ONLY)
if err != nil { if err != nil {
// DSN parse error or another initialization error // DSN parse error or another initialization error
log.Fatal(err) log.Fatal(err)

Wyświetl plik

@ -17,18 +17,40 @@ func pingRoute(w http.ResponseWriter, req *http.Request) {
func (es *extendedServer) blocksLatestRoute(w http.ResponseWriter, req *http.Request) { func (es *extendedServer) blocksLatestRoute(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
var latestBlock BlockNumberResponse var blockNumbers []uint64
row := es.db.QueryRow("SELECT block_number FROM ethereum_blocks ORDER BY block_number DESC LIMIT 1") var blockLatest BlockLatestResponse
err := row.Scan(&latestBlock.BlockNumber) rows, err := es.db.Query(`(SELECT block_number FROM ethereum_blocks ORDER BY block_number DESC LIMIT 1)
UNION ALL
(SELECT block_number FROM polygon_blocks ORDER BY block_number DESC LIMIT 1)
UNION ALL
(SELECT block_number FROM polygon_labels WHERE label = 'moonworm-alpha' ORDER BY block_number DESC LIMIT 1)`)
if err != nil { if err != nil {
if err == sql.ErrNoRows {
http.Error(w, "Row not found", http.StatusNotFound)
} else {
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
log.Printf("An error occurred during sql operation: %s", err) log.Printf("An error occurred during sql operation: %s", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return return
} }
defer rows.Close()
json.NewEncoder(w).Encode(latestBlock) for rows.Next() {
var bn uint64
err := rows.Scan(&bn)
if err != nil {
if err == sql.ErrNoRows {
http.Error(w, "Row not found", http.StatusNotFound)
} else {
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
log.Printf("An error occurred during scan sql response: %s", err)
return
}
blockNumbers = append(blockNumbers, bn)
}
blockLatest = BlockLatestResponse{
EthereumBlockLatest: blockNumbers[0],
PolygonBlockLatest: blockNumbers[1],
PolygonBlockLatestLabelsMoonwormAlpha: blockNumbers[2],
}
json.NewEncoder(w).Encode(blockLatest)
} }

Wyświetl plik

@ -8,7 +8,7 @@ import (
// Database configs // Database configs
var MOONSTREAM_DB_MAX_IDLE_CONNS int = 30 var MOONSTREAM_DB_MAX_IDLE_CONNS int = 30
var MOONSTREAM_DB_CONN_MAX_LIFETIME = 30 * time.Minute var MOONSTREAM_DB_CONN_MAX_LIFETIME = 30 * time.Minute
var MOONSTREAM_DB_URI = os.Getenv("MOONSTREAM_DB_URI") var MOONSTREAM_DB_URI_READ_ONLY = os.Getenv("MOONSTREAM_DB_URI_READ_ONLY")
// CORS // CORS
var MOONSTREAM_CORS_ALLOWED_ORIGINS = os.Getenv("MOONSTREAM_CORS_ALLOWED_ORIGINS") var MOONSTREAM_CORS_ALLOWED_ORIGINS = os.Getenv("MOONSTREAM_CORS_ALLOWED_ORIGINS")

Wyświetl plik

@ -1,9 +1,10 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# Expects access to Python environment with the requirements for this project installed. # Compile application and run with provided arguments
set -e set -e
MOONSTREAM_DB_SERVER_HOST="${MOONSTREAM_DB_SERVER_HOST:-0.0.0.0}" PROGRAM_NAME="moonstreamdb"
MOONSTREAM_DB_SERVER_PORT="${MOONSTREAM_DB_SERVER_PORT:-8080}"
go run main.go -host "${MOONSTREAM_DB_SERVER_HOST}" -port "${MOONSTREAM_DB_SERVER_PORT}" go build -o "$PROGRAM_NAME" .
./"$PROGRAM_NAME" "$@"

Wyświetl plik

@ -1,2 +1,2 @@
export MOONSTREAM_DB_URI="postgresql://<username>:<password>@<db_host>:<db_port>/<db_name>" export MOONSTREAM_DB_URI_READ_ONLY="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" export MOONSTREAM_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://moonstream.to,https://www.moonstream.to,https://alpha.moonstream.to"

24
db/sync_conf.yml 100644
Wyświetl plik

@ -0,0 +1,24 @@
- namespace: public
tablename: alembic_exploration_version
included: false
- namespace: public
tablename: esd_event_signatures
- namespace: public
tablename: esd_function_signatures
- namespace: public
tablename: ethereum_blocks
- namespace: public
tablename: ethereum_labels
- namespace: public
tablename: ethereum_labels_v1
included: false
- namespace: public
tablename: ethereum_transactions
- namespace: public
tablename: opensea_crawler_state
- namespace: public
tablename: polygon_blocks
- namespace: public
tablename: polygon_labels
- namespace: public
tablename: polygon_transactions

Wyświetl plik

@ -45,7 +45,7 @@ export default class MyDocument extends Document {
{/* <!-- Global site tag (gtag.js) - Google Analytics --> */} {/* <!-- Global site tag (gtag.js) - Google Analytics --> */}
<script <script
async async
src="https://www.googletagmanager.com/gtag/js?id=G-MNVHX36LZ1" src="https://www.googletagmanager.com/gtag/js?id=UA-156911549-2"
></script> ></script>
<script <script
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
@ -55,7 +55,7 @@ export default class MyDocument extends Document {
dataLayer.push(arguments); dataLayer.push(arguments);
} }
gtag("js", new Date()); gtag("js", new Date());
gtag("config", "G-MNVHX36LZ1");`, gtag("config", "UA-156911549-2");`,
}} }}
/> />
</Head> </Head>

Wyświetl plik

@ -0,0 +1,14 @@
import React from "react";
import { useRouter } from "../src/core/hooks";
const DiscordLeed = () => {
const router = useRouter();
React.useLayoutEffect(() => {
router.push("https://discord.gg/K56VNUQGvA");
}, [router]);
return <></>;
};
export default DiscordLeed;

Wyświetl plik

@ -8,7 +8,7 @@ const Docs = () => {
return ( return (
// <Box overflowY="hidden" w="100%" maxH="100%" minH="100vh"> // <Box overflowY="hidden" w="100%" maxH="100%" minH="100vh">
<> <>
<Box w="100%" maxH="100vh" overflowY="scroll"> <Box w="100%" maxH="100vh" overflowY="scroll" zIndex={0}>
<RedocStandalone <RedocStandalone
specUrl="https://api.moonstream.to/openapi.json" specUrl="https://api.moonstream.to/openapi.json"
options={{ options={{

Plik diff jest za duży Load Diff

Wyświetl plik

@ -1,57 +1,47 @@
import React, { useContext } from "react"; import React from "react";
import { useStatus } from "../../src/core/hooks"; import { useStatus } from "../../src/core/hooks";
import { Heading, Text, Flex, Spacer, chakra, Spinner } from "@chakra-ui/react"; import { Heading, Text, Flex, Spacer, chakra, Spinner } from "@chakra-ui/react";
import { getLayout, getLayoutProps } from "../../src/layouts/InfoPageLayout"; import { getLayout, getLayoutProps } from "../../src/layouts/InfoPageLayout";
import UserContext from "../../src/core/providers/UserProvider/context";
const Status = () => { const Status = () => {
const user = useContext(UserContext);
const healthyStatusText = "Available"; const healthyStatusText = "Available";
const downStatusText = "Unavailable"; const downStatusText = "Unavailable";
const unauthorizedText = "Please login";
const healthyStatusColor = "green.900"; const healthyStatusColor = "green.900";
const downStatusColor = "red.600"; const downStatusColor = "red.600";
const shortTimestamp = (rawTimestamp) => { const { serverListStatusCache } = useStatus();
return rawTimestamp.replace(/^.+T/, "").replace(/\..+/, "");
};
const { console.log(serverListStatusCache?.data);
serverListStatusCache,
crawlersStatusCache,
dbServerStatusCache,
latestBlockDBStatusCache,
} = useStatus();
const moonstreamapiStatus = serverListStatusCache?.data?.filter( const moonstreamapiStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "moonstreamapi" (i) => i.name === "moonstream_api"
)[0]; )[0];
const moonstreamCrawlersStatus = serverListStatusCache?.data?.filter( const moonstreamCrawlersStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "moonstream_crawlers" (i) => i.name === "moonstream_crawlers"
)[0];
const nodeBalacerStatus = serverListStatusCache?.data?.filter(
(i) => i.name === "moonstream_node_balancer"
)[0]; )[0];
const nodeEthereumAStatus = serverListStatusCache?.data?.filter( const nodeEthereumAStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_ethereum_a" (i) => i.name === "node_ethereum_a"
)[0];
const nodeEthereumAGeth = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_ethereum_a_geth"
)[0]; )[0];
const nodeEthereumBStatus = serverListStatusCache?.data?.filter( const nodeEthereumBStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_ethereum_b" (i) => i.name === "node_ethereum_b"
)[0];
const nodeEthereumBGeth = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_ethereum_b_geth"
)[0]; )[0];
const nodePolygonAStatus = serverListStatusCache?.data?.filter( const nodePolygonAStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_polygon_a" (i) => i.name === "node_polygon_a"
)[0];
const nodePolygonAGeth = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_polygon_a_bor"
)[0]; )[0];
const nodePolygonBStatus = serverListStatusCache?.data?.filter( const nodePolygonBStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_polygon_b" (i) => i.name === "node_polygon_b"
)[0]; )[0];
const nodePolygonBGeth = serverListStatusCache?.data?.filter( const dbServerStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_polygon_b_bor" (i) => i.name === "moonstream_database"
)[0];
const dbReplicaServerStatus = serverListStatusCache?.data?.filter(
(i) => i.name === "moonstream_database_replica"
)[0];
const unimLeaderboardStatus = serverListStatusCache?.data?.filter(
(i) => i.name === "unim_leaderboard"
)[0]; )[0];
const StatusRow = (props) => { const StatusRow = (props) => {
@ -80,12 +70,12 @@ const Status = () => {
<StatusRow title="Backend server" cache={serverListStatusCache}> <StatusRow title="Backend server" cache={serverListStatusCache}>
<Text <Text
color={ color={
moonstreamapiStatus?.status.body.status == "ok" moonstreamapiStatus?.status_code == 200
? healthyStatusColor ? healthyStatusColor
: downStatusColor : downStatusColor
} }
> >
{moonstreamapiStatus?.status.body.status == "ok" {moonstreamapiStatus?.status_code == 200
? healthyStatusText ? healthyStatusText
: downStatusText} : downStatusText}
</Text> </Text>
@ -93,42 +83,33 @@ const Status = () => {
<br /> <br />
<StatusRow title="Crawlers server" cache={crawlersStatusCache}> <StatusRow title="Crawlers server" cache={serverListStatusCache}>
<Text <Text
color={ color={
moonstreamCrawlersStatus?.status.body.status == "ok" moonstreamCrawlersStatus?.status_code == 200
? healthyStatusColor ? healthyStatusColor
: downStatusColor : downStatusColor
} }
> >
{moonstreamCrawlersStatus?.status.body.status == "ok" {moonstreamCrawlersStatus?.status_code == 200
? healthyStatusText ? healthyStatusText
: downStatusText} : downStatusText}
</Text> </Text>
</StatusRow> </StatusRow>
<StatusRow title="Txpool latest record ts" cache={crawlersStatusCache}>
<Text> <br />
{!user
? crawlersStatusCache?.data?.ethereum_txpool_timestamp <StatusRow title="Node balancer server" cache={serverListStatusCache}>
? shortTimestamp( <Text
crawlersStatusCache?.data?.ethereum_txpool_timestamp color={
) nodeBalacerStatus?.status_code == 200
: downStatusText ? healthyStatusColor
: unauthorizedText} : downStatusColor
</Text> }
</StatusRow> >
<StatusRow {nodeBalacerStatus?.status_code == 200
title="Trending latest record ts" ? healthyStatusText
cache={crawlersStatusCache} : downStatusText}
>
<Text>
{!user
? crawlersStatusCache?.data?.ethereum_trending_timestamp
? shortTimestamp(
crawlersStatusCache?.data?.ethereum_trending_timestamp
)
: downStatusText
: unauthorizedText}
</Text> </Text>
</StatusRow> </StatusRow>
@ -137,20 +118,20 @@ const Status = () => {
<StatusRow title="Node Ethereum A" cache={serverListStatusCache}> <StatusRow title="Node Ethereum A" cache={serverListStatusCache}>
<Text <Text
color={ color={
nodeEthereumAStatus?.status.body.status == "ok" nodeEthereumAStatus?.status_code == 200
? healthyStatusColor ? healthyStatusColor
: downStatusColor : downStatusColor
} }
> >
{nodeEthereumAStatus?.status.body.status == "ok" {nodeEthereumAStatus?.status_code == 200
? healthyStatusText ? healthyStatusText
: downStatusText} : downStatusText}
</Text> </Text>
</StatusRow> </StatusRow>
<StatusRow title="Current block" cache={serverListStatusCache}> <StatusRow title="Current block" cache={serverListStatusCache}>
<Text> <Text>
{nodeEthereumAGeth?.status.body.current_block {nodeEthereumAStatus?.response?.current_block
? nodeEthereumAGeth.status.body.current_block ? nodeEthereumAStatus.response.current_block
: 0} : 0}
</Text> </Text>
</StatusRow> </StatusRow>
@ -158,20 +139,20 @@ const Status = () => {
<StatusRow title="Node Ethereum B" cache={serverListStatusCache}> <StatusRow title="Node Ethereum B" cache={serverListStatusCache}>
<Text <Text
color={ color={
nodeEthereumBStatus?.status.body.status == "ok" nodeEthereumBStatus?.status_code == 200
? healthyStatusColor ? healthyStatusColor
: downStatusColor : downStatusColor
} }
> >
{nodeEthereumBStatus?.status.body.status == "ok" {nodeEthereumBStatus?.status_code == 200
? healthyStatusText ? healthyStatusText
: downStatusText} : downStatusText}
</Text> </Text>
</StatusRow> </StatusRow>
<StatusRow title="Current block" cache={serverListStatusCache}> <StatusRow title="Current block" cache={serverListStatusCache}>
<Text> <Text>
{nodeEthereumBGeth?.status.body.current_block {nodeEthereumBStatus?.response?.current_block
? nodeEthereumBGeth.status.body.current_block ? nodeEthereumBStatus.response.current_block
: 0} : 0}
</Text> </Text>
</StatusRow> </StatusRow>
@ -179,20 +160,20 @@ const Status = () => {
<StatusRow title="Node Polygon A" cache={serverListStatusCache}> <StatusRow title="Node Polygon A" cache={serverListStatusCache}>
<Text <Text
color={ color={
nodePolygonAStatus?.status.body.status == "ok" nodePolygonAStatus?.status_code == 200
? healthyStatusColor ? healthyStatusColor
: downStatusColor : downStatusColor
} }
> >
{nodePolygonAStatus?.status.body.status == "ok" {nodePolygonAStatus?.status_code == 200
? healthyStatusText ? healthyStatusText
: downStatusText} : downStatusText}
</Text> </Text>
</StatusRow> </StatusRow>
<StatusRow title="Current block" cache={serverListStatusCache}> <StatusRow title="Current block" cache={serverListStatusCache}>
<Text> <Text>
{nodePolygonAGeth?.status.body.current_block {nodePolygonAStatus?.response?.current_block
? nodePolygonAGeth.status.body.current_block ? nodePolygonAStatus.response.current_block
: 0} : 0}
</Text> </Text>
</StatusRow> </StatusRow>
@ -200,49 +181,105 @@ const Status = () => {
<StatusRow title="Node Polygon B" cache={serverListStatusCache}> <StatusRow title="Node Polygon B" cache={serverListStatusCache}>
<Text <Text
color={ color={
nodePolygonBStatus?.status.body.status == "ok" nodePolygonBStatus?.status_code == 200
? healthyStatusColor ? healthyStatusColor
: downStatusColor : downStatusColor
} }
> >
{nodePolygonBStatus?.status.body.status == "ok" {nodePolygonBStatus?.status_code == 200
? healthyStatusText ? healthyStatusText
: downStatusText} : downStatusText}
</Text> </Text>
</StatusRow> </StatusRow>
<StatusRow title="Current block" cache={serverListStatusCache}> <StatusRow title="Current block" cache={serverListStatusCache}>
<Text> <Text>
{nodePolygonBGeth?.status.body.current_block {nodePolygonBStatus?.response?.current_block
? nodePolygonBGeth.status.body.current_block ? nodePolygonBStatus.response.current_block
: 0} : 0}
</Text> </Text>
</StatusRow> </StatusRow>
<br /> <br />
<StatusRow title="Database server" cache={dbServerStatusCache}> <StatusRow title="Database server" cache={serverListStatusCache}>
<Text <Text
color={ color={
dbServerStatusCache?.data?.status == "ok" dbServerStatus?.status_code == 200
? healthyStatusColor ? healthyStatusColor
: downStatusColor : downStatusColor
} }
> >
{dbServerStatusCache?.data?.status == "ok" {dbServerStatus?.status_code == 200
? healthyStatusText ? healthyStatusText
: downStatusText} : downStatusText}
</Text> </Text>
</StatusRow> </StatusRow>
<StatusRow <StatusRow title="Ethereum latest block" cache={serverListStatusCache}>
title="Latest block in Database"
cache={latestBlockDBStatusCache}
>
<Text> <Text>
{latestBlockDBStatusCache?.data?.block_number {dbServerStatus?.response?.ethereum_block_latest
? latestBlockDBStatusCache.data.block_number ? dbServerStatus.response.ethereum_block_latest
: 0} : 0}
</Text> </Text>
</StatusRow> </StatusRow>
<StatusRow title="Polygon latest block" cache={serverListStatusCache}>
<Text>
{dbServerStatus?.response?.polygon_block_latest
? dbServerStatus.response.polygon_block_latest
: 0}
</Text>
</StatusRow>
<br />
<StatusRow
title="Database replica server"
cache={serverListStatusCache}
>
<Text
color={
dbReplicaServerStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{dbReplicaServerStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
</StatusRow>
<StatusRow title="Ethereum latest block" cache={serverListStatusCache}>
<Text>
{dbReplicaServerStatus?.response?.ethereum_block_latest
? dbReplicaServerStatus.response.ethereum_block_latest
: 0}
</Text>
</StatusRow>
<StatusRow title="Polygon latest block" cache={serverListStatusCache}>
<Text>
{dbReplicaServerStatus?.response?.polygon_block_latest
? dbReplicaServerStatus.response.polygon_block_latest
: 0}
</Text>
</StatusRow>
<br />
<StatusRow
title="Unim Leaderboard server"
cache={serverListStatusCache}
>
<Text
color={
unimLeaderboardStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{unimLeaderboardStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
</StatusRow>
</chakra.span> </chakra.span>
</> </>
); );

Wyświetl plik

@ -14,7 +14,7 @@ import {
import { AiOutlinePlusCircle } from "react-icons/ai"; import { AiOutlinePlusCircle } from "react-icons/ai";
import OverlayContext from "../src/core/providers/OverlayProvider/context"; import OverlayContext from "../src/core/providers/OverlayProvider/context";
import { MODAL_TYPES } from "../src/core/providers/OverlayProvider/constants"; import { MODAL_TYPES } from "../src/core/providers/OverlayProvider/constants";
import Scrollable from "../src/components/Scrollable";
const Subscriptions = () => { const Subscriptions = () => {
const { subscriptionsCache } = useSubscriptions(); const { subscriptionsCache } = useSubscriptions();
const modal = useContext(OverlayContext); const modal = useContext(OverlayContext);
@ -37,64 +37,66 @@ const Subscriptions = () => {
modal.toggleModal({ type: MODAL_TYPES.NEW_SUBSCRIPTON }); modal.toggleModal({ type: MODAL_TYPES.NEW_SUBSCRIPTON });
}; };
return ( return (
<Box w="100%" px="7%" pt={2}> <Scrollable>
{subscriptionsCache.isLoading ? ( <Box w="100%" px="7%" pt={2}>
<Center> {subscriptionsCache.isLoading ? (
<Spinner <Center>
hidden={false} <Spinner
my={8} hidden={false}
size="lg" my={8}
color="blue.500" size="lg"
thickness="4px" color="blue.500"
speed="1.5s" thickness="4px"
/> speed="1.5s"
</Center> />
) : ( </Center>
<ScaleFade in> ) : (
<Heading {...headingStyle}> My Subscriptions </Heading> <ScaleFade in>
<Flex <Heading {...headingStyle}> My Subscriptions </Heading>
mt={4}
overflow="initial"
maxH="unset"
height="100%"
direction="column"
>
<Flex <Flex
h="3rem" mt={4}
w="100%" overflow="initial"
bgColor="blue.50" maxH="unset"
borderTopRadius="xl" height="100%"
justifyContent="flex-end" direction="column"
alignItems="center"
> >
{subscriptionsCache.data?.is_free_subscription_availible && ( <Flex
h="3rem"
w="100%"
bgColor="blue.50"
borderTopRadius="xl"
justifyContent="flex-end"
alignItems="center"
>
{subscriptionsCache.data?.is_free_subscription_availible && (
<Button
onClick={() => newSubscriptionClicked(true)}
mr={8}
colorScheme="green"
variant="solid"
size="sm"
rightIcon={<AiOutlinePlusCircle />}
>
Add for free
</Button>
)}
<Button <Button
onClick={() => newSubscriptionClicked(true)} onClick={() => newSubscriptionClicked(false)}
mr={8} mr={8}
colorScheme="green" colorScheme="blue"
variant="solid" variant="solid"
size="sm" size="sm"
rightIcon={<AiOutlinePlusCircle />} rightIcon={<AiOutlinePlusCircle />}
> >
Add for free Add new
</Button> </Button>
)} </Flex>
<Button <SubscriptionsList data={subscriptionsCache.data} />
onClick={() => newSubscriptionClicked(false)}
mr={8}
colorScheme="blue"
variant="solid"
size="sm"
rightIcon={<AiOutlinePlusCircle />}
>
Add new
</Button>
</Flex> </Flex>
<SubscriptionsList data={subscriptionsCache.data} /> </ScaleFade>
</Flex> )}
</ScaleFade> </Box>
)} </Scrollable>
</Box>
); );
}; };

Wyświetl plik

@ -2,7 +2,6 @@ export NEXT_PUBLIC_MIXPANEL_TOKEN="<YOUR MIXPANEL TOKEN HERE>"
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="<stripe publishable key>" export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="<stripe publishable key>"
export NEXT_PUBLIC_BUGOUT_STATUS_URL=https://status.moonstream.to export NEXT_PUBLIC_BUGOUT_STATUS_URL=https://status.moonstream.to
export NEXT_PUBLIC_MOONSTREAM_API_URL=https://api.moonstream.to export NEXT_PUBLIC_MOONSTREAM_API_URL=https://api.moonstream.to
export NEXT_PUBLIC_MOONSTREAM_DB_URL=https://pg.moonstream.to
export NEXT_PUBLIC_SIMIOTICS_AUTH_URL=https://auth.bugout.dev export NEXT_PUBLIC_SIMIOTICS_AUTH_URL=https://auth.bugout.dev
export NEXT_PUBLIC_SIMIOTICS_JOURNALS_URL=https://spire.bugout.dev export NEXT_PUBLIC_SIMIOTICS_JOURNALS_URL=https://spire.bugout.dev
export NEXT_PUBLIC_FRONTEND_VERSION="<frontend_version_number>" export NEXT_PUBLIC_FRONTEND_VERSION="<frontend_version_number>"

Wyświetl plik

@ -114,6 +114,24 @@ const variantGhost = (props) => {
}; };
}; };
const variantOrangeAndBlue = () => {
return {
minW: ["200px", "250px", "250px", "300px", "350px", "400px"],
alignItems: "center",
justifyContent: "center",
border: "solid transparent",
fontWeight: "bold",
rounded: ["lg", "xl", "2xl"],
shadow: "md",
variant: "solid",
textColor: "blue.1200",
bg: `orange.900`,
fontSize: ["lg", "xl", "2xl", "3xl", "4xl", "4xl"],
py: [4, 6, 6, 8, 8],
px: [4, 4, 4, 8, 8],
};
};
const Button = { const Button = {
// 1. We can update the base styles // 1. We can update the base styles
baseStyle: () => ({ baseStyle: () => ({
@ -151,6 +169,7 @@ const Button = {
ghost: variantGhost, ghost: variantGhost,
outline: variantOutline, outline: variantOutline,
link: variantLink, link: variantLink,
orangeAndBlue: variantOrangeAndBlue,
}, },
}; };
export default Button; export default Button;

Wyświetl plik

@ -9,11 +9,12 @@ import {
MenuDivider, MenuDivider,
IconButton, IconButton,
chakra, chakra,
Portal,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { RiAccountCircleLine } from "react-icons/ri"; import { RiAccountCircleLine } from "react-icons/ri";
import useLogout from "../core/hooks/useLogout"; import useLogout from "../core/hooks/useLogout";
import UIContext from "../core/providers/UIProvider/context"; import UIContext from "../core/providers/UIProvider/context";
import { ALL_NAV_PATHES } from "../core/constants"; import { SITEMAP } from "../core/constants";
const AccountIconButton = (props) => { const AccountIconButton = (props) => {
const { logout } = useLogout(); const { logout } = useLogout();
@ -30,39 +31,49 @@ const AccountIconButton = (props) => {
icon={<RiAccountCircleLine m={0} size="26px" />} icon={<RiAccountCircleLine m={0} size="26px" />}
color="gray.100" color="gray.100"
/> />
<MenuList <Portal>
zIndex="dropdown" <MenuList
width={["100vw", "100vw", "18rem", "20rem", "22rem", "24rem"]} zIndex="dropdown"
borderRadius={0} width={["100vw", "100vw", "18rem", "20rem", "22rem", "24rem"]}
> borderRadius={0}
<MenuGroup>
<RouterLink href="/account/security" passHref>
<MenuItem>Security</MenuItem>
</RouterLink>
<RouterLink href="/account/tokens" passHref>
<MenuItem>API tokens</MenuItem>
</RouterLink>
</MenuGroup>
<MenuDivider />
{ui.isMobileView &&
ALL_NAV_PATHES.map((pathToLink, idx) => {
return (
<MenuItem key={`AccountIconButton-All_nav_pathes-${idx}`}>
<RouterLink href={pathToLink.path}>
{pathToLink.title}
</RouterLink>
</MenuItem>
);
})}
<MenuDivider />
<MenuItem
onClick={() => {
logout();
}}
> >
Logout <MenuGroup>
</MenuItem> <RouterLink href="/account/security" passHref>
</MenuList> <MenuItem>Security</MenuItem>
</RouterLink>
<RouterLink href="/account/tokens" passHref>
<MenuItem>API tokens</MenuItem>
</RouterLink>
</MenuGroup>
<MenuDivider />
{ui.isMobileView &&
SITEMAP.map((item, idx) => {
if (item.children) {
return (
<MenuGroup key={`AccountIconButton-MenuGroup-${idx}`}>
{item.children.map((child, idx) => {
return (
<MenuItem key={`AccountIconButton-SITEMAP-${idx}`}>
<RouterLink href={child.path}>
{child.title}
</RouterLink>
</MenuItem>
);
})}
</MenuGroup>
);
}
})}
<MenuDivider />
<MenuItem
onClick={() => {
logout();
}}
>
Logout
</MenuItem>
</MenuList>
</Portal>
</Menu> </Menu>
); );
}; };

Wyświetl plik

@ -1,4 +1,5 @@
import React, { useState, useContext, useEffect } from "react"; import React, { useState, useContext, useEffect } from "react";
import RouterLink from "next/link";
import { import {
Flex, Flex,
Image, Image,
@ -15,12 +16,19 @@ import {
useBreakpointValue, useBreakpointValue,
Spacer, Spacer,
ButtonGroup, ButtonGroup,
Button,
Menu,
MenuButton,
MenuList,
MenuItem,
Portal,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { import {
HamburgerIcon, HamburgerIcon,
QuestionOutlineIcon, QuestionOutlineIcon,
ArrowLeftIcon, ArrowLeftIcon,
ArrowRightIcon, ArrowRightIcon,
ChevronDownIcon,
} from "@chakra-ui/icons"; } from "@chakra-ui/icons";
import useRouter from "../core/hooks/useRouter"; import useRouter from "../core/hooks/useRouter";
import UIContext from "../core/providers/UIProvider/context"; import UIContext from "../core/providers/UIProvider/context";
@ -29,7 +37,7 @@ import RouteButton from "./RouteButton";
import AddNewIconButton from "./AddNewIconButton"; import AddNewIconButton from "./AddNewIconButton";
import { import {
USER_NAV_PATHES, USER_NAV_PATHES,
ALL_NAV_PATHES, SITEMAP,
WHITE_LOGO_W_TEXT_URL, WHITE_LOGO_W_TEXT_URL,
} from "../core/constants"; } from "../core/constants";
@ -98,21 +106,53 @@ const AppNavbar = () => {
<> <>
{!ui.isMobileView && ( {!ui.isMobileView && (
<> <>
<Flex width="100%" px={2}> <Flex px={2}>
<Spacer /> <Spacer />
<Flex placeSelf="flex-end"> <Flex placeSelf="flex-end">
<ButtonGroup spacing={4} colorScheme="orange"> <ButtonGroup variant="link" spacing={4} colorScheme="orange">
{ALL_NAV_PATHES.map((item, idx) => ( {SITEMAP.map((item, idx) => {
<RouteButton if (!item.children) {
key={`${idx}-${item.title}-landing-all-links`} return (
variant="link" <RouteButton
href={item.path} key={`${idx}-${item.title}-landing-all-links`}
color="white" variant="link"
isActive={!!(router.nextRouter.pathname === item.path)} href={item.path}
> color="white"
{item.title} isActive={!!(router.pathname === item.path)}
</RouteButton> >
))} {item.title}
</RouteButton>
);
} else {
return (
<Menu key={`menu-${idx}`}>
<MenuButton
key={`menu-button-${idx}`}
as={Button}
rightIcon={<ChevronDownIcon />}
>
{item.title}
</MenuButton>
<Portal>
<MenuList zIndex={100}>
{item.children.map((child, idx) => (
<RouterLink
shallow={true}
key={`${idx}-${item.title}-menu-links`}
href={child.path}
passHref
>
<MenuItem key={`menu-${idx}`} as={"a"} m={0}>
{child.title}
</MenuItem>
</RouterLink>
))}
</MenuList>
</Portal>
</Menu>
);
}
})}
{USER_NAV_PATHES.map((item, idx) => { {USER_NAV_PATHES.map((item, idx) => {
return ( return (
<RouteButton <RouteButton

Wyświetl plik

@ -12,7 +12,8 @@ import { BiCopy } from "react-icons/bi";
const CopyButton = (props) => { const CopyButton = (props) => {
const children = props.children ? props.children : ""; const children = props.children ? props.children : "";
const copyString = props.prefix ? props.prefix + children : children; const copyString =
props.copyString ?? (props.prefix ? props.prefix + children : children);
const { onCopy } = useClipboard(copyString); const { onCopy } = useClipboard(copyString);
@ -31,7 +32,7 @@ const CopyButton = (props) => {
icon={<BiCopy />} icon={<BiCopy />}
colorScheme="orange" colorScheme="orange"
variant="ghost" variant="ghost"
size="sm" size={props.size ?? "sm"}
/> />
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent

Wyświetl plik

@ -12,11 +12,7 @@ import {
chakra, chakra,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import RouterLink from "next/link"; import RouterLink from "next/link";
import { import { WHITE_LOGO_W_TEXT_URL, SITEMAP } from "../core/constants";
WHITE_LOGO_W_TEXT_URL,
ALL_NAV_PATHES,
FOOTER_COLUMNS,
} from "../core/constants";
import { FaGithub, FaTwitter, FaDiscord } from "react-icons/fa"; import { FaGithub, FaTwitter, FaDiscord } from "react-icons/fa";
import moment from "moment"; import moment from "moment";
@ -69,9 +65,9 @@ const Footer = () => (
bg={useColorModeValue("blue.900", "gray.900")} bg={useColorModeValue("blue.900", "gray.900")}
color={useColorModeValue("gray.700", "gray.200")} color={useColorModeValue("gray.700", "gray.200")}
> >
<Container as={Stack} maxW={"6xl"} py={10}> <Container as={Stack} maxW={"8xl"} py={10}>
<SimpleGrid <SimpleGrid
templateColumns={{ sm: "1fr 1fr", md: "2fr 1fr 1fr 2fr" }} templateColumns={{ sm: "1fr 1fr", md: "2fr 1fr 1fr 1fr 1fr" }}
spacing={8} spacing={8}
> >
<Stack spacing={6}> <Stack spacing={6}>
@ -104,24 +100,35 @@ const Footer = () => (
> >
<FaGithub /> <FaGithub />
</SocialButton> </SocialButton>
<SocialButton <SocialButton label={"Discord"} href={"/discordleed"}>
label={"Discord"}
href={"https://discord.gg/K56VNUQGvA"}
>
<FaDiscord /> <FaDiscord />
</SocialButton> </SocialButton>
</Stack> </Stack>
</Stack> </Stack>
{Object.values(FOOTER_COLUMNS).map((columnEnum, colIndex) => { {Object.values(SITEMAP).map((category, colIndex) => {
return ( return (
<Stack align={"flex-start"} key={`footer-list-column-${colIndex}`}> <Stack align={"flex-start"} key={`footer-list-column-${colIndex}`}>
{ALL_NAV_PATHES.filter( <>
(navPath) => navPath.footerCategory === columnEnum <ListHeader>{category.title}</ListHeader>
{category.children.map((linkItem, linkItemIndex) => {
return (
<RouterLink
passHref
href={linkItem.path}
key={`footer-list-link-item-${linkItemIndex}-col-${colIndex}`}
>
<Link {...LINKS_SIZES}>{linkItem.title}</Link>
</RouterLink>
);
})}
</>
{/* {SITEMAP.filter(
(navPath) => navPath.SiteMapCategory === columnEnum
).length > 0 && ( ).length > 0 && (
<> <>
<ListHeader>{columnEnum}</ListHeader> <ListHeader>{columnEnum}</ListHeader>
{ALL_NAV_PATHES.filter( {ALL_NAV_PATHES.filter(
(navPath) => navPath.footerCategory === columnEnum (navPath) => navPath.SiteMapCategory === columnEnum
).map((linkItem, linkItemIndex) => { ).map((linkItem, linkItemIndex) => {
return ( return (
<RouterLink <RouterLink
@ -134,7 +141,7 @@ const Footer = () => (
); );
})} })}
</> </>
)} )} */}
</Stack> </Stack>
); );
})} })}

Wyświetl plik

@ -8,13 +8,18 @@ import {
Link, Link,
IconButton, IconButton,
Flex, Flex,
Menu,
MenuButton,
MenuList,
MenuItem,
Portal,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { HamburgerIcon } from "@chakra-ui/icons"; import { ChevronDownIcon, HamburgerIcon } from "@chakra-ui/icons";
import useModals from "../core/hooks/useModals"; import useModals from "../core/hooks/useModals";
import UIContext from "../core/providers/UIProvider/context"; import UIContext from "../core/providers/UIProvider/context";
import ChakraAccountIconButton from "./AccountIconButton"; import ChakraAccountIconButton from "./AccountIconButton";
import RouteButton from "./RouteButton"; import RouteButton from "./RouteButton";
import { ALL_NAV_PATHES, WHITE_LOGO_W_TEXT_URL } from "../core/constants"; import { SITEMAP, WHITE_LOGO_W_TEXT_URL } from "../core/constants";
import router from "next/router"; import router from "next/router";
import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants"; import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants";
@ -37,7 +42,7 @@ const LandingNavbar = () => {
<Flex <Flex
pl={ui.isMobileView ? 2 : 8} pl={ui.isMobileView ? 2 : 8}
justifySelf="flex-start" justifySelf="flex-start"
h="100%" h="48px"
py={1} py={1}
flexBasis="200px" flexBasis="200px"
flexGrow={1} flexGrow={1}
@ -47,7 +52,7 @@ const LandingNavbar = () => {
<Link <Link
as={Image} as={Image}
w="auto" w="auto"
h="full" h="100%"
justifyContent="left" justifyContent="left"
src={WHITE_LOGO_W_TEXT_URL} src={WHITE_LOGO_W_TEXT_URL}
alt="Moonstream logo" alt="Moonstream logo"
@ -59,21 +64,51 @@ const LandingNavbar = () => {
<> <>
<Spacer /> <Spacer />
<ButtonGroup variant="link" colorScheme="orange" spacing={4} pr={16}> <ButtonGroup variant="link" colorScheme="orange" spacing={4} pr={16}>
{ALL_NAV_PATHES.map((item, idx) => ( {SITEMAP.map((item, idx) => {
<RouteButton return (
key={`${idx}-${item.title}-landing-all-links`} <React.Fragment key={`Fragment-${idx}`}>
variant="link" {!item.children && (
href={item.path} <RouteButton
color="white" key={`${idx}-${item.title}-landing-all-links`}
isActive={!!(router.pathname === item.path)} variant="link"
> href={item.path}
{item.title} color="white"
</RouteButton> isActive={!!(router.pathname === item.path)}
))} >
{item.title}
</RouteButton>
)}
{item.children && (
<Menu>
<MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
{item.title}
</MenuButton>
<Portal>
<MenuList zIndex={100}>
{item.children.map((child, idx) => (
<RouterLink
shallow={true}
key={`${idx}-${item.title}-menu-links`}
href={child.path}
passHref
>
<MenuItem key={`menu-${idx}`} as={"a"} m={0}>
{child.title}
</MenuItem>
</RouterLink>
))}
</MenuList>
</Portal>
</Menu>
)}
</React.Fragment>
);
})}
{ui.isLoggedIn && ( {ui.isLoggedIn && (
<RouterLink href="/welcome" passHref> <RouterLink href="/welcome" passHref>
<Button <Button
alignSelf={"center"}
as={Link} as={Link}
colorScheme="orange" colorScheme="orange"
variant="outline" variant="outline"

Wyświetl plik

@ -0,0 +1,26 @@
import { React } from "react";
import { chakra, Box, Text } from "@chakra-ui/react";
const _MilestoneBox = ({ headingText }) => {
return (
<Box
minW={["150px", "180px", "300px", "300px", "400px", "500px"]}
py={5}
px={3}
>
<Text
fontSize={["md", "2xl", "3xl", "4xl", "4xl", "4xl"]}
ml={5}
color="orange.500"
fontWeight="bold"
textAlign="center"
>
{headingText}
</Text>
</Box>
);
};
const MilestoneBox = chakra(_MilestoneBox);
export default MilestoneBox;

Wyświetl plik

@ -2,7 +2,7 @@ import React, { Suspense, useContext } from "react";
import { Flex } from "@chakra-ui/react"; import { Flex } from "@chakra-ui/react";
import UIContext from "../core/providers/UIProvider/context"; import UIContext from "../core/providers/UIProvider/context";
const LandingNavbar = React.lazy(() => import("./LandingNavbar")); import LandingNavbar from "./LandingNavbar";
const AppNavbar = React.lazy(() => import("./AppNavbar")); const AppNavbar = React.lazy(() => import("./AppNavbar"));
const Navbar = () => { const Navbar = () => {
@ -11,6 +11,7 @@ const Navbar = () => {
return ( return (
<Flex <Flex
boxShadow={["sm", "md"]} boxShadow={["sm", "md"]}
zIndex={1}
alignItems="center" alignItems="center"
id="Navbar" id="Navbar"
minH="3rem" minH="3rem"
@ -19,9 +20,12 @@ const Navbar = () => {
direction="row" direction="row"
w="100%" w="100%"
overflow="hidden" overflow="hidden"
position={"fixed"}
transition={"0.3s"}
top={"0"}
> >
{(!isAppView || !isLoggedIn) && <LandingNavbar />}
<Suspense fallback={""}> <Suspense fallback={""}>
{(!isAppView || !isLoggedIn) && <LandingNavbar />}
{isAppView && isLoggedIn && <AppNavbar />} {isAppView && isLoggedIn && <AppNavbar />}
</Suspense> </Suspense>
</Flex> </Flex>

Wyświetl plik

@ -2,14 +2,14 @@ import { Flex, Box } from "@chakra-ui/react";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { useRouter } from "../core/hooks"; import { useRouter } from "../core/hooks";
import mixpanel from "mixpanel-browser"; import mixpanel from "mixpanel-browser";
import { useXarrow, Xwrapper } from "react-xarrows";
const Scrollable = (props) => { const Scrollable = (props) => {
const scrollerRef = useRef(); const scrollerRef = useRef();
const router = useRouter(); const router = useRouter();
const [path, setPath] = useState(); const [path, setPath] = useState();
const updateXarrow = useXarrow();
const [scrollDepth, setScrollDepth] = useState(0); const [scrollDepth, setScrollDepth] = useState(0);
const [y, setY] = useState(0);
const [dir, setDir] = useState(0);
const getScrollPrecent = ({ currentTarget }) => { const getScrollPrecent = ({ currentTarget }) => {
const scroll_level = const scroll_level =
@ -19,8 +19,20 @@ const Scrollable = (props) => {
}; };
const handleScroll = (e) => { const handleScroll = (e) => {
updateXarrow();
const currentScroll = Math.ceil(getScrollPrecent(e) / 10); const currentScroll = Math.ceil(getScrollPrecent(e) / 10);
if (currentScroll) {
if (currentScroll != y) {
setDir(y - currentScroll);
setY(currentScroll);
}
}
if (dir === -1) {
document.getElementById("Navbar").style.top = "-48px";
} else {
document.getElementById("Navbar").style.top = "-0";
}
setY(currentScroll);
if (currentScroll > scrollDepth) { if (currentScroll > scrollDepth) {
setScrollDepth(currentScroll); setScrollDepth(currentScroll);
mixpanel?.get_distinct_id() && mixpanel?.get_distinct_id() &&
@ -51,17 +63,15 @@ const Scrollable = (props) => {
overflowY="hidden" overflowY="hidden"
maxH="100%" maxH="100%"
> >
<Xwrapper> <Box
<Box className="Scrollable"
className="Scrollable" direction="column"
direction="column" ref={scrollerRef}
ref={scrollerRef} overflowY="scroll"
overflowY="scroll" onScroll={(e) => handleScroll(e)}
onScroll={(e) => handleScroll(e)} >
> {props.children}
{props.children} </Box>
</Box>
</Xwrapper>
</Flex> </Flex>
); );
}; };

Wyświetl plik

@ -25,7 +25,7 @@ import {
LockIcon, LockIcon,
} from "@chakra-ui/icons"; } from "@chakra-ui/icons";
import { MdSettings, MdDashboard, MdTimeline } from "react-icons/md"; import { MdSettings, MdDashboard, MdTimeline } from "react-icons/md";
import { WHITE_LOGO_W_TEXT_URL, ALL_NAV_PATHES } from "../core/constants"; import { WHITE_LOGO_W_TEXT_URL, SITEMAP } from "../core/constants";
import useDashboard from "../core/hooks/useDashboard"; import useDashboard from "../core/hooks/useDashboard";
import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants"; import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants";
import OverlayContext from "../core/providers/OverlayProvider/context"; import OverlayContext from "../core/providers/OverlayProvider/context";
@ -102,14 +102,27 @@ const Sidebar = () => {
Login Login
</MenuItem> </MenuItem>
{ui.isMobileView && {ui.isMobileView &&
ALL_NAV_PATHES.map((pathToLink, linkItemIndex) => { SITEMAP.map((item, idx) => {
return ( if (item.children) {
<MenuItem key={`mobile-all-nav-path-item-${linkItemIndex}`}> return (
<RouterLink href={pathToLink.path}> <React.Fragment key={`Fragment-${idx}`}>
{pathToLink.title} {item.children.map((child, idx) => {
</RouterLink> return (
</MenuItem> <MenuItem
); key={`MenuItem-SITEMAP-${idx}`}
onClick={() => {
ui.setSidebarToggled(false);
}}
>
<RouterLink href={child.path}>
{child.title}
</RouterLink>
</MenuItem>
);
})}
</React.Fragment>
);
}
})} })}
</> </>
)} )}
@ -134,6 +147,9 @@ const Sidebar = () => {
<MenuItem <MenuItem
icon={<MdDashboard />} icon={<MdDashboard />}
key={`dashboard-link-${idx}`} key={`dashboard-link-${idx}`}
onClick={() => {
ui.setSidebarToggled(false);
}}
> >
<RouterLink href={`/dashboard/${dashboard?.id}`}> <RouterLink href={`/dashboard/${dashboard?.id}`}>
{dashboard.resource_data.name} {dashboard.resource_data.name}
@ -148,11 +164,12 @@ const Sidebar = () => {
variant="solid" variant="solid"
colorScheme="orange" colorScheme="orange"
size="sm" size="sm"
onClick={() => onClick={() => {
overlay.toggleModal({ overlay.toggleModal({
type: MODAL_TYPES.NEW_DASHBOARD_FLOW, type: MODAL_TYPES.NEW_DASHBOARD_FLOW,
}) });
} ui.setSidebarToggled(false);
}}
// w="100%" // w="100%"
// borderRadius={0} // borderRadius={0}
> >
@ -175,13 +192,28 @@ const Sidebar = () => {
<Divider color="gray.300" w="100%" /> <Divider color="gray.300" w="100%" />
{ui.isLoggedIn && ( {ui.isLoggedIn && (
<Menu iconShape="square"> <Menu iconShape="square">
<MenuItem icon={<MdSettings />}> <MenuItem
icon={<MdSettings />}
onClick={() => {
ui.setSidebarToggled(false);
}}
>
<RouterLink href="/subscriptions">Subscriptions </RouterLink> <RouterLink href="/subscriptions">Subscriptions </RouterLink>
</MenuItem> </MenuItem>
<MenuItem icon={<MdTimeline />}> <MenuItem
icon={<MdTimeline />}
onClick={() => {
ui.setSidebarToggled(false);
}}
>
<RouterLink href="/stream">Stream</RouterLink> <RouterLink href="/stream">Stream</RouterLink>
</MenuItem> </MenuItem>
<MenuItem icon={<LockIcon />}> <MenuItem
icon={<LockIcon />}
onClick={() => {
ui.setSidebarToggled(false);
}}
>
<RouterLink href="/account/tokens">API Tokens</RouterLink> <RouterLink href="/account/tokens">API Tokens</RouterLink>
</MenuItem> </MenuItem>
<Divider /> <Divider />

Wyświetl plik

@ -40,6 +40,7 @@ const SignUp = ({ toggleModal }) => {
colorScheme="blue" colorScheme="blue"
placeholder="Your username here" placeholder="Your username here"
name="username" name="username"
autoComplete="username"
ref={register({ required: "Username is required!" })} ref={register({ required: "Username is required!" })}
/> />
<InputRightElement> <InputRightElement>
@ -57,6 +58,7 @@ const SignUp = ({ toggleModal }) => {
colorScheme="blue" colorScheme="blue"
placeholder="Your email here" placeholder="Your email here"
name="email" name="email"
autoComplete="email"
ref={register({ required: "Email is required!" })} ref={register({ required: "Email is required!" })}
/> />
<InputRightElement> <InputRightElement>
@ -71,6 +73,7 @@ const SignUp = ({ toggleModal }) => {
<PasswordInput <PasswordInput
placeholder="Add password" placeholder="Add password"
name="password" name="password"
autoComplete="new-password"
ref={register({ required: "Password is required!" })} ref={register({ required: "Password is required!" })}
/> />
<FormErrorMessage color="red.400" pl="1"> <FormErrorMessage color="red.400" pl="1">

Wyświetl plik

@ -12,11 +12,15 @@ import {
Button, Button,
useBreakpointValue, useBreakpointValue,
useToken, useToken,
chakra,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import React, { useContext } from "react"; import React, { useContext } from "react";
import UIContext from "../core/providers/UIProvider/context"; import UIContext from "../core/providers/UIProvider/context";
import { FaDiscord, FaGithubSquare } from "react-icons/fa"; import { FaDiscord, FaGithubSquare } from "react-icons/fa";
import RouteButton from "../components/RouteButton"; import RouteButton from "../components/RouteButton";
import mixpanel from "mixpanel-browser";
import MIXPANEL_EVENTS from "../core/providers/AnalyticsProvider/constants";
import { useRouter } from "../core/hooks";
const Feature = ({ text, icon, iconBg, bullets }) => { const Feature = ({ text, icon, iconBg, bullets }) => {
return ( return (
@ -47,7 +51,7 @@ const Feature = ({ text, icon, iconBg, bullets }) => {
text={bullet.text} text={bullet.text}
{...bullet} {...bullet}
icon={ icon={
<Icon as={bullet.icon} color={bullet.color} w={5} h={5} /> <Icon as={bullet.icon} color={bullet.color} w={16} h={16} />
} }
/> />
); );
@ -58,7 +62,7 @@ const Feature = ({ text, icon, iconBg, bullets }) => {
); );
}; };
const SplitWithImage = ({ const _SplitWithImage = ({
badge, badge,
title, title,
body, body,
@ -71,7 +75,10 @@ const SplitWithImage = ({
socialButton, socialButton,
imgBoxShadow, imgBoxShadow,
py, py,
...props
}) => { }) => {
const router = useRouter();
var buttonSize = useBreakpointValue({ var buttonSize = useBreakpointValue({
base: { single: "sm", double: "xs" }, base: { single: "sm", double: "xs" },
sm: { single: "md", double: "sm" }, sm: { single: "md", double: "sm" },
@ -116,6 +123,7 @@ const SplitWithImage = ({
py={py} py={py}
className={`fade-in-section ${isVisible ? "is-visible" : ""}`} className={`fade-in-section ${isVisible ? "is-visible" : ""}`}
ref={domRef} ref={domRef}
{...props}
> >
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={[0, 0, 10, null, 10]}> <SimpleGrid columns={{ base: 1, md: 2 }} spacing={[0, 0, 10, null, 10]}>
{mirror && !ui.isMobileView && ( {mirror && !ui.isMobileView && (
@ -166,7 +174,7 @@ const SplitWithImage = ({
<Feature <Feature
key={`splitWImageBullet-${idx}-${title}`} key={`splitWImageBullet-${idx}-${title}`}
icon={ icon={
<Icon as={bullet.icon} color={bullet.color} w={5} h={5} /> <Icon as={bullet.icon} color={bullet.color} w={16} h={16} />
} }
iconBg={bullet.bgColor} iconBg={bullet.bgColor}
text={bullet.text} text={bullet.text}
@ -186,6 +194,16 @@ const SplitWithImage = ({
w={["100%", "100%", "fit-content", null]} w={["100%", "100%", "fit-content", null]}
maxW={["250px", null, "fit-content"]} maxW={["250px", null, "fit-content"]}
href={socialButton.url} href={socialButton.url}
onClick={() => {
if (mixpanel.get_distinct_id()) {
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
full_url: router.nextRouter.asPath,
buttonName: `${socialButton.title}`,
page: `splitWImage`,
section: `${badge}`,
});
}
}}
mt={[0, 0, null, 16]} mt={[0, 0, null, 16]}
size={socialButton ? buttonSize.double : buttonSize.single} size={socialButton ? buttonSize.double : buttonSize.single}
variant="outline" variant="outline"
@ -206,7 +224,18 @@ const SplitWithImage = ({
variant="outline" variant="outline"
mt={[0, 0, null, 16]} mt={[0, 0, null, 16]}
size={socialButton ? buttonSize.double : buttonSize.single} size={socialButton ? buttonSize.double : buttonSize.single}
onClick={cta.onClick} onClick={() => {
if (mixpanel.get_distinct_id()) {
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
full_url: router.nextRouter.asPath,
buttonName: `${cta.label}`,
page: `splitWImage`,
section: `${badge}`,
});
}
cta.onClick();
}}
> >
{cta.label} {cta.label}
</Button> </Button>
@ -230,5 +259,6 @@ const SplitWithImage = ({
</Container> </Container>
); );
}; };
const SplitWithImage = chakra(_SplitWithImage);
export default SplitWithImage; export default SplitWithImage;

Wyświetl plik

@ -0,0 +1,361 @@
import React, { useContext } from "react";
import { IconButton } from "@chakra-ui/react";
import {
Td,
Tr,
Tooltip,
Editable,
EditableInput,
EditablePreview,
Image,
Button,
AccordionItem,
AccordionButton,
AccordionPanel,
AccordionIcon,
Flex,
Text,
Spacer,
Stack,
} from "@chakra-ui/react";
import { CheckIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons";
import moment from "moment";
import CopyButton from "./CopyButton";
import { useSubscriptions } from "../core/hooks";
import ConfirmationRequest from "./ConfirmationRequest";
import ColorSelector from "./ColorSelector";
import OverlayContext from "../core/providers/OverlayProvider/context";
import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants";
const mapper = {
"tag:erc721": "NFTs",
"input:address": "Address",
};
const SubscriptionCard = ({ subscription, isDesktopView, iconLink }) => {
const overlay = useContext(OverlayContext);
const { updateSubscription, deleteSubscription, subscriptionTypeNames } =
useSubscriptions();
const [_isLoading, _setIsLoading] = React.useState(
updateSubscription.isLoading
);
const updateCallback = ({ id, label, color }) => {
const data = { id: id };
label && (data.label = label);
color && (data.color = color);
updateSubscription.mutate(data);
};
const cellProps = {
px: ["16px", "8px", "16px"],
};
React.useEffect(() => {
console.log("update subscription ue");
if (updateSubscription.isLoading) _setIsLoading(true);
else _setIsLoading(false);
}, [updateSubscription.isLoading]);
return (
<>
{!isDesktopView && (
<AccordionItem
bgColor="blue.50"
borderBottomColor="blue.500"
key={`token-row-${subscription.id}`}
>
<h2>
<AccordionButton>
<Stack
direction="row"
textAlign="center"
alignItems="center"
w="100%"
>
<Tooltip
label={`${
subscriptionTypeNames[subscription.subscription_type_id]
}`}
fontSize="md"
>
<Image
h={["32px", "16px", "32px", null]}
src={iconLink}
alt="pool icon"
/>
</Tooltip>
<Text>{subscription.label}</Text>
{/* <Input
w="100%"
colorScheme="blue"
placeholder="enter note here"
// isDisabled={!isEditing}
isReadOnly={isEditing}
// isPreviewFocusable={false}
defaultValue={subscription.label}
onSubmit={handleSubmit}
value={inputState}
onChange={(e) => {
setInputState(e.target.value);
}}
// onClick={() => { setIsEditing(true) }}
ref={inputRef}
variant="outline"
></Input> */}
</Stack>
<AccordionIcon />
</AccordionButton>
</h2>
<AccordionPanel pb={4} bgColor="blue.100" boxShadow="md">
<Stack>
<Stack fontSize="sm" h="min-content" pr={0}>
<Text placeSelf="flex-start">Address:</Text>
{/* <Spacer /> */}
{subscription.address?.startsWith("tag") ? (
<CopyButton size="xs" copyString={subscription.address}>
{mapper[subscription.address]}
</CopyButton>
) : (
<Flex
alignItems="center"
size="xs"
position="relative"
maxWidth="200px"
// w="100%"
flexGrow={1}
>
<CopyButton copyString={subscription.address}></CopyButton>
<Text
isTruncated
dataFileType={subscription.address.slice(-3)}
whiteSpace="nowrap"
textOverflow="ellipsis"
overflow="hidden"
_after={{
content: "attr(datafiletype)",
position: "absolute",
top: 0,
left: "100%",
pt: "9px",
marginLeft: "-2px",
}}
>
{subscription.address}
</Text>
</Flex>
)}
</Stack>
<Flex
fontSize="sm"
placeContent="center"
h="min-content"
alignItems="center"
pr={0}
>
<Text>Abi:</Text>
<Spacer />
{subscription.abi ? (
<CheckIcon />
) : (
// <Button
// colorScheme="orange"
// size="xs"
// py={2}
// disabled={!subscription.address}
// onClick={() =>
// overlay.toggleModal({
// type: MODAL_TYPES.UPLOAD_ABI,
// props: { id: subscription.id },
// })
// }
// >
// Upload
// </Button>
<Text>Not Available</Text>
)}
</Flex>
<Flex fontSize="sm" placeContent="center" h="min-content" pr={0}>
<Spacer />
<Flex
alignItems="center"
direction="column"
spacing={0}
w="100%"
>
{/* {isEditing && (
<ButtonGroup justifyContent="center" size="sm">
<IconButton icon={<CheckIcon />} onClick={handleSubmit} />
<IconButton
icon={<CloseIcon />}
onClick={() => setIsEditing(false)}
/>
</ButtonGroup>
)} */}
<Button
m={0}
borderRadius={0}
borderTopRadius="md"
w="100%"
colorScheme="blue"
size="xs"
py={2}
disabled={!subscription.address}
onClick={() => {
console.log("rename button clicked");
overlay.toggleModal({
type: MODAL_TYPES.MOBILE_INPUT_FIELD,
props: {
title: "New name",
initialValue: subscription.label,
cancelText: "Cancel",
submitText: "Rename",
id: subscription.id,
},
_key: `rename-subscription-${subscription.id}-${_isLoading}`,
});
// setIsEditing(true);
}}
leftIcon={<EditIcon />}
>
Rename
</Button>
<Button
m={0}
borderRadius={0}
w="100%"
colorScheme="orange"
size="xs"
py={2}
disabled={!subscription.address}
onClick={() =>
overlay.toggleModal({
type: MODAL_TYPES.UPLOAD_ABI,
props: { id: subscription.id },
})
}
leftIcon={<DeleteIcon />}
>
Upload Abi
</Button>
<ConfirmationRequest
bodyMessage={"please confirm"}
header={"Delete subscription"}
onConfirm={() => deleteSubscription.mutate(subscription.id)}
>
<Button
m={0}
borderRadius={0}
borderBottomRadius="md"
w="100%"
colorScheme="red"
size="xs"
py={2}
disabled={!subscription.address}
leftIcon={<DeleteIcon />}
>
Delete
</Button>
</ConfirmationRequest>
</Flex>
</Flex>
</Stack>
</AccordionPanel>
</AccordionItem>
)}
{isDesktopView && (
<Tr key={`token-row-${subscription.id}`}>
<Td {...cellProps}>
<Tooltip
label={`${
subscriptionTypeNames[subscription.subscription_type_id]
}`}
fontSize="md"
>
<Image
h={["32px", "16px", "32px", null]}
src={iconLink}
alt="pool icon"
/>
</Tooltip>
</Td>
<Td py={0} {...cellProps} wordBreak="break-word">
<Editable
colorScheme="blue"
placeholder="enter note here"
defaultValue={subscription.label}
onSubmit={(nextValue) =>
updateCallback({
id: subscription.id,
label: nextValue,
})
}
>
<EditablePreview maxW="40rem" _placeholder={{ color: "black" }} />
<EditableInput maxW="40rem" />
</Editable>
</Td>
<Td mr={4} p={0} wordBreak="break-word" {...cellProps}>
{subscription.address?.startsWith("tag") ? (
<CopyButton>{mapper[subscription.address]}</CopyButton>
) : (
<CopyButton>{subscription.address}</CopyButton>
)}
</Td>
<Td mr={4} p={0} {...cellProps}>
{subscription.abi ? (
<CheckIcon />
) : (
<Button
colorScheme="orange"
size="xs"
py={2}
disabled={!subscription.address}
onClick={() =>
overlay.toggleModal({
type: MODAL_TYPES.UPLOAD_ABI,
props: { id: subscription.id },
})
}
>
Upload
</Button>
)}
</Td>
<Td {...cellProps}>
<ColorSelector
// subscriptionId={subscription.id}
initialColor={subscription.color}
callback={(color) =>
updateCallback({ id: subscription.id, color: color })
}
/>
</Td>
<Td py={0} {...cellProps} wordBreak="break-word">
{moment(subscription.created_at).format("L")}
</Td>
<Td py={0} {...cellProps}>
<ConfirmationRequest
bodyMessage={"please confirm"}
header={"Delete subscription"}
onConfirm={() => deleteSubscription.mutate(subscription.id)}
>
<IconButton
size="sm"
variant="ghost"
colorScheme="blue"
icon={<DeleteIcon />}
/>
</ConfirmationRequest>
</Td>
</Tr>
)}
</>
);
};
export default SubscriptionCard;

Wyświetl plik

@ -1,48 +1,24 @@
import React, { useContext } from "react"; import React from "react";
import { Skeleton, IconButton, Container } from "@chakra-ui/react"; import { Skeleton, Container } from "@chakra-ui/react";
import { import {
Table, Table,
Th, Th,
Td,
Tr, Tr,
Thead, Thead,
Tbody, Tbody,
Tooltip,
Editable,
EditableInput,
EditablePreview,
Image,
Button, Button,
useMediaQuery,
Accordion,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { CheckIcon, DeleteIcon } from "@chakra-ui/icons";
import moment from "moment";
import CopyButton from "./CopyButton";
import { useSubscriptions } from "../core/hooks"; import { useSubscriptions } from "../core/hooks";
import ConfirmationRequest from "./ConfirmationRequest"; import SubscriptionCard from "./SubscriptionCard";
import ColorSelector from "./ColorSelector";
import OverlayContext from "../core/providers/OverlayProvider/context";
import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants";
const mapper = {
"tag:erc721": "NFTs",
"input:address": "Address",
};
const SubscriptionsList = ({ emptyCTA }) => { const SubscriptionsList = ({ emptyCTA }) => {
const overlay = useContext(OverlayContext); const [isLargerThan530px] = useMediaQuery(["(min-width: 530px)"]);
const { const { subscriptionsCache, subscriptionTypeIcons } = useSubscriptions();
subscriptionsCache,
updateSubscription,
deleteSubscription,
subscriptionTypeIcons,
subscriptionTypeNames,
} = useSubscriptions();
const updateCallback = ({ id, label, color }) => { const cellProps = {
const data = { id: id }; px: ["16px", "8px", "16px"],
label && (data.label = label);
color && (data.color = color);
updateSubscription.mutate(data);
}; };
if ( if (
@ -50,121 +26,65 @@ const SubscriptionsList = ({ emptyCTA }) => {
subscriptionsCache.data.subscriptions.length > 0 subscriptionsCache.data.subscriptions.length > 0
) { ) {
return ( return (
<Table <>
borderColor="gray.200" {isLargerThan530px && (
borderWidth="1px" <Table
variant="simple" borderColor="gray.200"
colorScheme="blue" borderWidth="1px"
justifyContent="center" variant="simple"
borderBottomRadius="xl" colorScheme="blue"
alignItems="baseline" justifyContent="center"
h="auto" borderBottomRadius="xl"
size="sm" alignItems="baseline"
mt={0} h="auto"
> size="sm"
<Thead> mt={0}
<Tr> >
<Th>Token</Th> <Thead>
<Th>Label</Th> <Tr>
<Th>Address</Th> <Th {...cellProps}>Token</Th>
<Th>abi</Th> <Th {...cellProps}>Label</Th>
<Th>Color</Th> <Th {...cellProps}>Address</Th>
<Th>Date Created</Th> <Th {...cellProps}>abi</Th>
<Th>Actions</Th> <Th {...cellProps}>Color</Th>
</Tr> <Th {...cellProps}>Date Created</Th>
</Thead> <Th {...cellProps}>Actions</Th>
<Tbody>
{subscriptionsCache.data.subscriptions.map((subscription) => {
const iconLink =
subscriptionTypeIcons[subscription.subscription_type_id];
return (
<Tr key={`token-row-${subscription.id}`}>
<Td>
<Tooltip
label={`${
subscriptionTypeNames[subscription.subscription_type_id]
}`}
fontSize="md"
>
<Image h="32px" src={iconLink} alt="pool icon" />
</Tooltip>
</Td>
<Td py={0}>
<Editable
colorScheme="blue"
placeholder="enter note here"
defaultValue={subscription.label}
onSubmit={(nextValue) =>
updateCallback({
id: subscription.id,
label: nextValue,
})
}
>
<EditablePreview
maxW="40rem"
_placeholder={{ color: "black" }}
/>
<EditableInput maxW="40rem" />
</Editable>
</Td>
<Td mr={4} p={0}>
{subscription.address?.startsWith("tag") ? (
<CopyButton>{mapper[subscription.address]}</CopyButton>
) : (
<CopyButton>{subscription.address}</CopyButton>
)}
</Td>
<Td mr={4} p={0}>
{subscription.abi ? (
<CheckIcon />
) : (
<Button
colorScheme="orange"
size="xs"
py={2}
disabled={!subscription.address}
onClick={() =>
overlay.toggleModal({
type: MODAL_TYPES.UPLOAD_ABI,
props: { id: subscription.id },
})
}
>
Upload
</Button>
)}
</Td>
<Td>
<ColorSelector
// subscriptionId={subscription.id}
initialColor={subscription.color}
callback={(color) =>
updateCallback({ id: subscription.id, color: color })
}
/>
</Td>
<Td py={0}>{moment(subscription.created_at).format("L")}</Td>
<Td py={0}>
<ConfirmationRequest
bodyMessage={"please confirm"}
header={"Delete subscription"}
onConfirm={() => deleteSubscription.mutate(subscription.id)}
>
<IconButton
size="sm"
variant="ghost"
colorScheme="blue"
icon={<DeleteIcon />}
/>
</ConfirmationRequest>
</Td>
</Tr> </Tr>
); </Thead>
})}
</Tbody> <Tbody>
</Table> {subscriptionsCache.data.subscriptions.map((subscription) => {
const iconLink =
subscriptionTypeIcons[subscription.subscription_type_id];
return (
<SubscriptionCard
key={`token-row-${subscription.id}`}
subscription={subscription}
isDesktopView={isLargerThan530px}
iconLink={iconLink}
/>
);
})}
</Tbody>
</Table>
)}
{!isLargerThan530px && (
<Accordion allowToggle={true}>
{subscriptionsCache.data.subscriptions.map((subscription) => {
const iconLink =
subscriptionTypeIcons[subscription.subscription_type_id];
return (
<SubscriptionCard
key={`token-row-${subscription.id}`}
subscription={subscription}
isDesktopView={isLargerThan530px}
iconLink={iconLink}
/>
);
})}
</Accordion>
)}
</>
); );
} else if ( } else if (
subscriptionsCache.data && subscriptionsCache.data &&

Wyświetl plik

@ -1,5 +1,12 @@
import { React } from "react"; import { React } from "react";
import { Flex, Image, Link, LinkBox, LinkOverlay } from "@chakra-ui/react"; import {
Flex,
Image,
Link,
LinkBox,
LinkOverlay,
chakra,
} from "@chakra-ui/react";
const TrustedBadge = ({ const TrustedBadge = ({
name, name,
@ -8,10 +15,15 @@ const TrustedBadge = ({
scale, scale,
isGrayScale, isGrayScale,
boxURL, boxURL,
invertColors,
...props
}) => { }) => {
const _scale = scale ?? 1; const _scale = scale ?? 1;
const _isGrayScale = isGrayScale ? "grayscale(100%)" : "";
const _invert = invertColors ? "invert(100%)" : "";
const filterStr = _isGrayScale + " " + _invert;
return ( return (
<LinkBox m={2}> <LinkBox m={2} borderRadius="md" {...props}>
<LinkOverlay href={boxURL} isExternal> <LinkOverlay href={boxURL} isExternal>
<Flex <Flex
m={1} m={1}
@ -23,7 +35,7 @@ const TrustedBadge = ({
direction="column" direction="column"
> >
<Image <Image
sx={isGrayScale && { filter: "grayscale(100%)" }} sx={{ filter: filterStr }}
h={[ h={[
`${2.25 * _scale}rem`, `${2.25 * _scale}rem`,
null, null,
@ -51,4 +63,4 @@ const TrustedBadge = ({
</LinkBox> </LinkBox>
); );
}; };
export default TrustedBadge; export default chakra(TrustedBadge);

Wyświetl plik

@ -0,0 +1,82 @@
import React, { useContext, useState, useEffect, useRef } from "react";
import { Button, Input } from "@chakra-ui/react";
import OverlayContext from "../core/providers/OverlayProvider/context";
import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants";
import { useSubscriptions } from "../core/hooks";
const MobileFiledInput = ({
onChange,
initialValue,
cancelText,
submitText,
id,
}) => {
const { updateSubscription } = useSubscriptions();
const isLoading = updateSubscription.isLoading;
const [value, setValue] = useState(initialValue);
const updateCallback = () => {
const data = { id: id };
value && (data.label = value);
updateSubscription.mutate(data);
};
const [wasSubmitted, setWasSubmitted] = useState(false);
console.log("MobileFiledInput", isLoading, wasSubmitted);
const inputRef = useRef();
const handleChange = (e) => {
setValue(e.target.value);
onChange && onChange(e);
};
const overlay = useContext(OverlayContext);
const handleSubmit = (e) => {
e.preventDefault();
updateCallback({});
};
useEffect(() => {
if (isLoading) {
setWasSubmitted(true);
}
}, [isLoading]);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
useEffect(() => {
if (!isLoading && wasSubmitted) {
overlay.toggleModal({ type: MODAL_TYPES.OFF });
setWasSubmitted(false);
}
}, [isLoading, overlay, wasSubmitted]);
return (
<>
<Input
ref={inputRef}
type="text"
value={value}
onChange={handleChange}
placeholder="Enter a value"
/>
<Button
colorScheme="green"
onClick={(e) => handleSubmit(e)}
isLoading={isLoading}
>
{submitText}
</Button>
<Button
isDisabled={isLoading}
onClick={() => overlay.toggleModal({ type: MODAL_TYPES.OFF })}
colorScheme="blue"
>
{cancelText}
</Button>
</>
);
};
export default MobileFiledInput;

Wyświetl plik

@ -6,51 +6,87 @@ export const BUGOUT_ENDPOINTS = {
}; };
export const DEFAULT_METATAGS = { export const DEFAULT_METATAGS = {
title: "Moonstream: Building blocks for your blockchain economy", title: "Moonstream: Building blocks for your blockchain game",
description: description:
"Moonstream DAO makes tools that help you build, manage, and secure your blockchain economy.", "Moonstream DAO makes tools that help you build, manage, and secure your blockchain game.",
keywords: keywords:
"analytics, blockchain analytics, protocol, protocols, blockchain, crypto, data, NFT gaming, smart contracts, web3, smart contract, ethereum, polygon, matic, transactions, defi, finance, decentralized, mempool, NFT, NFTs, DAO, DAOs, cryptocurrency, cryptocurrencies, bitcoin, blockchain economy, marketplace, blockchain security, loyalty program, Ethereum bridge, Ethereum bridges, NFT game, NFT games", "analytics, blockchain analytics, protocol, protocols, blockchain, crypto, data, NFT gaming, smart contracts, web3, smart contract, ethereum, polygon, matic, transactions, defi, finance, decentralized, mempool, NFT, NFTs, DAO, DAOs, cryptocurrency, cryptocurrencies, bitcoin, blockchain economy, blockchain game, marketplace, blockchain security, loyalty program, Ethereum bridge, Ethereum bridges, NFT game, NFT games",
url: "https://www.moonstream.to", url: "https://www.moonstream.to",
image: `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/crypto+traders.png`, image: `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/crypto+traders.png`,
}; };
export const FOOTER_COLUMNS = { // export const FOOTER_COLUMNS = {
NEWS: "News", // NEWS: "News",
COMPANY: "Company", // COMPANY: "Company",
PRODUCT: "Product", // PRODUCT: "Product",
// };
export const SIITEMAP_CATEGORIES = {
SOLUTIONS: "Solutions",
DEVELOPERS: "Developers",
RESOURCES: "Resources",
ABOUT: "About",
}; };
export const ALL_NAV_PATHES = [ export const PAGETYPE = {
EMPTY: 0,
CONTENT: 1,
EXTERNAL: 2,
};
export const SITEMAP = [
{ {
title: "Product", title: "Resources",
path: "/product", path: "/resources",
footerCategory: FOOTER_COLUMNS.PRODUCT, type: PAGETYPE.EMPTY,
children: [
{
title: "Case studies",
path: "https://docs.google.com/document/d/1mjfF8SgRrAZvtCVVxB2qNSUcbbmrH6dTEYSMfHKdEgc",
type: PAGETYPE.EXTERNAL,
},
{
title: "Whitepapers",
path: "/whitepapers",
type: PAGETYPE.CONTENT,
},
{
title: "Blog",
path: "https://blog.moonstream.to",
type: PAGETYPE.EXTERNAL,
},
],
}, },
{ {
title: "Team", title: "Developers",
path: "/team", path: "/developers",
footerCategory: FOOTER_COLUMNS.COMPANY, type: PAGETYPE.EMPTY,
children: [
{
title: "Docs",
path: "/docs",
type: PAGETYPE.CONTENT,
},
{
title: "Status",
path: "/status",
type: PAGETYPE.CONTENT,
},
],
}, },
{ {
title: "Docs", title: "About",
path: "/docs", path: "/about",
footerCategory: FOOTER_COLUMNS.PRODUCT, type: PAGETYPE.EMPTY,
}, children: [
{ {
title: "Whitepapers", title: "Team",
path: "/whitepapers", path: "/team",
footerCategory: FOOTER_COLUMNS.PRODUCT, type: PAGETYPE.CONTENT,
}, },
{ ],
title: "Blog",
path: "https://blog.moonstream.to",
footerCategory: FOOTER_COLUMNS.NEWS,
},
{
title: "Status",
path: "/status",
footerCategory: FOOTER_COLUMNS.PRODUCT,
}, },
]; ];

Wyświetl plik

@ -7,18 +7,6 @@ const useStatus = () => {
const response = await StatusService.serverListStatus(); const response = await StatusService.serverListStatus();
return response.data; 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 serverListStatusCache = useQuery( const serverListStatusCache = useQuery(
"serverListStatus", "serverListStatus",
@ -28,28 +16,9 @@ const useStatus = () => {
retry: 0, retry: 0,
} }
); );
const crawlersStatusCache = useQuery("crawlers", getCrawlersStatus, {
...queryCacheProps,
retry: 0,
});
const dbServerStatusCache = useQuery("dbServer", getDBServerStatus, {
...queryCacheProps,
retry: 0,
});
const latestBlockDBStatusCache = useQuery(
"latestBlockDB",
getLatestBlockDBStatus,
{
...queryCacheProps,
retry: 0,
}
);
return { return {
serverListStatusCache, serverListStatusCache,
crawlersStatusCache,
dbServerStatusCache,
latestBlockDBStatusCache,
}; };
}; };

Wyświetl plik

@ -5,6 +5,8 @@ import { useClientID, useUser, useRouter } from "../../hooks";
import { MIXPANEL_EVENTS, MIXPANEL_PROPS } from "./constants"; import { MIXPANEL_EVENTS, MIXPANEL_PROPS } from "./constants";
import UIContext from "../UIProvider/context"; import UIContext from "../UIProvider/context";
const TELEMETRY_SCHEMA_VERSION = 1.0;
const AnalyticsProvider = ({ children }) => { const AnalyticsProvider = ({ children }) => {
const clientID = useClientID(); const clientID = useClientID();
const analytics = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN; const analytics = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
@ -138,6 +140,7 @@ const AnalyticsProvider = ({ children }) => {
loaded: () => { loaded: () => {
setIsLoaded(true); setIsLoaded(true);
mixpanel.identify(clientID); mixpanel.identify(clientID);
mixpanel.register({ schema_version: TELEMETRY_SCHEMA_VERSION });
}, },
}); });
} catch (error) { } catch (error) {

Wyświetl plik

@ -9,6 +9,7 @@ export const MODAL_TYPES = {
NEW_SUBSCRIPTON: 7, NEW_SUBSCRIPTON: 7,
UPLOAD_ABI: 8, UPLOAD_ABI: 8,
NEW_DASHBOARD_FLOW: 9, NEW_DASHBOARD_FLOW: 9,
MOBILE_INPUT_FIELD: 10,
}; };
export const DRAWER_TYPES = { export const DRAWER_TYPES = {

Wyświetl plik

@ -38,6 +38,7 @@ import SignUp from "../../../components/SignUp";
import NewDashboardChart from "../../../components/NewDashboardChart"; import NewDashboardChart from "../../../components/NewDashboardChart";
import { useRouter } from "../../hooks"; import { useRouter } from "../../hooks";
import { DASHBOARD_UPDATE_ACTIONS } from "../../constants"; import { DASHBOARD_UPDATE_ACTIONS } from "../../constants";
import UpdateSubscriptionLabelInput from "../../../components/UpdateSubscriptionLabelInput";
const NewDashboardName = React.lazy(() => const NewDashboardName = React.lazy(() =>
import("../../../components/NewDashboardName") import("../../../components/NewDashboardName")
); );
@ -64,6 +65,7 @@ const OverlayProvider = ({ children }) => {
const [modal, toggleModal] = useState({ const [modal, toggleModal] = useState({
type: MODAL_TYPES.OFF, type: MODAL_TYPES.OFF,
props: undefined, props: undefined,
key: undefined,
}); });
const [drawer, toggleDrawer] = useState({ const [drawer, toggleDrawer] = useState({
type: DRAWER_TYPES.OFF, type: DRAWER_TYPES.OFF,
@ -224,6 +226,8 @@ const OverlayProvider = ({ children }) => {
}); });
}; };
console.log("_key:", modal._key);
return ( return (
<OverlayContext.Provider <OverlayContext.Provider
value={{ modal, toggleModal, drawer, toggleDrawer, toggleAlert }} value={{ modal, toggleModal, drawer, toggleDrawer, toggleAlert }}
@ -279,6 +283,7 @@ const OverlayProvider = ({ children }) => {
{modal.type === MODAL_TYPES.UPLOAD_ABI && "Assign ABI"} {modal.type === MODAL_TYPES.UPLOAD_ABI && "Assign ABI"}
{modal.type === MODAL_TYPES.NEW_DASHBOARD_FLOW && {modal.type === MODAL_TYPES.NEW_DASHBOARD_FLOW &&
"Would you like to give it a name?"} "Would you like to give it a name?"}
{modal.type === MODAL_TYPES.MOBILE_INPUT_FIELD && modal.props.title}
</ModalHeader> </ModalHeader>
<Divider /> <Divider />
<ModalCloseButton /> <ModalCloseButton />
@ -316,6 +321,9 @@ const OverlayProvider = ({ children }) => {
{modal.type === MODAL_TYPES.NEW_DASHBOARD_FLOW && ( {modal.type === MODAL_TYPES.NEW_DASHBOARD_FLOW && (
<NewDashboardName {...modal.props} /> <NewDashboardName {...modal.props} />
)} )}
{modal.type === MODAL_TYPES.MOBILE_INPUT_FIELD && (
<UpdateSubscriptionLabelInput {...modal.props} />
)}
</Suspense> </Suspense>
</ModalBody> </ModalBody>
</ModalContent> </ModalContent>

Wyświetl plik

@ -1,33 +1,10 @@
import { http } from "../utils"; import { http } from "../utils";
const BUGOUT_STATUS_URL = process.env.NEXT_PUBLIC_BUGOUT_STATUS_URL; const BUGOUT_STATUS_URL = process.env.NEXT_PUBLIC_BUGOUT_STATUS_URL;
const API_URL = process.env.NEXT_PUBLIC_MOONSTREAM_API_URL;
const DB_URL = process.env.NEXT_PUBLIC_MOONSTREAM_DB_URL;
export const serverListStatus = () => { export const serverListStatus = () => {
return http({ return http({
method: "GET", method: "GET",
url: `${BUGOUT_STATUS_URL}`, url: `${BUGOUT_STATUS_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`,
}); });
}; };

Wyświetl plik

@ -2,6 +2,7 @@ import { Flex, Spinner, Box } from "@chakra-ui/react";
import { getLayout as getSiteLayout } from "./RootLayout"; import { getLayout as getSiteLayout } from "./RootLayout";
import React, { useContext, useEffect } from "react"; import React, { useContext, useEffect } from "react";
import UIContext from "../core/providers/UIProvider/context"; import UIContext from "../core/providers/UIProvider/context";
import AppNavbar from "../components/AppNavbar";
const AppLayout = ({ children }) => { const AppLayout = ({ children }) => {
const ui = useContext(UIContext); const ui = useContext(UIContext);
@ -16,12 +17,12 @@ const AppLayout = ({ children }) => {
return ( return (
<Flex <Flex
direction="row"
id="JournalsWrapper" id="JournalsWrapper"
flexGrow={1} flexGrow={1}
maxH="100%" maxH="100%"
w="100%" w="100%"
overflow="hidden" overflow="hidden"
direction="column"
> >
{(!ui.isAppReady || !ui.isLoggedIn) && ( {(!ui.isAppReady || !ui.isLoggedIn) && (
<Spinner <Spinner
@ -44,7 +45,9 @@ const AppLayout = ({ children }) => {
zIndex={1010} zIndex={1010}
/> />
)} )}
<Flex direction={"column"} bgColor="blue.900" id="Navbar">
<AppNavbar />
</Flex>
{ui.isAppReady && ui.isLoggedIn && children} {ui.isAppReady && ui.isLoggedIn && children}
</Flex> </Flex>
); );

Wyświetl plik

@ -3,7 +3,6 @@ import { Flex, Center, Text, Link, IconButton } from "@chakra-ui/react";
import React, { Suspense, useContext, useState } from "react"; import React, { Suspense, useContext, useState } from "react";
import UIContext from "../core/providers/UIProvider/context"; import UIContext from "../core/providers/UIProvider/context";
const Sidebar = React.lazy(() => import("../components/Sidebar")); const Sidebar = React.lazy(() => import("../components/Sidebar"));
const Navbar = React.lazy(() => import("../components/Navbar"));
const RootLayout = (props) => { const RootLayout = (props) => {
const ui = useContext(UIContext); const ui = useContext(UIContext);
@ -27,9 +26,6 @@ const RootLayout = (props) => {
flexBasis="100px" flexBasis="100px"
overflowX="hidden" overflowX="hidden"
> >
<Suspense fallback="">
<Navbar />
</Suspense>
{!ui.isAppView && ( {!ui.isAppView && (
<Flex <Flex
w="100%" w="100%"

Wyświetl plik

@ -0,0 +1,141 @@
import React, { useState, useEffect, useLayoutEffect } from "react";
import { Flex, useMediaQuery, Stack } from "@chakra-ui/react";
import { DEFAULT_METATAGS, AWS_ASSETS_PATH } from "../core/constants";
import { getLayout as getSiteLayout } from "./index";
const assets = {
background720: `${AWS_ASSETS_PATH}/blog-background-720x405.png`,
background1920: `${AWS_ASSETS_PATH}/blog-background-720x405.png`,
background2880: `${AWS_ASSETS_PATH}/blog-background-720x405.png`,
background3840: `${AWS_ASSETS_PATH}/blog-background-720x405.png`,
};
const InfoPageLayout = ({ children }) => {
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}/blog-background-720x405.png`;
assets[
"background1920"
] = `${AWS_ASSETS_PATH}/blog-background-1920x1080.png`;
assets[
"background2880"
] = `${AWS_ASSETS_PATH}/blog-background-2880x1620.png`;
assets[
"background3840"
] = `${AWS_ASSETS_PATH}/blog-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}/blog-background-720x405.png`;
imageLoader720.onload = () => {
setBackgroundLoaded720(true);
};
}, []);
useLayoutEffect(() => {
const imageLoader1920 = new Image();
imageLoader1920.src = `${AWS_ASSETS_PATH}/blog-background-1920x1080.png`;
imageLoader1920.onload = () => {
setBackgroundLoaded1920(true);
};
}, []);
useLayoutEffect(() => {
const imageLoader2880 = new Image();
imageLoader2880.src = `${AWS_ASSETS_PATH}/blog-background-2880x1620.png`;
imageLoader2880.onload = () => {
setBackgroundLoaded2880(true);
};
}, []);
useLayoutEffect(() => {
const imageLoader3840 = new Image();
imageLoader3840.src = `${AWS_ASSETS_PATH}/blog-background-3840x2160.png`;
imageLoader3840.onload = () => {
setBackgroundLoaded3840(true);
};
}, []);
const margin = 0;
return (
<Flex
bgPos="bottom"
bgColor="transparent"
bgSize="cover"
backgroundImage={`url(${assets[`${background}`]})`}
minH="100vh"
direction="column"
alignItems="center"
w="100%"
>
<Stack
mx={margin}
my={[4, 6, 12]}
maxW="1700px"
textAlign="justify"
minH={"100vh"}
>
{children}
</Stack>
</Flex>
);
};
export const getLayout = (page) =>
getSiteLayout(<InfoPageLayout>{page}</InfoPageLayout>);
export const getLayoutProps = () => {
const assetPreload = Object.keys(assets).map((key) => {
return {
rel: "preload",
href: assets[key],
as: "image",
};
});
const preconnects = [{ rel: "preconnect", href: "https://s3.amazonaws.com" }];
const preloads = assetPreload.concat(preconnects);
return {
props: { metaTags: { ...DEFAULT_METATAGS }, preloads },
};
};
export default InfoPageLayout;

Wyświetl plik

@ -2,11 +2,12 @@ import React from "react";
import Footer from "../components/Footer"; import Footer from "../components/Footer";
import Scrollable from "../components/Scrollable"; import Scrollable from "../components/Scrollable";
import RootLayout from "./RootLayout"; import RootLayout from "./RootLayout";
import Navbar from "../components/Navbar";
const LayoutWrapper = ({ children }) => { const LayoutWrapper = ({ children }) => {
return ( return (
<RootLayout> <RootLayout>
<Scrollable> <Scrollable>
<Navbar />
{children} {children}
<Footer /> <Footer />
</Scrollable> </Scrollable>

62
nodes/.gitignore vendored
Wyświetl plik

@ -1,5 +1,65 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,go
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,go
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### Go Patch ###
/vendor/
/Godeps/
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# Support for Project snippet scope
.vscode/*.code-snippets
# Ignore code-workspaces
*.code-workspace
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,go
# Custom # Custom
.secrets/* .secrets/*
dev.env dev.env
prod.env prod.env
test.env test.env
nodebalancer

Wyświetl plik

@ -24,6 +24,8 @@ PARAMETERS_ENV_PATH="${SECRETS_DIR}/app.env"
AWS_SSM_PARAMETER_PATH="${AWS_SSM_PARAMETER_PATH:-/moonstream/prod}" AWS_SSM_PARAMETER_PATH="${AWS_SSM_PARAMETER_PATH:-/moonstream/prod}"
SCRIPT_DIR="$(realpath $(dirname $0))" SCRIPT_DIR="$(realpath $(dirname $0))"
PARAMETERS_SCRIPT="${SCRIPT_DIR}/parameters.py" PARAMETERS_SCRIPT="${SCRIPT_DIR}/parameters.py"
NODE_BALANCER_CONFIG_PATH="${NODE_BALANCER_CONFIG_PATH:-/home/ubuntu/.nodebalancer}"
NODE_BALANCER_CONFIG_SOURCE_FILE="node-balancer-config.txt"
# Service file # Service file
NODE_BALANCER_SERVICE_FILE="node-balancer.service" NODE_BALANCER_SERVICE_FILE="node-balancer.service"
@ -59,6 +61,15 @@ cd "${APP_NODES_DIR}/node_balancer"
HOME=/root /usr/local/go/bin/go build -o "${APP_NODES_DIR}/node_balancer/nodebalancer" "${APP_NODES_DIR}/node_balancer/main.go" HOME=/root /usr/local/go/bin/go build -o "${APP_NODES_DIR}/node_balancer/nodebalancer" "${APP_NODES_DIR}/node_balancer/main.go"
cd "${EXEC_DIR}" cd "${EXEC_DIR}"
echo
echo
echo -e "${PREFIX_INFO} Update nodebalancer configuration file"
if [ ! -d "$NODE_BALANCER_CONFIG_PATH" ]; then
mkdir "$NODE_BALANCER_CONFIG_PATH"
echo -e "${PREFIX_WARN} Created new node balancer config directory"
fi
cp "${SCRIPT_DIR}/${NODE_BALANCER_CONFIG_SOURCE_FILE}" "${NODE_BALANCER_CONFIG_PATH}/config.txt"
echo echo
echo echo
echo -e "${PREFIX_INFO} Replacing existing load balancer for nodes service definition with ${NODE_BALANCER_SERVICE_FILE}" echo -e "${PREFIX_INFO} Replacing existing load balancer for nodes service definition with ${NODE_BALANCER_SERVICE_FILE}"

Wyświetl plik

@ -0,0 +1,5 @@
ethereum,a.ethereum.moonstream.internal,8545
ethereum,b.ethereum.moonstream.internal,8545
polygon,a.polygon.moonstream.internal,8545
polygon,b.polygon.moonstream.internal,8545
xdai,a.xdai.moonstream.internal,8545

Wyświetl plik

@ -11,8 +11,12 @@ WorkingDirectory=/home/ubuntu/moonstream/nodes/node_balancer
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
Restart=on-failure Restart=on-failure
RestartSec=15s RestartSec=15s
ExecStart=/home/ubuntu/moonstream/nodes/node_balancer/nodebalancer -host "${AWS_LOCAL_IPV4}" -port 8544 -healthcheck ExecStart=/home/ubuntu/moonstream/nodes/node_balancer/nodebalancer server \
-host "${AWS_LOCAL_IPV4}" \
-port 8544 \
-healthcheck \
-config /home/ubuntu/.nodebalancer/config.txt
SyslogIdentifier=node-balancer SyslogIdentifier=node-balancer
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

Wyświetl plik

@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Deployment script - intended to run on Moonstream Gnosis node server
# Colors
C_RESET='\033[0m'
C_RED='\033[1;31m'
C_GREEN='\033[1;32m'
C_YELLOW='\033[1;33m'
# Logs
PREFIX_INFO="${C_GREEN}[INFO]${C_RESET} [$(date +%d-%m\ %T)]"
PREFIX_WARN="${C_YELLOW}[WARN]${C_RESET} [$(date +%d-%m\ %T)]"
PREFIX_CRIT="${C_RED}[CRIT]${C_RESET} [$(date +%d-%m\ %T)]"
# Main
AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-us-east-1}"
APP_DIR="${APP_DIR:-/home/ubuntu/moonstream}"
APP_NODES_DIR="${APP_DIR}/nodes"
SECRETS_DIR="${SECRETS_DIR:-/home/ubuntu/moonstream-secrets}"
PARAMETERS_ENV_PATH="${SECRETS_DIR}/app.env"
SCRIPT_DIR="$(realpath $(dirname $0))"
# Node status server service file
NODE_STATUS_SERVER_SERVICE_FILE="node-status.service"
# Gnosis nethermind service file
XDAI_NETHERMIND_SERVICE_FILE="xdai.service"
set -eu
echo
echo
echo -e "${PREFIX_INFO} Building executable server of node status server"
EXEC_DIR=$(pwd)
cd "${APP_NODES_DIR}/server"
HOME=/root /usr/local/go/bin/go build -o "${APP_NODES_DIR}/server/nodestatus" "${APP_NODES_DIR}/server/main.go"
cd "${EXEC_DIR}"
echo
echo
echo -e "${PREFIX_INFO} Create secrets directory"
mkdir -p "${SECRETS_DIR}"
echo
echo
echo -e "${PREFIX_INFO} Install checkenv"
HOME=/root /usr/local/go/bin/go install github.com/bugout-dev/checkenv@latest
echo
echo
echo -e "${PREFIX_INFO} Retrieving deployment parameters"
AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION}" /root/go/bin/checkenv show aws_ssm+Product:moonstream,Node:true > "${PARAMETERS_ENV_PATH}"
echo
echo
echo -e "${PREFIX_INFO} Add instance local IP to parameters"
AWS_LOCAL_IPV4="$(ec2metadata --local-ipv4)"
echo "AWS_LOCAL_IPV4=$AWS_LOCAL_IPV4" >> "${PARAMETERS_ENV_PATH}"
echo
echo
echo -e "${PREFIX_INFO} Replacing existing node status server definition with ${NODE_STATUS_SERVER_SERVICE_FILE}"
chmod 644 "${SCRIPT_DIR}/${NODE_STATUS_SERVER_SERVICE_FILE}"
cp "${SCRIPT_DIR}/${NODE_STATUS_SERVER_SERVICE_FILE}" "/etc/systemd/system/${NODE_STATUS_SERVER_SERVICE_FILE}"
systemctl daemon-reload
systemctl restart "${NODE_STATUS_SERVER_SERVICE_FILE}"
echo
echo
echo -e "${PREFIX_INFO} Replacing existing Gnosis nethermind service definition with ${XDAI_NETHERMIND_SERVICE_FILE}"
chmod 644 "${SCRIPT_DIR}/${XDAI_NETHERMIND_SERVICE_FILE}"
cp "${SCRIPT_DIR}/${XDAI_NETHERMIND_SERVICE_FILE}" "/etc/systemd/system/${XDAI_NETHERMIND_SERVICE_FILE}"
systemctl daemon-reload
systemctl disable "${XDAI_NETHERMIND_SERVICE_FILE}"
echo -e "${PREFIX_WARN} Nethermind service updated, but not restarted!"

Wyświetl plik

@ -0,0 +1,17 @@
[Unit]
Description=Moonstream node status server
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/nodes/server
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
ExecStart=/home/ubuntu/moonstream/nodes/server/nodestatus \
-blockchain xdai \
-host "${AWS_LOCAL_IPV4}" \
-port "${MOONSTREAM_NODES_SERVER_PORT}"
SyslogIdentifier=node-status
[Install]
WantedBy=multi-user.target

Wyświetl plik

@ -0,0 +1,25 @@
[Unit]
Description=Gnosis node nethermind client
StartLimitIntervalSec=500
StartLimitBurst=5
After=network.target
[Service]
Restart=on-failure
RestartSec=5s
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
ExecStart=/usr/bin/nethermind \
--config /usr/share/nethermind/configs/xdai.cfg \
--datadir /mnt/disks/nodes/xdai \
--JsonRpc.Enabled true \
--JsonRpc.EnabledModules Eth,Web3,TxPool \
--JsonRpc.Host "${AWS_LOCAL_IPV4}" \
--JsonRpc.Port 8545
Type=simple
User=ubuntu
ExecStop=/bin/kill -s SIGINT -$MAINPID
TimeoutStopSec=180
SyslogIdentifier=xdai
[Install]
WantedBy=multi-user.target

Wyświetl plik

@ -0,0 +1,143 @@
package cmd
import (
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/bugout-dev/moonstream/nodes/node_balancer/configs"
)
var (
stateCLI StateCLI
)
type flagSlice []string
func (i *flagSlice) String() string {
return strings.Join(*i, ", ")
}
func (i *flagSlice) Set(value string) error {
*i = append(*i, value)
return nil
}
// Command Line Interface state
type StateCLI struct {
serverCmd *flag.FlagSet
versionCmd *flag.FlagSet
// Common flags
configPathFlag string
helpFlag bool
// Server flags
listeningAddrFlag string
listeningPortFlag string
nodesFlag flagSlice
enableHealthCheckFlag bool
enableDebugFlag bool
}
func (s *StateCLI) usage() {
fmt.Printf(`usage: nodebalancer [-h] {%[1]s,%[2]s} ...
Moonstream node balancer CLI
optional arguments:
-h, --help show this help message and exit
subcommands:
{%[1]s,%[2]s}
`, s.serverCmd.Name(), s.versionCmd.Name())
}
func (s *StateCLI) checkRequirements() {
if s.helpFlag {
switch {
case s.serverCmd.Parsed():
fmt.Printf("Start nodebalancer server\n\n")
s.serverCmd.PrintDefaults()
os.Exit(0)
case s.versionCmd.Parsed():
fmt.Printf("Show version\n\n")
s.versionCmd.PrintDefaults()
os.Exit(0)
default:
s.usage()
os.Exit(0)
}
}
if s.configPathFlag == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatalf("Unable to find user home directory, %v", err)
}
configDirPath := fmt.Sprintf("%s/.nodebalancer", homeDir)
configPath := fmt.Sprintf("%s/config.txt", configDirPath)
err = os.MkdirAll(configDirPath, os.ModePerm)
if err != nil {
log.Fatalf("Unable to create directory, %v", err)
}
_, err = os.Stat(configPath)
if err != nil {
tempConfigB := []byte("ethereum,http://127.0.0.1,8545")
err = os.WriteFile(configPath, tempConfigB, 0644)
if err != nil {
log.Fatalf("Unable to write config, %v", err)
}
}
s.configPathFlag = configPath
}
}
func (s *StateCLI) populateCLI() {
// Subcommands setup
s.serverCmd = flag.NewFlagSet("server", flag.ExitOnError)
s.versionCmd = flag.NewFlagSet("version", flag.ExitOnError)
// Common flag pointers
for _, fs := range []*flag.FlagSet{s.serverCmd, s.versionCmd} {
fs.BoolVar(&s.helpFlag, "help", false, "Show help message")
fs.StringVar(&s.configPathFlag, "config", "", "Path to configuration file (default: ~/.nodebalancer/config.txt)")
}
// Server subcommand flag pointers
s.serverCmd.StringVar(&s.listeningAddrFlag, "host", "127.0.0.1", "Server listening address")
s.serverCmd.StringVar(&s.listeningPortFlag, "port", "8544", "Server listening port")
s.serverCmd.BoolVar(&s.enableHealthCheckFlag, "healthcheck", false, "To enable healthcheck ser healthcheck flag")
s.serverCmd.BoolVar(&s.enableDebugFlag, "debug", false, "To enable debug mode with extended log set debug flag")
}
func CLI() {
stateCLI.populateCLI()
if len(os.Args) < 2 {
stateCLI.usage()
os.Exit(1)
}
// Parse subcommands and appropriate FlagSet
switch os.Args[1] {
case "server":
stateCLI.serverCmd.Parse(os.Args[2:])
stateCLI.checkRequirements()
Server()
case "version":
stateCLI.versionCmd.Parse(os.Args[2:])
stateCLI.checkRequirements()
fmt.Printf("v%s\n", configs.NB_VERSION)
default:
stateCLI.usage()
os.Exit(1)
}
}

Wyświetl plik

@ -8,13 +8,17 @@ import (
configs "github.com/bugout-dev/moonstream/nodes/node_balancer/configs" configs "github.com/bugout-dev/moonstream/nodes/node_balancer/configs"
) )
var ethereumClientPool ClientPool var (
var polygonClientPool ClientPool ethereumClientPool ClientPool
polygonClientPool ClientPool
xdaiClientPool ClientPool
)
// Generate client pools for different blockchains // Generate client pools for different blockchains
func CreateClientPools() { func CreateClientPools() {
ethereumClientPool.Client = make(map[string]*Client) ethereumClientPool.Client = make(map[string]*Client)
polygonClientPool.Client = make(map[string]*Client) polygonClientPool.Client = make(map[string]*Client)
xdaiClientPool.Client = make(map[string]*Client)
} }
// Return client pool correspongin to blockchain // Return client pool correspongin to blockchain
@ -24,6 +28,8 @@ func GetClientPool(blockchain string) (*ClientPool, error) {
cpool = &ethereumClientPool cpool = &ethereumClientPool
} else if blockchain == "polygon" { } else if blockchain == "polygon" {
cpool = &polygonClientPool cpool = &polygonClientPool
} else if blockchain == "xdai" {
cpool = &xdaiClientPool
} else { } else {
return nil, errors.New("Unexisting blockchain provided") return nil, errors.New("Unexisting blockchain provided")
} }

Wyświetl plik

@ -35,6 +35,8 @@ func lbHandler(w http.ResponseWriter, r *http.Request) {
blockchain = "ethereum" blockchain = "ethereum"
case strings.HasPrefix(r.URL.Path, "/nb/polygon"): case strings.HasPrefix(r.URL.Path, "/nb/polygon"):
blockchain = "polygon" blockchain = "polygon"
case strings.HasPrefix(r.URL.Path, "/nb/xdai"):
blockchain = "xdai"
default: default:
http.Error(w, fmt.Sprintf("Unacceptable blockchain provided %s", blockchain), http.StatusBadRequest) http.Error(w, fmt.Sprintf("Unacceptable blockchain provided %s", blockchain), http.StatusBadRequest)
return return

Wyświetl plik

@ -5,7 +5,6 @@ package cmd
import ( import (
"context" "context"
"flag"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
@ -29,7 +28,8 @@ func initHealthCheck(debug bool) {
blockchainPool.HealthCheck() blockchainPool.HealthCheck()
ethereumClients := ethereumClientPool.CleanInactiveClientNodes() ethereumClients := ethereumClientPool.CleanInactiveClientNodes()
polygonClients := polygonClientPool.CleanInactiveClientNodes() polygonClients := polygonClientPool.CleanInactiveClientNodes()
log.Printf("Active etehereum clients: %d, polygon clients: %d\n", ethereumClients, polygonClients) xdaiClients := xdaiClientPool.CleanInactiveClientNodes()
log.Printf("Active etehereum clients: %d, polygon clients: %d, xdai clients: %d\n", ethereumClients, polygonClients, xdaiClients)
if debug { if debug {
blockchainPool.StatusLog() blockchainPool.StatusLog()
} }
@ -92,24 +92,7 @@ func proxyErrorHandler(proxy *httputil.ReverseProxy, url *url.URL) {
} }
} }
func InitServer() { func Server() {
var listeningAddr string
var listeningPort string
var enableHealthCheck bool
var enableDebug bool
var showVersion bool
flag.StringVar(&listeningAddr, "host", "127.0.0.1", "Server listening address")
flag.StringVar(&listeningPort, "port", "8544", "Server listening port")
flag.BoolVar(&enableHealthCheck, "healthcheck", false, "To enable healthcheck ser healthcheck flag")
flag.BoolVar(&enableDebug, "debug", false, "To enable debug mode with extended log set debug flag")
flag.BoolVar(&showVersion, "version", false, "Print version")
flag.Parse()
if showVersion {
fmt.Printf("Node balancer version: v%s\n", configs.NODE_BALANCER_VERSION)
return
}
// Generate map of clients // Generate map of clients
CreateClientPools() CreateClientPools()
@ -125,7 +108,7 @@ func InitServer() {
reporter.Publish(humbug.SystemReport()) reporter.Publish(humbug.SystemReport())
// Fill NodeConfigList with initial nodes from environment variables // Fill NodeConfigList with initial nodes from environment variables
configs.ConfigList.InitNodeConfigList() configs.ConfigList.InitNodeConfigList(stateCLI.configPathFlag)
// Parse nodes and set list of proxies // Parse nodes and set list of proxies
for i, nodeConfig := range configs.ConfigList.Configs { for i, nodeConfig := range configs.ConfigList.Configs {
@ -165,18 +148,18 @@ func InitServer() {
commonHandler = panicMiddleware(commonHandler) commonHandler = panicMiddleware(commonHandler)
server := http.Server{ server := http.Server{
Addr: fmt.Sprintf("%s:%s", listeningAddr, listeningPort), Addr: fmt.Sprintf("%s:%s", stateCLI.listeningAddrFlag, stateCLI.listeningPortFlag),
Handler: commonHandler, Handler: commonHandler,
ReadTimeout: 10 * time.Second, ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
} }
// Start node health checking and current block fetching // Start node health checking and current block fetching
if enableHealthCheck { if stateCLI.enableHealthCheckFlag {
go initHealthCheck(enableDebug) go initHealthCheck(stateCLI.enableDebugFlag)
} }
log.Printf("Starting server at %s:%s\n", listeningAddr, listeningPort) log.Printf("Starting server at %s:%s\n", stateCLI.listeningAddrFlag, stateCLI.listeningPortFlag)
err = server.ListenAndServe() err = server.ListenAndServe()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

Wyświetl plik

@ -4,9 +4,11 @@ Configurations for load balancer server.
package configs package configs
import ( import (
"io/ioutil"
"log" "log"
"os" "os"
"strconv" "strconv"
"strings"
"time" "time"
) )
@ -28,67 +30,43 @@ type NodeConfigList struct {
var ConfigList NodeConfigList var ConfigList NodeConfigList
var MOONSTREAM_NODE_ETHEREUM_A_IPC_ADDR = os.Getenv("MOONSTREAM_NODE_ETHEREUM_A_IPC_ADDR")
var MOONSTREAM_NODE_ETHEREUM_B_IPC_ADDR = os.Getenv("MOONSTREAM_NODE_ETHEREUM_B_IPC_ADDR")
var MOONSTREAM_NODE_ETHEREUM_IPC_PORT = os.Getenv("MOONSTREAM_NODE_ETHEREUM_IPC_PORT")
var MOONSTREAM_NODE_POLYGON_A_IPC_ADDR = os.Getenv("MOONSTREAM_NODE_POLYGON_A_IPC_ADDR")
var MOONSTREAM_NODE_POLYGON_B_IPC_ADDR = os.Getenv("MOONSTREAM_NODE_POLYGON_B_IPC_ADDR")
var MOONSTREAM_NODE_POLYGON_IPC_PORT = os.Getenv("MOONSTREAM_NODE_POLYGON_IPC_PORT")
var MOONSTREAM_NODES_SERVER_PORT = os.Getenv("MOONSTREAM_NODES_SERVER_PORT") var MOONSTREAM_NODES_SERVER_PORT = os.Getenv("MOONSTREAM_NODES_SERVER_PORT")
var MOONSTREAM_CLIENT_ID_HEADER = os.Getenv("MOONSTREAM_CLIENT_ID_HEADER") var MOONSTREAM_CLIENT_ID_HEADER = os.Getenv("MOONSTREAM_CLIENT_ID_HEADER")
func checkEnvVarSet() { func checkEnvVarSet() {
if MOONSTREAM_NODE_ETHEREUM_A_IPC_ADDR == "" {
MOONSTREAM_NODE_ETHEREUM_A_IPC_ADDR = "a.ethereum.moonstream.internal"
}
if MOONSTREAM_NODE_ETHEREUM_B_IPC_ADDR == "" {
MOONSTREAM_NODE_ETHEREUM_B_IPC_ADDR = "b.ethereum.moonstream.internal"
}
if MOONSTREAM_NODE_POLYGON_A_IPC_ADDR == "" {
MOONSTREAM_NODE_POLYGON_A_IPC_ADDR = "a.polygon.moonstream.internal"
}
if MOONSTREAM_NODE_POLYGON_B_IPC_ADDR == "" {
MOONSTREAM_NODE_POLYGON_B_IPC_ADDR = "b.polygon.moonstream.internal"
}
if MOONSTREAM_CLIENT_ID_HEADER == "" { if MOONSTREAM_CLIENT_ID_HEADER == "" {
MOONSTREAM_CLIENT_ID_HEADER = "x-moonstream-client-id" MOONSTREAM_CLIENT_ID_HEADER = "x-moonstream-client-id"
} }
if MOONSTREAM_NODES_SERVER_PORT == "" || MOONSTREAM_NODE_ETHEREUM_IPC_PORT == "" || MOONSTREAM_NODE_POLYGON_IPC_PORT == "" { if MOONSTREAM_NODES_SERVER_PORT == "" {
log.Fatal("Some of environment variables not set") log.Fatal("Environment variable MOONSTREAM_NODES_SERVER_PORT not set")
} }
} }
// Return list of NodeConfig structures // Return list of NodeConfig structures
func (nc *NodeConfigList) InitNodeConfigList() { func (nc *NodeConfigList) InitNodeConfigList(configPath string) {
checkEnvVarSet() checkEnvVarSet()
// Define available blockchain nodes rawBytes, err := ioutil.ReadFile(configPath)
blockchainConfigList := make([]BlockchainConfig, 0, 2) if err != nil {
blockchainConfigList = append(blockchainConfigList, BlockchainConfig{ log.Fatalf("Unable to read config file, %v", err)
Blockchain: "ethereum", }
IPs: []string{MOONSTREAM_NODE_ETHEREUM_A_IPC_ADDR, MOONSTREAM_NODE_ETHEREUM_B_IPC_ADDR}, text := string(rawBytes)
Port: MOONSTREAM_NODE_ETHEREUM_IPC_PORT, lines := strings.Split(text, "\n")
})
blockchainConfigList = append(blockchainConfigList, BlockchainConfig{
Blockchain: "polygon",
IPs: []string{MOONSTREAM_NODE_POLYGON_A_IPC_ADDR, MOONSTREAM_NODE_POLYGON_B_IPC_ADDR},
Port: MOONSTREAM_NODE_POLYGON_IPC_PORT,
})
// Parse node addr, ip and blockchain // Define available blockchain nodes
for _, b := range blockchainConfigList { for _, line := range lines {
for _, nodeIP := range b.IPs { fields := strings.Split(line, ",")
port, err := strconv.ParseInt(b.Port, 0, 16) if len(fields) == 3 {
port, err := strconv.ParseInt(fields[2], 0, 16)
if err != nil { if err != nil {
log.Printf("Unable to parse port number: %s", b.Port) log.Printf("Unable to parse port number, %v", err)
continue continue
} }
nc.Configs = append(nc.Configs, NodeConfig{ nc.Configs = append(nc.Configs, NodeConfig{
Blockchain: b.Blockchain, Blockchain: fields[0],
Addr: nodeIP, Addr: fields[1],
Port: uint16(port), Port: uint16(port),
}) })
} }

Wyświetl plik

@ -1,3 +1,3 @@
package configs package configs
var NODE_BALANCER_VERSION = "0.0.1" var NB_VERSION = "0.0.2"

Wyświetl plik

@ -0,0 +1,10 @@
#!/usr/bin/env sh
# Compile application and run with provided arguments
set -e
PROGRAM_NAME="nodebalancer"
go build -o "$PROGRAM_NAME" .
./"$PROGRAM_NAME" "$@"

Wyświetl plik

@ -5,5 +5,5 @@ import (
) )
func main() { func main() {
cmd.InitServer() cmd.CLI()
} }

Wyświetl plik

@ -32,7 +32,7 @@ func (es *extendedServer) pingGethRoute(w http.ResponseWriter, req *http.Request
http.Error(w, http.StatusText(500), http.StatusInternalServerError) http.Error(w, http.StatusText(500), http.StatusInternalServerError)
return return
} }
if es.blockchain != "ethereum" && es.blockchain != "polygon" { if es.blockchain != "ethereum" && es.blockchain != "polygon" && es.blockchain != "xdai" {
log.Printf("Unaccepted blockchain type: %s", es.blockchain) log.Printf("Unaccepted blockchain type: %s", es.blockchain)
http.Error(w, http.StatusText(400), http.StatusBadRequest) http.Error(w, http.StatusText(400), http.StatusBadRequest)
return return

Wyświetl plik

@ -17,7 +17,7 @@ func InitServer() {
var blockchain string var blockchain string
flag.StringVar(&listeningAddr, "host", "127.0.0.1", "Server listening address") flag.StringVar(&listeningAddr, "host", "127.0.0.1", "Server listening address")
flag.StringVar(&listeningPort, "port", "8080", "Server listening port") flag.StringVar(&listeningPort, "port", "8080", "Server listening port")
flag.StringVar(&blockchain, "blockchain", "", "Blockchain to work with (Ethereum/Polygon)") flag.StringVar(&blockchain, "blockchain", "", "Blockchain to work with (ethereum/polygon/xdai)")
flag.Parse() flag.Parse()
es := extendedServer{blockchain: blockchain} es := extendedServer{blockchain: blockchain}