matthew
2 years ago
21 changed files with 471 additions and 73 deletions
@ -0,0 +1,3 @@ |
|||||||
|
export interface StaticPageProps<T>{ |
||||||
|
props: T |
||||||
|
} |
@ -0,0 +1,141 @@ |
|||||||
|
import { Doughnut, Line } from "react-chartjs-2" |
||||||
|
import { format } from "date-fns" |
||||||
|
import { useQuery } from "@blitzjs/rpc" |
||||||
|
import { useState } from "react" |
||||||
|
import { useTranslation } from "react-i18next" |
||||||
|
import { ChartData } from "chart.js/auto" |
||||||
|
|
||||||
|
import getActualSitesState from "app/stateSites/queries/getActualSitesState" |
||||||
|
import getHistoryOfSitesState from "app/stateSites/queries/getHistoryOfSitesState" |
||||||
|
import { InfluxPeriod } from "services/modules/influxdb/types" |
||||||
|
import { cn } from "app/core/helpers/common" |
||||||
|
|
||||||
|
import s from "./styles.module.css" |
||||||
|
|
||||||
|
interface HistoryOfStateItem { |
||||||
|
value: number |
||||||
|
time: string |
||||||
|
} |
||||||
|
|
||||||
|
interface HistoryOfState { |
||||||
|
all: HistoryOfStateItem[] |
||||||
|
available: HistoryOfStateItem[] |
||||||
|
} |
||||||
|
|
||||||
|
export interface StatePageProps { |
||||||
|
actualState: Awaited<ReturnType<typeof getActualSitesState>> |
||||||
|
historyOfState: HistoryOfState |
||||||
|
} |
||||||
|
|
||||||
|
const availableSitesColor = "#08c" |
||||||
|
const allSitesColor = "hsl(200 15% 81% / 1)" |
||||||
|
|
||||||
|
const getDohnutData = (data: number[]) => ({ |
||||||
|
labels: ["Available", "All"], |
||||||
|
datasets: [ |
||||||
|
{ |
||||||
|
data: data, |
||||||
|
backgroundColor: [availableSitesColor, allSitesColor], |
||||||
|
borderColor: [availableSitesColor, "rgba(0,0,0,0)"], |
||||||
|
borderWidth: 1, |
||||||
|
}, |
||||||
|
], |
||||||
|
}) |
||||||
|
|
||||||
|
const liteOptions = { |
||||||
|
responsive: true, |
||||||
|
plugins: { |
||||||
|
legend: { |
||||||
|
display: false, |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
intersect: false, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
scales: { |
||||||
|
x: { |
||||||
|
display: false, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
export const getGraphData = ( |
||||||
|
data: HistoryOfStateItem[], |
||||||
|
label: string, |
||||||
|
color: string |
||||||
|
): ChartData<"line", number[], string> => ({ |
||||||
|
labels: data.map((item) => format(new Date(item.time), "dd.MM.yy hh:mm")), |
||||||
|
datasets: [ |
||||||
|
{ |
||||||
|
label, |
||||||
|
data: data.map((item) => item.value), |
||||||
|
backgroundColor: color, |
||||||
|
borderColor: color, |
||||||
|
tension: 0.3, |
||||||
|
}, |
||||||
|
], |
||||||
|
}) |
||||||
|
|
||||||
|
interface HistoryItemProps { |
||||||
|
data: ChartData<"line", number[], string> |
||||||
|
title: string |
||||||
|
} |
||||||
|
|
||||||
|
const HistoryItem = ({ data, title }: HistoryItemProps) => { |
||||||
|
return ( |
||||||
|
<div className={s.historyStateItem}> |
||||||
|
<div>{title}</div> |
||||||
|
<Line options={liteOptions} data={data} /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const State = ({ actualState, historyOfState: historyOfStatePreloaded }: StatePageProps) => { |
||||||
|
const [historyPeriod, setHistoryPeriod] = useState(InfluxPeriod.D) |
||||||
|
const { t } = useTranslation() |
||||||
|
const [historyOfState] = useQuery(getHistoryOfSitesState, historyPeriod, { |
||||||
|
suspense: false, |
||||||
|
keepPreviousData: true, |
||||||
|
initialData: historyOfStatePreloaded, |
||||||
|
}) |
||||||
|
console.log(historyPeriod, historyOfState) |
||||||
|
return ( |
||||||
|
<div className={s.root}> |
||||||
|
<div className={s.title}>{t("state.title")}</div> |
||||||
|
<div className={s.doughnutAvailable}> |
||||||
|
<Doughnut |
||||||
|
data={getDohnutData([actualState.availableDomainsCount, actualState.allDomainsCount])} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div className={s.actualStateWrapper}> |
||||||
|
<span className={s.availableCount}>{actualState.availableDomainsCount}</span>{" "} |
||||||
|
{t("state.siteOutOf")} <span className={s.allCount}>{actualState.allDomainsCount}</span> |
||||||
|
<span className={s.areNowAvailable}>{t("state.areNowAvailable")}</span> |
||||||
|
</div> |
||||||
|
{historyOfState && ( |
||||||
|
<div className={s.historyStateWrapper}> |
||||||
|
<div className={s.historyStatePeriodsWrapper}> |
||||||
|
{Object.values(InfluxPeriod).map((i) => ( |
||||||
|
<button |
||||||
|
onClick={() => setHistoryPeriod(i)} |
||||||
|
className={cn(s.historyStatePeriodsItem, { [s.active]: historyPeriod === i })} |
||||||
|
> |
||||||
|
{i} |
||||||
|
</button> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
<HistoryItem |
||||||
|
title="All sites count" |
||||||
|
data={getGraphData(historyOfState.all, "All sites count", availableSitesColor)} |
||||||
|
/> |
||||||
|
<HistoryItem |
||||||
|
title="Available sites count" |
||||||
|
data={getGraphData(historyOfState.available, "Available sites count", allSitesColor)} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export default State |
@ -0,0 +1 @@ |
|||||||
|
export { default } from "./State" |
@ -0,0 +1,67 @@ |
|||||||
|
.root { |
||||||
|
/* margin-right: var(--logoWrapperWidth); */ |
||||||
|
} |
||||||
|
|
||||||
|
.actualStateWrapper { |
||||||
|
font-size: 20px; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
.availableCount { |
||||||
|
font-size: 100px; |
||||||
|
margin-right: 10px; |
||||||
|
color: var(--button_primary); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.allCount { |
||||||
|
margin: 0px 10px; |
||||||
|
font-size: 30px; |
||||||
|
color: var(--text_secondary); |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
.areNowAvailable { |
||||||
|
} |
||||||
|
.doughnutAvailable { |
||||||
|
width: 200px; |
||||||
|
margin: auto; |
||||||
|
} |
||||||
|
|
||||||
|
.title { |
||||||
|
font-size: 40px; |
||||||
|
text-align: center; |
||||||
|
margin: auto; |
||||||
|
margin-bottom: 20px; |
||||||
|
/* font-weight: 500; */ |
||||||
|
} |
||||||
|
|
||||||
|
.historyStateWrapper { |
||||||
|
display: flex; |
||||||
|
justify-content: space-around; |
||||||
|
flex-wrap: wrap; |
||||||
|
max-width: 1000px; |
||||||
|
margin: auto; |
||||||
|
} |
||||||
|
.historyStateItem { |
||||||
|
width: 400px; |
||||||
|
} |
||||||
|
|
||||||
|
.historyStatePeriodsWrapper { |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
.historyStatePeriodsItem { |
||||||
|
border-radius: 5px; |
||||||
|
margin: 5px; |
||||||
|
border: none; |
||||||
|
background: rgba(0, 0, 0, 0.04); |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.historyStatePeriodsItem.active { |
||||||
|
background: var(--button_primary); |
||||||
|
color: white; |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
import db from "db" |
||||||
|
|
||||||
|
export default async function getActualSitesState() { |
||||||
|
const allDomainsCount = await db.nftDomain.count() |
||||||
|
const availableDomainsCount = await db.nftDomain.count({ where: { available: true } }) |
||||||
|
|
||||||
|
return { |
||||||
|
allDomainsCount, |
||||||
|
availableDomainsCount, |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
import influxdb from "services/modules/influxdb" |
||||||
|
import { InfluxPeriod } from "services/modules/influxdb/types" |
||||||
|
|
||||||
|
export default async function getActualSitesState(period: InfluxPeriod = InfluxPeriod.W) { |
||||||
|
return await influxdb.getHistoryOfState(period) |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
import { Suspense } from "react" |
||||||
|
|
||||||
|
import Layout from "app/core/layouts/Layout" |
||||||
|
import getActualSitesState from "app/stateSites/queries/getActualSitesState" |
||||||
|
import getHistoryOfSitesState from "app/stateSites/queries/getHistoryOfSitesState" |
||||||
|
import { BlitzPage } from "@blitzjs/next" |
||||||
|
import State from "app/core/pages/State" |
||||||
|
import { gSP } from "app/blitz-server" |
||||||
|
|
||||||
|
import { ErrorBoundary } from "@blitzjs/next" |
||||||
|
|
||||||
|
import { |
||||||
|
ServerSidePropsContext, |
||||||
|
} from "app/core/contextProviders/serverSidePropsProvider" |
||||||
|
import { StatePageProps } from "app/core/pages/State/State" |
||||||
|
import { StaticPageProps } from "app/core/commonTypes" |
||||||
|
|
||||||
|
function ErrorFallback({ error, resetErrorBoundary }) { |
||||||
|
return ( |
||||||
|
<div role="alert"> |
||||||
|
<p>Something went wrong:</p> |
||||||
|
<pre>{error.message}</pre> |
||||||
|
<button onClick={resetErrorBoundary}>Try again</button> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const StatePage: BlitzPage<StatePageProps> = (props) => { |
||||||
|
return ( |
||||||
|
<ErrorBoundary |
||||||
|
FallbackComponent={ErrorFallback} |
||||||
|
onReset={() => { |
||||||
|
// reset the state of your app so the error doesn't happen again
|
||||||
|
}} |
||||||
|
> |
||||||
|
<ServerSidePropsContext.Provider value={props}> |
||||||
|
<Layout title="State of TON Sites" withoutPaddings> |
||||||
|
<Suspense fallback="Loading..."> |
||||||
|
<State {...props} /> |
||||||
|
</Suspense> |
||||||
|
</Layout> |
||||||
|
</ServerSidePropsContext.Provider> |
||||||
|
</ErrorBoundary> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export const getStaticProps = gSP(async ({ params, ctx }): StaticPageProps<StatePageProps> => { |
||||||
|
const actualState = await getActualSitesState(); |
||||||
|
const historyOfState = await getHistoryOfSitesState(); |
||||||
|
return { |
||||||
|
props: { |
||||||
|
actualState, |
||||||
|
historyOfState |
||||||
|
}, |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
export default StatePage |
@ -0,0 +1,17 @@ |
|||||||
|
import path from "path" |
||||||
|
import dotenv from "dotenv" |
||||||
|
dotenv.config({ path: path.resolve(__dirname, "../.env.local") }) |
||||||
|
|
||||||
|
import { Ctx } from "blitz" |
||||||
|
import db from "db" |
||||||
|
|
||||||
|
|
||||||
|
import { InfluxPeriod } from "./modules/influxdb/types" |
||||||
|
import influxdb from "./modules/influxdb" |
||||||
|
|
||||||
|
export default async function run(period:InfluxPeriod = InfluxPeriod.W) { |
||||||
|
const res = await influxdb.getHistoryOfState(period); |
||||||
|
console.log(res) |
||||||
|
|
||||||
|
} |
||||||
|
run() |
@ -1,46 +1,51 @@ |
|||||||
import { fluxDuration } from "@influxdata/influxdb-client"; |
import { fluxDuration } from "@influxdata/influxdb-client" |
||||||
import { influxBucket, influxHost, influxPointName } from "./constants"; |
import { influxBucket, influxHost, influxPointName } from "./constants" |
||||||
import { InfluxField, InfluxPeriod } from "./types"; |
import { InfluxField, InfluxPeriod } from "./types" |
||||||
|
|
||||||
export const influxQuery = (field: InfluxField, fetchPeriod: InfluxPeriod) =>{ |
export const influxQuery = (field: InfluxField, fetchPeriod: InfluxPeriod) => { |
||||||
let start; |
let start |
||||||
let period; |
let period |
||||||
|
|
||||||
switch(fetchPeriod){
|
switch (fetchPeriod) { |
||||||
case InfluxPeriod.H: |
case InfluxPeriod.H: |
||||||
start ='-1h' |
start = "-1h" |
||||||
period = '6m' |
period = "6m" |
||||||
|
break |
||||||
case InfluxPeriod.D: |
case InfluxPeriod.D: |
||||||
start ='-1d' |
start = "-1d" |
||||||
period = '144m' |
period = "144m" |
||||||
case InfluxPeriod.W: |
break |
||||||
start ='-1w' |
// case InfluxPeriod.W:
|
||||||
period = '1008m' |
// start = "-1w"
|
||||||
case InfluxPeriod.M: |
// period = "1008m"
|
||||||
start ='-1mo' |
// break
|
||||||
period = '3d' |
// case InfluxPeriod.M:
|
||||||
case InfluxPeriod.Y: |
// start = "-1mo"
|
||||||
start ='-1y' |
// period = "3d"
|
||||||
period = '36d' |
// break
|
||||||
case InfluxPeriod.tenminute: |
// case InfluxPeriod.Y:
|
||||||
start ='10m' |
// start = "-1y"
|
||||||
period = '1m' |
// period = "36d"
|
||||||
|
// break
|
||||||
|
// case InfluxPeriod.tenminute:
|
||||||
|
// start ='-10m'
|
||||||
|
// period = '1m'
|
||||||
} |
} |
||||||
|
|
||||||
const influxPeriod = fluxDuration(period); |
const query = `from(bucket: "${influxBucket}")
|
||||||
const influxStart = fluxDuration(start); |
|
||||||
return `from(bucket: "${influxBucket}")
|
|
||||||
|> range(start: ${start}, stop: now()) |
|> range(start: ${start}, stop: now()) |
||||||
|> filter(fn: (r) => r["host"] == "${influxHost}") |
|> filter(fn: (r) => r["host"] == "${influxHost}") |
||||||
|> filter(fn: (r) => r["_field"] == "${field}") |
|> filter(fn: (r) => r["_field"] == "${field}") |
||||||
|> filter(fn: (r) => r["_measurement"] == "${influxPointName}") |
|> filter(fn: (r) => r["_measurement"] == "${influxPointName}") |
||||||
|> aggregateWindow(every: ${period}, fn: mean, createEmpty: false) |
|> aggregateWindow(every: ${period}, fn: mean, createEmpty: false) |
||||||
|> yield(name: "mean")` |
|> yield(name: "mean")` |
||||||
|
|
||||||
|
return query |
||||||
} |
} |
||||||
|
|
||||||
export const processInfluxResult = (res:unknown[]) => { |
export const processInfluxResult = (res: unknown[]) => { |
||||||
return res.map(i=>({ |
return res.map((i) => ({ |
||||||
value: i._value, |
value: i._value, |
||||||
time: i._time |
time: i._time, |
||||||
})) |
})) |
||||||
} |
} |
Loading…
Reference in new issue