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"
paths:
- "backend/**"
- "!backend/deploy/**"
jobs:
build:

Wyświetl plik

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

Wyświetl plik

@ -6,6 +6,7 @@ on:
- "main"
paths:
- "db/**"
- "!db/deploy/**"
jobs:
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",
stripe_product_id=None,
stripe_price_id=None,
active=False,
active=True,
),
"polygon_smartcontract": SubscriptionTypeResourceData(
id="polygon_smartcontract",

Wyświetl plik

@ -8,4 +8,5 @@ User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl
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
WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl
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]
Description=Ethereum txpool crawler
After=network.target
StartLimitIntervalSec=300
StartLimitBurst=3
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/txpool
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}"
SyslogIdentifier=ethereum-txpool

Wyświetl plik

@ -8,4 +8,5 @@ User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/mooncrawl
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
EnvironmentFile=/home/ubuntu/moonstream-secrets/app.env
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]
Description=Polygon txpool crawler
After=network.target
StartLimitIntervalSec=300
StartLimitBurst=3
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/moonstream/crawlers/txpool
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}"
SyslogIdentifier=polygon-txpool

Wyświetl plik

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

Wyświetl plik

@ -172,14 +172,14 @@ def continuous_crawler(
)
last_heartbeat_time = datetime.utcnow()
blocks_cache: Dict[int, int] = {}
current_sleep_time = min_sleep_time
failed_count = 0
try:
while True:
try:
# query db with limit 1, to avoid session closing
db_session.execute("SELECT 1")
time.sleep(min_sleep_time)
time.sleep(current_sleep_time)
end_block = min(
web3.eth.blockNumber - confirmations,
@ -187,12 +187,12 @@ def continuous_crawler(
)
if start_block + min_blocks_batch > end_block:
min_sleep_time += 0.1
current_sleep_time += 0.1
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
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}")
all_events = _crawl_events(

Wyświetl plik

@ -206,18 +206,27 @@ def make_function_call_crawl_jobs(
"""
crawl_job_by_address: Dict[str, FunctionCallCrawlJob] = {}
method_signature_by_address: Dict[str, List[str]] = {}
for entry in entries:
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:
crawl_job_by_address[contract_address] = FunctionCallCrawlJob(
contract_abi=[json.loads(abi)],
contract_abi=[abi],
contract_address=contract_address,
created_at=int(datetime.fromisoformat(entry.created_at).timestamp()),
)
method_signature_by_address[contract_address] = [method_signature]
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()]

Wyświetl plik

@ -91,6 +91,3 @@ def function_call_crawler(
i,
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.moonstreamdb.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
)
# 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
"""

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"`
}
type BlockNumberResponse struct {
BlockNumber uint64 `json:"block_number"`
type BlockLatestResponse struct {
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 {
db, err := sql.Open("postgres", settings.MOONSTREAM_DB_URI)
db, err := sql.Open("postgres", settings.MOONSTREAM_DB_URI_READ_ONLY)
if err != nil {
// DSN parse error or another initialization error
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) {
w.Header().Set("Content-Type", "application/json")
var latestBlock BlockNumberResponse
row := es.db.QueryRow("SELECT block_number FROM ethereum_blocks ORDER BY block_number DESC LIMIT 1")
err := row.Scan(&latestBlock.BlockNumber)
var blockNumbers []uint64
var blockLatest BlockLatestResponse
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 == 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)
http.Error(w, "Internal server error", http.StatusInternalServerError)
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
var MOONSTREAM_DB_MAX_IDLE_CONNS int = 30
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
var MOONSTREAM_CORS_ALLOWED_ORIGINS = os.Getenv("MOONSTREAM_CORS_ALLOWED_ORIGINS")

Wyświetl plik

@ -1,9 +1,10 @@
#!/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
MOONSTREAM_DB_SERVER_HOST="${MOONSTREAM_DB_SERVER_HOST:-0.0.0.0}"
MOONSTREAM_DB_SERVER_PORT="${MOONSTREAM_DB_SERVER_PORT:-8080}"
PROGRAM_NAME="moonstreamdb"
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"

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 --> */}
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-MNVHX36LZ1"
src="https://www.googletagmanager.com/gtag/js?id=UA-156911549-2"
></script>
<script
dangerouslySetInnerHTML={{
@ -55,7 +55,7 @@ export default class MyDocument extends Document {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-MNVHX36LZ1");`,
gtag("config", "UA-156911549-2");`,
}}
/>
</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 (
// <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
specUrl="https://api.moonstream.to/openapi.json"
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 { Heading, Text, Flex, Spacer, chakra, Spinner } from "@chakra-ui/react";
import { getLayout, getLayoutProps } from "../../src/layouts/InfoPageLayout";
import UserContext from "../../src/core/providers/UserProvider/context";
const Status = () => {
const user = useContext(UserContext);
const healthyStatusText = "Available";
const downStatusText = "Unavailable";
const unauthorizedText = "Please login";
const healthyStatusColor = "green.900";
const downStatusColor = "red.600";
const shortTimestamp = (rawTimestamp) => {
return rawTimestamp.replace(/^.+T/, "").replace(/\..+/, "");
};
const { serverListStatusCache } = useStatus();
const {
serverListStatusCache,
crawlersStatusCache,
dbServerStatusCache,
latestBlockDBStatusCache,
} = useStatus();
console.log(serverListStatusCache?.data);
const moonstreamapiStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "moonstreamapi"
(i) => i.name === "moonstream_api"
)[0];
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];
const nodeEthereumAStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_ethereum_a"
)[0];
const nodeEthereumAGeth = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_ethereum_a_geth"
(i) => i.name === "node_ethereum_a"
)[0];
const nodeEthereumBStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_ethereum_b"
)[0];
const nodeEthereumBGeth = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_ethereum_b_geth"
(i) => i.name === "node_ethereum_b"
)[0];
const nodePolygonAStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_polygon_a"
)[0];
const nodePolygonAGeth = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_polygon_a_bor"
(i) => i.name === "node_polygon_a"
)[0];
const nodePolygonBStatus = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_polygon_b"
(i) => i.name === "node_polygon_b"
)[0];
const nodePolygonBGeth = serverListStatusCache?.data?.filter(
(i) => i.status.name === "node_polygon_b_bor"
const dbServerStatus = serverListStatusCache?.data?.filter(
(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];
const StatusRow = (props) => {
@ -80,12 +70,12 @@ const Status = () => {
<StatusRow title="Backend server" cache={serverListStatusCache}>
<Text
color={
moonstreamapiStatus?.status.body.status == "ok"
moonstreamapiStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{moonstreamapiStatus?.status.body.status == "ok"
{moonstreamapiStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
@ -93,42 +83,33 @@ const Status = () => {
<br />
<StatusRow title="Crawlers server" cache={crawlersStatusCache}>
<StatusRow title="Crawlers server" cache={serverListStatusCache}>
<Text
color={
moonstreamCrawlersStatus?.status.body.status == "ok"
moonstreamCrawlersStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{moonstreamCrawlersStatus?.status.body.status == "ok"
{moonstreamCrawlersStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
</StatusRow>
<StatusRow title="Txpool latest record ts" cache={crawlersStatusCache}>
<Text>
{!user
? crawlersStatusCache?.data?.ethereum_txpool_timestamp
? shortTimestamp(
crawlersStatusCache?.data?.ethereum_txpool_timestamp
)
: downStatusText
: unauthorizedText}
</Text>
</StatusRow>
<StatusRow
title="Trending latest record ts"
cache={crawlersStatusCache}
>
<Text>
{!user
? crawlersStatusCache?.data?.ethereum_trending_timestamp
? shortTimestamp(
crawlersStatusCache?.data?.ethereum_trending_timestamp
)
: downStatusText
: unauthorizedText}
<br />
<StatusRow title="Node balancer server" cache={serverListStatusCache}>
<Text
color={
nodeBalacerStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{nodeBalacerStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
</StatusRow>
@ -137,20 +118,20 @@ const Status = () => {
<StatusRow title="Node Ethereum A" cache={serverListStatusCache}>
<Text
color={
nodeEthereumAStatus?.status.body.status == "ok"
nodeEthereumAStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{nodeEthereumAStatus?.status.body.status == "ok"
{nodeEthereumAStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
</StatusRow>
<StatusRow title="Current block" cache={serverListStatusCache}>
<Text>
{nodeEthereumAGeth?.status.body.current_block
? nodeEthereumAGeth.status.body.current_block
{nodeEthereumAStatus?.response?.current_block
? nodeEthereumAStatus.response.current_block
: 0}
</Text>
</StatusRow>
@ -158,20 +139,20 @@ const Status = () => {
<StatusRow title="Node Ethereum B" cache={serverListStatusCache}>
<Text
color={
nodeEthereumBStatus?.status.body.status == "ok"
nodeEthereumBStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{nodeEthereumBStatus?.status.body.status == "ok"
{nodeEthereumBStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
</StatusRow>
<StatusRow title="Current block" cache={serverListStatusCache}>
<Text>
{nodeEthereumBGeth?.status.body.current_block
? nodeEthereumBGeth.status.body.current_block
{nodeEthereumBStatus?.response?.current_block
? nodeEthereumBStatus.response.current_block
: 0}
</Text>
</StatusRow>
@ -179,20 +160,20 @@ const Status = () => {
<StatusRow title="Node Polygon A" cache={serverListStatusCache}>
<Text
color={
nodePolygonAStatus?.status.body.status == "ok"
nodePolygonAStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{nodePolygonAStatus?.status.body.status == "ok"
{nodePolygonAStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
</StatusRow>
<StatusRow title="Current block" cache={serverListStatusCache}>
<Text>
{nodePolygonAGeth?.status.body.current_block
? nodePolygonAGeth.status.body.current_block
{nodePolygonAStatus?.response?.current_block
? nodePolygonAStatus.response.current_block
: 0}
</Text>
</StatusRow>
@ -200,49 +181,105 @@ const Status = () => {
<StatusRow title="Node Polygon B" cache={serverListStatusCache}>
<Text
color={
nodePolygonBStatus?.status.body.status == "ok"
nodePolygonBStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{nodePolygonBStatus?.status.body.status == "ok"
{nodePolygonBStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
</StatusRow>
<StatusRow title="Current block" cache={serverListStatusCache}>
<Text>
{nodePolygonBGeth?.status.body.current_block
? nodePolygonBGeth.status.body.current_block
{nodePolygonBStatus?.response?.current_block
? nodePolygonBStatus.response.current_block
: 0}
</Text>
</StatusRow>
<br />
<StatusRow title="Database server" cache={dbServerStatusCache}>
<StatusRow title="Database server" cache={serverListStatusCache}>
<Text
color={
dbServerStatusCache?.data?.status == "ok"
dbServerStatus?.status_code == 200
? healthyStatusColor
: downStatusColor
}
>
{dbServerStatusCache?.data?.status == "ok"
{dbServerStatus?.status_code == 200
? healthyStatusText
: downStatusText}
</Text>
</StatusRow>
<StatusRow
title="Latest block in Database"
cache={latestBlockDBStatusCache}
>
<StatusRow title="Ethereum latest block" cache={serverListStatusCache}>
<Text>
{latestBlockDBStatusCache?.data?.block_number
? latestBlockDBStatusCache.data.block_number
{dbServerStatus?.response?.ethereum_block_latest
? dbServerStatus.response.ethereum_block_latest
: 0}
</Text>
</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>
</>
);

Wyświetl plik

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

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_BUGOUT_STATUS_URL=https://status.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_JOURNALS_URL=https://spire.bugout.dev
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 = {
// 1. We can update the base styles
baseStyle: () => ({
@ -151,6 +169,7 @@ const Button = {
ghost: variantGhost,
outline: variantOutline,
link: variantLink,
orangeAndBlue: variantOrangeAndBlue,
},
};
export default Button;

Wyświetl plik

@ -9,11 +9,12 @@ import {
MenuDivider,
IconButton,
chakra,
Portal,
} from "@chakra-ui/react";
import { RiAccountCircleLine } from "react-icons/ri";
import useLogout from "../core/hooks/useLogout";
import UIContext from "../core/providers/UIProvider/context";
import { ALL_NAV_PATHES } from "../core/constants";
import { SITEMAP } from "../core/constants";
const AccountIconButton = (props) => {
const { logout } = useLogout();
@ -30,39 +31,49 @@ const AccountIconButton = (props) => {
icon={<RiAccountCircleLine m={0} size="26px" />}
color="gray.100"
/>
<MenuList
zIndex="dropdown"
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();
}}
<Portal>
<MenuList
zIndex="dropdown"
width={["100vw", "100vw", "18rem", "20rem", "22rem", "24rem"]}
borderRadius={0}
>
Logout
</MenuItem>
</MenuList>
<MenuGroup>
<RouterLink href="/account/security" passHref>
<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>
);
};

Wyświetl plik

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

Wyświetl plik

@ -12,7 +12,8 @@ import { BiCopy } from "react-icons/bi";
const CopyButton = (props) => {
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);
@ -31,7 +32,7 @@ const CopyButton = (props) => {
icon={<BiCopy />}
colorScheme="orange"
variant="ghost"
size="sm"
size={props.size ?? "sm"}
/>
</PopoverTrigger>
<PopoverContent

Wyświetl plik

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

Wyświetl plik

@ -8,13 +8,18 @@ import {
Link,
IconButton,
Flex,
Menu,
MenuButton,
MenuList,
MenuItem,
Portal,
} from "@chakra-ui/react";
import { HamburgerIcon } from "@chakra-ui/icons";
import { ChevronDownIcon, HamburgerIcon } from "@chakra-ui/icons";
import useModals from "../core/hooks/useModals";
import UIContext from "../core/providers/UIProvider/context";
import ChakraAccountIconButton from "./AccountIconButton";
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 { MODAL_TYPES } from "../core/providers/OverlayProvider/constants";
@ -37,7 +42,7 @@ const LandingNavbar = () => {
<Flex
pl={ui.isMobileView ? 2 : 8}
justifySelf="flex-start"
h="100%"
h="48px"
py={1}
flexBasis="200px"
flexGrow={1}
@ -47,7 +52,7 @@ const LandingNavbar = () => {
<Link
as={Image}
w="auto"
h="full"
h="100%"
justifyContent="left"
src={WHITE_LOGO_W_TEXT_URL}
alt="Moonstream logo"
@ -59,21 +64,51 @@ const LandingNavbar = () => {
<>
<Spacer />
<ButtonGroup variant="link" colorScheme="orange" spacing={4} pr={16}>
{ALL_NAV_PATHES.map((item, idx) => (
<RouteButton
key={`${idx}-${item.title}-landing-all-links`}
variant="link"
href={item.path}
color="white"
isActive={!!(router.pathname === item.path)}
>
{item.title}
</RouteButton>
))}
{SITEMAP.map((item, idx) => {
return (
<React.Fragment key={`Fragment-${idx}`}>
{!item.children && (
<RouteButton
key={`${idx}-${item.title}-landing-all-links`}
variant="link"
href={item.path}
color="white"
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 && (
<RouterLink href="/welcome" passHref>
<Button
alignSelf={"center"}
as={Link}
colorScheme="orange"
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 UIContext from "../core/providers/UIProvider/context";
const LandingNavbar = React.lazy(() => import("./LandingNavbar"));
import LandingNavbar from "./LandingNavbar";
const AppNavbar = React.lazy(() => import("./AppNavbar"));
const Navbar = () => {
@ -11,6 +11,7 @@ const Navbar = () => {
return (
<Flex
boxShadow={["sm", "md"]}
zIndex={1}
alignItems="center"
id="Navbar"
minH="3rem"
@ -19,9 +20,12 @@ const Navbar = () => {
direction="row"
w="100%"
overflow="hidden"
position={"fixed"}
transition={"0.3s"}
top={"0"}
>
{(!isAppView || !isLoggedIn) && <LandingNavbar />}
<Suspense fallback={""}>
{(!isAppView || !isLoggedIn) && <LandingNavbar />}
{isAppView && isLoggedIn && <AppNavbar />}
</Suspense>
</Flex>

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -12,11 +12,15 @@ import {
Button,
useBreakpointValue,
useToken,
chakra,
} from "@chakra-ui/react";
import React, { useContext } from "react";
import UIContext from "../core/providers/UIProvider/context";
import { FaDiscord, FaGithubSquare } from "react-icons/fa";
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 }) => {
return (
@ -47,7 +51,7 @@ const Feature = ({ text, icon, iconBg, bullets }) => {
text={bullet.text}
{...bullet}
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,
title,
body,
@ -71,7 +75,10 @@ const SplitWithImage = ({
socialButton,
imgBoxShadow,
py,
...props
}) => {
const router = useRouter();
var buttonSize = useBreakpointValue({
base: { single: "sm", double: "xs" },
sm: { single: "md", double: "sm" },
@ -116,6 +123,7 @@ const SplitWithImage = ({
py={py}
className={`fade-in-section ${isVisible ? "is-visible" : ""}`}
ref={domRef}
{...props}
>
<SimpleGrid columns={{ base: 1, md: 2 }} spacing={[0, 0, 10, null, 10]}>
{mirror && !ui.isMobileView && (
@ -166,7 +174,7 @@ const SplitWithImage = ({
<Feature
key={`splitWImageBullet-${idx}-${title}`}
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}
text={bullet.text}
@ -186,6 +194,16 @@ const SplitWithImage = ({
w={["100%", "100%", "fit-content", null]}
maxW={["250px", null, "fit-content"]}
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]}
size={socialButton ? buttonSize.double : buttonSize.single}
variant="outline"
@ -206,7 +224,18 @@ const SplitWithImage = ({
variant="outline"
mt={[0, 0, null, 16]}
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}
</Button>
@ -230,5 +259,6 @@ const SplitWithImage = ({
</Container>
);
};
const SplitWithImage = chakra(_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 { Skeleton, IconButton, Container } from "@chakra-ui/react";
import React from "react";
import { Skeleton, Container } from "@chakra-ui/react";
import {
Table,
Th,
Td,
Tr,
Thead,
Tbody,
Tooltip,
Editable,
EditableInput,
EditablePreview,
Image,
Button,
useMediaQuery,
Accordion,
} 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 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",
};
import SubscriptionCard from "./SubscriptionCard";
const SubscriptionsList = ({ emptyCTA }) => {
const overlay = useContext(OverlayContext);
const {
subscriptionsCache,
updateSubscription,
deleteSubscription,
subscriptionTypeIcons,
subscriptionTypeNames,
} = useSubscriptions();
const [isLargerThan530px] = useMediaQuery(["(min-width: 530px)"]);
const { subscriptionsCache, subscriptionTypeIcons } = useSubscriptions();
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"],
};
if (
@ -50,121 +26,65 @@ const SubscriptionsList = ({ emptyCTA }) => {
subscriptionsCache.data.subscriptions.length > 0
) {
return (
<Table
borderColor="gray.200"
borderWidth="1px"
variant="simple"
colorScheme="blue"
justifyContent="center"
borderBottomRadius="xl"
alignItems="baseline"
h="auto"
size="sm"
mt={0}
>
<Thead>
<Tr>
<Th>Token</Th>
<Th>Label</Th>
<Th>Address</Th>
<Th>abi</Th>
<Th>Color</Th>
<Th>Date Created</Th>
<Th>Actions</Th>
</Tr>
</Thead>
<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>
<>
{isLargerThan530px && (
<Table
borderColor="gray.200"
borderWidth="1px"
variant="simple"
colorScheme="blue"
justifyContent="center"
borderBottomRadius="xl"
alignItems="baseline"
h="auto"
size="sm"
mt={0}
>
<Thead>
<Tr>
<Th {...cellProps}>Token</Th>
<Th {...cellProps}>Label</Th>
<Th {...cellProps}>Address</Th>
<Th {...cellProps}>abi</Th>
<Th {...cellProps}>Color</Th>
<Th {...cellProps}>Date Created</Th>
<Th {...cellProps}>Actions</Th>
</Tr>
);
})}
</Tbody>
</Table>
</Thead>
<Tbody>
{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 (
subscriptionsCache.data &&

Wyświetl plik

@ -1,5 +1,12 @@
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 = ({
name,
@ -8,10 +15,15 @@ const TrustedBadge = ({
scale,
isGrayScale,
boxURL,
invertColors,
...props
}) => {
const _scale = scale ?? 1;
const _isGrayScale = isGrayScale ? "grayscale(100%)" : "";
const _invert = invertColors ? "invert(100%)" : "";
const filterStr = _isGrayScale + " " + _invert;
return (
<LinkBox m={2}>
<LinkBox m={2} borderRadius="md" {...props}>
<LinkOverlay href={boxURL} isExternal>
<Flex
m={1}
@ -23,7 +35,7 @@ const TrustedBadge = ({
direction="column"
>
<Image
sx={isGrayScale && { filter: "grayscale(100%)" }}
sx={{ filter: filterStr }}
h={[
`${2.25 * _scale}rem`,
null,
@ -51,4 +63,4 @@ const TrustedBadge = ({
</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 = {
title: "Moonstream: Building blocks for your blockchain economy",
title: "Moonstream: Building blocks for your blockchain game",
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:
"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",
image: `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/crypto+traders.png`,
};
export const FOOTER_COLUMNS = {
NEWS: "News",
COMPANY: "Company",
PRODUCT: "Product",
// export const FOOTER_COLUMNS = {
// NEWS: "News",
// COMPANY: "Company",
// 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",
path: "/product",
footerCategory: FOOTER_COLUMNS.PRODUCT,
title: "Resources",
path: "/resources",
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",
path: "/team",
footerCategory: FOOTER_COLUMNS.COMPANY,
title: "Developers",
path: "/developers",
type: PAGETYPE.EMPTY,
children: [
{
title: "Docs",
path: "/docs",
type: PAGETYPE.CONTENT,
},
{
title: "Status",
path: "/status",
type: PAGETYPE.CONTENT,
},
],
},
{
title: "Docs",
path: "/docs",
footerCategory: FOOTER_COLUMNS.PRODUCT,
},
{
title: "Whitepapers",
path: "/whitepapers",
footerCategory: FOOTER_COLUMNS.PRODUCT,
},
{
title: "Blog",
path: "https://blog.moonstream.to",
footerCategory: FOOTER_COLUMNS.NEWS,
},
{
title: "Status",
path: "/status",
footerCategory: FOOTER_COLUMNS.PRODUCT,
title: "About",
path: "/about",
type: PAGETYPE.EMPTY,
children: [
{
title: "Team",
path: "/team",
type: PAGETYPE.CONTENT,
},
],
},
];

Wyświetl plik

@ -7,18 +7,6 @@ const useStatus = () => {
const response = await StatusService.serverListStatus();
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(
"serverListStatus",
@ -28,28 +16,9 @@ const useStatus = () => {
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 {
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 UIContext from "../UIProvider/context";
const TELEMETRY_SCHEMA_VERSION = 1.0;
const AnalyticsProvider = ({ children }) => {
const clientID = useClientID();
const analytics = process.env.NEXT_PUBLIC_MIXPANEL_TOKEN;
@ -138,6 +140,7 @@ const AnalyticsProvider = ({ children }) => {
loaded: () => {
setIsLoaded(true);
mixpanel.identify(clientID);
mixpanel.register({ schema_version: TELEMETRY_SCHEMA_VERSION });
},
});
} catch (error) {

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -1,33 +1,10 @@
import { http } from "../utils";
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 = () => {
return http({
method: "GET",
url: `${BUGOUT_STATUS_URL}`,
});
};
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`,
url: `${BUGOUT_STATUS_URL}/status`,
});
};

Wyświetl plik

@ -2,6 +2,7 @@ import { Flex, Spinner, Box } from "@chakra-ui/react";
import { getLayout as getSiteLayout } from "./RootLayout";
import React, { useContext, useEffect } from "react";
import UIContext from "../core/providers/UIProvider/context";
import AppNavbar from "../components/AppNavbar";
const AppLayout = ({ children }) => {
const ui = useContext(UIContext);
@ -16,12 +17,12 @@ const AppLayout = ({ children }) => {
return (
<Flex
direction="row"
id="JournalsWrapper"
flexGrow={1}
maxH="100%"
w="100%"
overflow="hidden"
direction="column"
>
{(!ui.isAppReady || !ui.isLoggedIn) && (
<Spinner
@ -44,7 +45,9 @@ const AppLayout = ({ children }) => {
zIndex={1010}
/>
)}
<Flex direction={"column"} bgColor="blue.900" id="Navbar">
<AppNavbar />
</Flex>
{ui.isAppReady && ui.isLoggedIn && children}
</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 UIContext from "../core/providers/UIProvider/context";
const Sidebar = React.lazy(() => import("../components/Sidebar"));
const Navbar = React.lazy(() => import("../components/Navbar"));
const RootLayout = (props) => {
const ui = useContext(UIContext);
@ -27,9 +26,6 @@ const RootLayout = (props) => {
flexBasis="100px"
overflowX="hidden"
>
<Suspense fallback="">
<Navbar />
</Suspense>
{!ui.isAppView && (
<Flex
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 Scrollable from "../components/Scrollable";
import RootLayout from "./RootLayout";
import Navbar from "../components/Navbar";
const LayoutWrapper = ({ children }) => {
return (
<RootLayout>
<Scrollable>
<Navbar />
{children}
<Footer />
</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
.secrets/*
dev.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}"
SCRIPT_DIR="$(realpath $(dirname $0))"
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
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"
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 -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
Restart=on-failure
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
[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"
)
var ethereumClientPool ClientPool
var polygonClientPool ClientPool
var (
ethereumClientPool ClientPool
polygonClientPool ClientPool
xdaiClientPool ClientPool
)
// Generate client pools for different blockchains
func CreateClientPools() {
ethereumClientPool.Client = make(map[string]*Client)
polygonClientPool.Client = make(map[string]*Client)
xdaiClientPool.Client = make(map[string]*Client)
}
// Return client pool correspongin to blockchain
@ -24,6 +28,8 @@ func GetClientPool(blockchain string) (*ClientPool, error) {
cpool = &ethereumClientPool
} else if blockchain == "polygon" {
cpool = &polygonClientPool
} else if blockchain == "xdai" {
cpool = &xdaiClientPool
} else {
return nil, errors.New("Unexisting blockchain provided")
}

Wyświetl plik

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

Wyświetl plik

@ -5,7 +5,6 @@ package cmd
import (
"context"
"flag"
"fmt"
"log"
"net/http"
@ -29,7 +28,8 @@ func initHealthCheck(debug bool) {
blockchainPool.HealthCheck()
ethereumClients := ethereumClientPool.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 {
blockchainPool.StatusLog()
}
@ -92,24 +92,7 @@ func proxyErrorHandler(proxy *httputil.ReverseProxy, url *url.URL) {
}
}
func InitServer() {
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
}
func Server() {
// Generate map of clients
CreateClientPools()
@ -125,7 +108,7 @@ func InitServer() {
reporter.Publish(humbug.SystemReport())
// Fill NodeConfigList with initial nodes from environment variables
configs.ConfigList.InitNodeConfigList()
configs.ConfigList.InitNodeConfigList(stateCLI.configPathFlag)
// Parse nodes and set list of proxies
for i, nodeConfig := range configs.ConfigList.Configs {
@ -165,18 +148,18 @@ func InitServer() {
commonHandler = panicMiddleware(commonHandler)
server := http.Server{
Addr: fmt.Sprintf("%s:%s", listeningAddr, listeningPort),
Addr: fmt.Sprintf("%s:%s", stateCLI.listeningAddrFlag, stateCLI.listeningPortFlag),
Handler: commonHandler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
// Start node health checking and current block fetching
if enableHealthCheck {
go initHealthCheck(enableDebug)
if stateCLI.enableHealthCheckFlag {
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()
if err != nil {
log.Fatal(err)

Wyświetl plik

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

Wyświetl plik

@ -1,3 +1,3 @@
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() {
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)
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)
http.Error(w, http.StatusText(400), http.StatusBadRequest)
return

Wyświetl plik

@ -17,7 +17,7 @@ func InitServer() {
var blockchain string
flag.StringVar(&listeningAddr, "host", "127.0.0.1", "Server listening address")
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()
es := extendedServer{blockchain: blockchain}