Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge to main #16

Merged
merged 39 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b9d03a0
chore : upgrade nextjs to 14
cbadawi Feb 13, 2024
99aaebd
fix : ts error : Property 'grid' does not exist on type 'JSX.Intrinsi…
cbadawi Feb 13, 2024
3880971
fix : on build, getServerData is not a valid Page export field.
cbadawi Feb 14, 2024
65605f1
Merge pull request #8 from stacksfoundation/chore/upgrade-next-typesc…
cbadawi Feb 14, 2024
f8f4456
feat: add navbar
cbadawi Feb 14, 2024
aa398aa
chore : pull node db & force delete old migrations since conflict wit…
cbadawi Feb 15, 2024
89a2021
chore : minor navbar ui improvement
cbadawi Feb 16, 2024
e5a53ab
chore : delete unused files
cbadawi Feb 16, 2024
52909a1
chore : upgrade to ts
cbadawi Feb 16, 2024
ed588a1
fix : micro is 1/10**6
cbadawi Feb 16, 2024
a519388
chore : add footer
cbadawi Feb 16, 2024
cd42a04
feat: add basic minimal cards
cbadawi Feb 16, 2024
7485dcf
refactor: redeisgn and add new metrics
cbadawi Feb 22, 2024
d964a50
fix : typo
cbadawi Feb 22, 2024
50af509
fix : no unused vars
cbadawi Feb 22, 2024
702c280
remove unused import
cbadawi Feb 22, 2024
29bf244
fix : Deployment to Vercel not running prisma generate
cbadawi Feb 22, 2024
585949f
Merge pull request #9 from stacksfoundation/refactor/redesign_add_new…
cbadawi Feb 22, 2024
a96ba00
chore : make design responsive grid col 1
cbadawi Feb 23, 2024
38ad05f
Merge pull request #10 from stacksfoundation/chore/responsive-design
cbadawi Feb 23, 2024
6cad336
feat: fetch data
cbadawi Feb 28, 2024
af01e8f
Merge branch 'stage' into feat/fetch-linechart-data
cbadawi Feb 28, 2024
6c8e239
feat: add tooltip to line charts
cbadawi Feb 28, 2024
2123311
Merge pull request #11 from stacksfoundation/feat/fetch-linechart-data
cbadawi Feb 28, 2024
027740e
add favicon
cbadawi Feb 29, 2024
89741d4
minor ui changes
cbadawi Feb 29, 2024
62cdefe
add links to latest block
cbadawi Feb 29, 2024
4d15659
add footer
cbadawi Feb 29, 2024
df6ee1f
Merge pull request #12 from stacksfoundation/chore/minor-changes
cbadawi Feb 29, 2024
7229372
add top row
cbadawi Mar 7, 2024
badf2fa
use index as key
cbadawi Mar 7, 2024
c829e07
fix : add async to footer
cbadawi Mar 7, 2024
2ed4193
remove get block len
cbadawi Mar 8, 2024
3b19772
Merge pull request #13 from stacksfoundation/chore/add-top-row
cbadawi Mar 8, 2024
5f76212
revalidate path
cbadawi Mar 9, 2024
e993acd
Merge pull request #15 from stacksfoundation/fix/cache
cbadawi Mar 9, 2024
9c73838
cleaning up for merge
wileyj Apr 8, 2024
9cfc32c
removing unneccesary lines in readme
wileyj Apr 8, 2024
b262fc2
update readme and enable cron auth header
wileyj Apr 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
NODE_ENV=development
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
API_SECRET_KEY="secret"
DATABASE_URL="postgresql://user:pass@localhost:5432/stacks_blockchain_api?schema=stacks_blockchain_api"
CRON_SECRET="secret"
15 changes: 8 additions & 7 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
{
"env": {
"browser": true,
"es2021": true
"es2021": true,
},
"extends": [
"plugin:react/recommended",
"standard",
"eslint:recommended",
"next"
"next",
],
"overrides": [],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
"sourceType": "module",
},
"plugins": ["react"],
"settings": {
"react": {
"version": "detect"
}
"version": "detect",
},
},
"rules": {
"react/prop-types": 0,
"no-extra-semi": 0,
"semi": 0,
"quotes": "off",
"space-before-function-paren": "off",
"comma-dangle": "off"
}
"comma-dangle": "off",
"no-var": "off",
},
}
15 changes: 0 additions & 15 deletions .github/workflows/cron.yaml

This file was deleted.

8 changes: 8 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"semi": true,
"tabWidth": 2,
"singleQuote": true,
"jsxSingleQuote": true,
"plugins": ["prettier-plugin-tailwindcss"]
}
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
84 changes: 28 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,90 +4,62 @@
[![Pull Requests Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)

This repository contains the code for the [**Stacks Blockchain Status Page**](https://status.stacks.org/) code used by the [Stacks Foundation](https://www.stacks.org) to display blockchain metrics.
This is a next.js react app that reads locally stored metric data from a postgres database, and periodically will fetch data from [https://stacksonchain.com/](https://stacksonchain.com/) to refresh the displayed data.
This is a next.js react app that reads [Stacks Blockchain API](https://github.com/hirosystems/stacks-blockchain-api) data from a postgres database to display chainstate data.

## Getting Started

- PostgreSQL is a required dependency
example with Docker:
- PostgreSQL is a **required** dependency with a [Stacks Blockchain API](https://github.com/hirosystems/stacks-blockchain-api) instance writing chainstate data.
- https://github.com/hirosystems/stacks-blockchain-api?tab=readme-ov-file#quick-start
- [Vercel cron](https://vercel.com/docs/cron-jobs) is used for updating mempool txs

```bash
$ docker run -d \
--rm \
--name postgres \
-p 5432:5432 \
-e POSTGRES_PASSWORD=postgres \
-v ./postgres_data:/var/lib/postgresql/data \
postgres:15-alpine
```
### Clone this repo and enter its directory

```bash
# Clone this repo and enter its directory
git clone https://github.com/stacksfoundation/stacksstatus && cd stacksstatus
```

### Create .env file

# Create .env file
```bash
cp .env.local.example .env

# update .env file with DB connection options (HOST, USER, PASSWORD, etc) and apply migrations
npx prisma migrate dev --name init

# Install dependencies
npm install

# Run development server
npm run dev
# or
yarn dev

# Run production server
npm run build && npm run start
yarn build && yarn start
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

## Refreshing the data
### Edit .env file,

There are 2 paths via the `/api` route to refresh the data, and 1 standalone script with not authentication
set `DATABASE_URL` to your Stacks Blockchain API database instance.
ex:

1. `/api/refresh` - this path is used with [upstash](upstash.com) [docs](https://docs.upstash.com/qstash/quickstarts/vercel-nextjs)
2. `/api/refresh_local` - this path is used with a local key defined below `API_SECRET_KEY`
3. `node lib/standalone_check.js` - [package.json](./package.json) will need to be set to `"type": "module"` to run this
```
DATABASE_URL="postgresql://user:pass@localhost:5432/stacks_blockchain_api?schema=stacks_blockchain_api"
```

To refresh the data, you can run a `curl` command like the following:
### Install dependencies

```bash
$ curl -sLw '\n' \
--request POST \
--url 'localhost:3000/api/refresh_local' \
--header 'Authorization: Bearer secret'
npm install
```

With upstash:
### Run development server

```bash
$ curl -sL \
--request POST \
--url "https://qstash.upstash.io/v1/publish/<url of app>/api/refresh" \
--header "Authorization: Bearer <refer to upstash docs>"
npm run dev
```

## Adding more checks
### Run production server

In folder `./status_checks`, each json file defines an API request to [stacksonchain](https://stacksonchain.com).
To add a new check, copy/paste an existing file and modify the query (be sure to set `"disabled": false` to enable the check).
Refer to the json schemas:
```bash
npm run build && npm run prismamigrate && npm run start
```

- [status_checks_array.schema.json](./status_checks_array.schema.json)
- [status_checks_string.schema.json](./status_checks_string.schema.json)
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

## Configuration

### Environment Variables

| Name | Description | Default Value |
| ---------------- | -------------------------------- | -------------------------------------------------------- |
| `NODE_ENV` | Sets the node environment | `development` |
| `DATABASE_URL` | Postgresql connection URI | `postgresql://postgres:postgres@localhost:5432/postgres` |
| `API_SECRET_KEY` | Key to refresh the data via POST | `secret` |
| Name | Description | Default Value |
| -------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `NODE_ENV` | Sets the node environment | `development` |
| `DATABASE_URL` | Postgresql connection URI | `postgresql://user:pass@localhost:5432/stacks_blockchain_api?schema=stacks_blockchain_api` |
| `CRON_SECRET` | Auth key to refresh mempool data via [vercel cron](https://vercel.com/docs/cron-jobs) | `secret` |
38 changes: 38 additions & 0 deletions app/api/cron/mempool-txs/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// https://nextjs.org/docs/app/building-your-application/routing/route-handlers
import { NextRequest, NextResponse } from 'next/server';
import { MempoolTxTypeI, apiRoot, getData } from '../../../../lib/util';
import { addMempoolSize } from '../../../datastore/nodeDB';

export async function GET(req: NextRequest) {
try {
const authHeader = req.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new NextResponse('Unauthorized', {
status: 401,
});
}
const size = await getMempoolSize();
await addMempoolSize(size);
return NextResponse.json({ size });
} catch (error) {
const msg = 'Failed to save mempool transactions';
console.error({ msg, error });
return NextResponse.json(
{ error: msg },
{
status: 500,
}
);
}
}

const getMempoolSize = async () => {
const mempool = await getData(`${apiRoot}/extended/v1/tx/mempool/stats`);
const txTypeCounts = mempool.tx_type_counts as MempoolTxTypeI;
const size =
txTypeCounts.token_transfer +
txTypeCounts.smart_contract +
txTypeCounts.contract_call +
txTypeCounts.poison_microblock;
return size;
};
5 changes: 5 additions & 0 deletions app/api/status/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NextResponse } from 'next/server';

export async function GET(req) {
return NextResponse.json({ status: 'OK' });
}
26 changes: 26 additions & 0 deletions app/components/BlockFullness.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import {
BlockExecutionCostDB,
getBlockFullnessPercentages,
getMax,
} from '../../lib/util';

type NewType = BlockExecutionCostDB;

interface BlockFullnessProps {
block: NewType;
}

const BlockFullness = async ({ block }: BlockFullnessProps) => {
const fullnessPerc = await getBlockFullnessPercentages({ block });
const maxFullness = getMax(fullnessPerc);

return (
<>
<div>Block Fullness:</div>
<div>{maxFullness.toFixed(2)}%</div>
</>
);
};

export default BlockFullness;
12 changes: 12 additions & 0 deletions app/components/BlockHash.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import Card from './Card';

const BlockHash = ({ blockhash }: { blockhash: string }) => {
return (
<div className='block-hash col-span-1'>
<Card title='Blockhash' value={blockhash} />
</div>
);
};

export default BlockHash;
12 changes: 12 additions & 0 deletions app/components/BlockHeight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import Card from './Card';

const BlockHeight = ({ height }: { height: number }) => {
return (
<div className='block-height col-span-1'>
<Card title='Block height' value={height.toLocaleString()} />
</div>
);
};

export default BlockHeight;
36 changes: 36 additions & 0 deletions app/components/BlocksPerTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import {
getTimestampFromNow,
millisecondsPerHour,
queryLineChartData,
} from '../../lib/util';
import { blocks } from '@prisma/client';
import Card from './Card';

interface BlocksPerTimeProps {
blocks: Pick<blocks, 'burn_block_time' | 'block_height'>[];
}

const BlocksPerTime = async ({ blocks }: BlocksPerTimeProps) => {
const data = (await queryLineChartData('BlocksPerHour')) as {
hour: Date;
blocks: number;
}[];
const startTimestamp = getTimestampFromNow(millisecondsPerHour) / 1000;
const blocksInLastHour = blocks.filter(
(b) => b.burn_block_time >= startTimestamp
);
return (
<div className='blocks-per-time col-span-2'>
<Card
title='Blocks in the last hour'
value={blocksInLastHour.length.toLocaleString()}
data={data}
x='hour'
y='blocks'
/>
</div>
);
};

export default BlocksPerTime;
Empty file.
40 changes: 40 additions & 0 deletions app/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import LineChart from './Charts/LineChart';

interface CardProps {
title: string;
value: string;
data?: any[];
x?: string;
y?: string;
}

const Card = ({ title, value, data, x, y }: CardProps) => {
return (
<div className='card flex h-full items-center justify-center rounded-lg border border-gray-700 bg-[#081115] p-6 shadow-lg'>
<div className='flex w-full flex-col items-center justify-between md:flex-row'>
<div className='min-w-0 flex-1'>
<div className='flex items-center space-x-4'>
<div className='flex-1 overflow-hidden'>
<h2 className='text-lg font-bold leading-tight text-gray-400'>
{title}
</h2>
{value !== 'NaN' && (
<p className='mt-1 flex justify-center overflow-hidden text-xl font-semibold text-indigo-600 md:justify-normal'>
{value}
</p>
)}
</div>
</div>
</div>
{data && (
<div className='mt-4 min-w-0 flex-1 md:mt-0'>
<LineChart data={data} xLabel={x} yLabel={y} tooltipTitle={title} />
</div>
)}
</div>
</div>
);
};

export default Card;
Empty file added app/components/CardContent.tsx
Empty file.
15 changes: 15 additions & 0 deletions app/components/CardsGridLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

interface CardsGridLayoutProps {
children: React.ReactNode;
}

const CardsGridLayout = ({ children }: CardsGridLayoutProps) => {
return (
<div className='cards-grid-layout grid h-full w-[95%] grid-cols-1 gap-2 md:grid-cols-[repeat(6,_minmax(100px,1fr))]'>
{children}
</div>
);
};

export default CardsGridLayout;
Loading
Loading