diff --git a/package.json b/package.json index 05ba54fde5..6dad04a45a 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "build:prod:frontend": "lerna run build:prod:frontend", "build:dev": "npm-run-all build:dev:backend webpack:dev:web build:dev:frontend", "build:dev:backend": "npm-run-all lint clean codegen tsc build:dev:backend:postbuild", - "build:dev:frontend": "lerna run build:dev:frontend --scope=\"@hyperledger/cactus-example-supply-chain-frontend\" && lerna run build:dev:frontend --scope=\"@hyperledger/cactus-example-carbon-accounting-frontend\"", + "build:dev:frontend": "lerna run build:dev:frontend --scope=\"@hyperledger/cactus-example-supply-chain-frontend\" && lerna run build:dev:frontend --scope=\"@hyperledger/cactus-example-carbon-accounting-frontend\"&& lerna run build:dev:frontend --scope=\"packages/cacti-cmd-gui-app\"", "build:dev:common": "lerna exec --stream --scope '*/*common' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:backend:postbuild": "lerna run build:dev:backend:postbuild", "test:cmd-api-server": "tap --ts --timeout=600 \"packages/cactus-*cmd-api-server/src/test/typescript/{unit,integration}/\"", diff --git a/packages/cacti-cmd-gui-app/README.md b/packages/cacti-cmd-gui-app/README.md new file mode 100644 index 0000000000..8f63795e1e --- /dev/null +++ b/packages/cacti-cmd-gui-app/README.md @@ -0,0 +1,61 @@ +# `@hyperledger/cacti-cmd-gui-app` + +This component allows viewing ledger data in Supabase or other postgreSQL compatible database. The data is fed to supabase by persistence plugins for each ledgers. + +## Summary + +- [`@hyperledger/cacti-cmd-gui-app`](#hyperledgercacti-cmd-gui-app) + - [Summary](#summary) + - [Remarks](#remarks) + - [Getting Started](#getting-started) + - [Prerequisites using yarn](#prerequisites-using-yarn) + - [Alternative Prerequisites using npm](#alternative-prerequisites-using-npm) + - [Usage](#usage) + - [Contributing](#contributing) + - [License](#license) + - [Acknowledgments](#acknowledgments) + +## Remarks + +- Plugin requires running Supabase or other database and persistence plugins in order to properly view ledger data. +- Currently, fabric and ethereum based ledgers are supported. + +## Getting Started + +Clone the git repository on your local machine. Follow these instructions that will get you a copy of the project up and running on your local machine for development and testing purposes. + +### Prerequisites using yarn + +In the root of the project, execute the command to install and build the dependencies. It will also build this GUI front-end component: + +```sh +yarn run build +``` +### Alternative Prerequisites using npm + +In the root of the project, execute the command to install and build the dependencies. It will also build this GUI front-end component: + +```sh +npm install +``` + +### Usage +- Run Supabase instance (see documentation for detailed instructions). For development purposes, you can use our image located in `tools/docker/supabase-all-in-one`. +- Run one or more persistence plugins: + - [Ethereum](../cacti-plugin-persistence-ethereum) + - [Fabric] (../cacti-plugin-persistence-fabric) +- Edit [Supabase configuration file](./src/supabase-client.tsx), set correct supabase API URL and service_role key. +- Execute `yarn run start` or `npm start` in this package directory. +- The running application address: http://localhost:3001/ (can be changed in [Vite configuration](./vite.config.ts)) + +## Contributing + +We welcome contributions to Hyperledger Cacti in many forms, and there’s always plenty to do! + +Please review [CONTIRBUTING.md](../../CONTRIBUTING.md) to get started. + +## License + +This distribution is published under the Apache License Version 2.0 found in the [LICENSE](../../LICENSE) file. + +## Acknowledgments \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/index.html b/packages/cacti-cmd-gui-app/index.html new file mode 100644 index 0000000000..6da7c5535b --- /dev/null +++ b/packages/cacti-cmd-gui-app/index.html @@ -0,0 +1,16 @@ + + + + + + + + Cacti GUI + + + +
+ + + + diff --git a/packages/cacti-cmd-gui-app/package.json b/packages/cacti-cmd-gui-app/package.json new file mode 100644 index 0000000000..4bb828201f --- /dev/null +++ b/packages/cacti-cmd-gui-app/package.json @@ -0,0 +1,76 @@ +{ + "name": "@hyperledger/cactus-cmd-gui-app", + "version": "1.1.3", + "description": "Cacti GUI for visualizing ledger data.", + "keywords": [ + "Hyperledger", + "Cacti", + "Integration", + "Blockchain", + "Distributed Ledger Technology" + ], + "homepage": "https://github.com/hyperledger/cacti#readme", + "bugs": { + "url": "https://github.com/hyperledger/cacti/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cacti.git" + }, + "license": "Apache-2.0", + "author": { + "name": "Hyperledger Cacti Contributors", + "email": "cacti@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cacti" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "url": "https://example.com" + }, + { + "name": "Eryk Baranowski", + "email": "eryk.baranowski@fujitsu.com", + "url": "https://www.fujitsu.com/global/" + }, + { + "name": "Barnaba Pawelczak", + "email": "barnaba.pawelczak@fujitsu.com", + "url": "https://www.fujitsu.com/global/" + } + ], + "scripts": { + "start": "vite", + "serve": "vite preview", + "build": "yarn run build:prod:frontend", + "build:prod:frontend": "vite build" + }, + "devDependencies": { + "autoprefixer": "10.4.8", + "postcss": "8.4.16", + "supabase": "1.28.4", + "typescript-plugin-css-modules": "4.1.1", + "vite": "3.0.0", + "vite-plugin-solid": "2.3.0" + }, + "dependencies": { + "@solidjs/router": "0.4.2", + "@supabase/supabase-js": "1.35.6", + "apexcharts": "3.36.0", + "chart.js": "3.9.1", + "moment": "2.29.4", + "solid-apexcharts": "0.1.6", + "solid-icons": "1.0.4", + "solid-js": "1.5.7", + "solid-slider": "1.3.9", + "solid-toast": "^0.4.0" + }, + "engines": { + "npm": ">=6" + }, + "publishConfig": { + "access": "public" + }, + "watch": {} +} diff --git a/packages/cacti-cmd-gui-app/postcss.config.js b/packages/cacti-cmd-gui-app/postcss.config.js new file mode 100644 index 0000000000..a47ef4f952 --- /dev/null +++ b/packages/cacti-cmd-gui-app/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {}, + }, +}; diff --git a/packages/cacti-cmd-gui-app/src/App.module.css b/packages/cacti-cmd-gui-app/src/App.module.css new file mode 100644 index 0000000000..cba488b71c --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/App.module.css @@ -0,0 +1,12 @@ +.main { + width: 100%; +} +.content { + margin: 1rem 2rem; +} + +@media (max-width: 1699px) { + .content { + margin: 1rem; + } +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/App.tsx b/packages/cacti-cmd-gui-app/src/App.tsx new file mode 100644 index 0000000000..a810c8f041 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/App.tsx @@ -0,0 +1,18 @@ +import { Component } from "solid-js"; +import Pages from "./pages/Pages"; +import Menu from "./components/UI/Menu/Menu"; +// @ts-expect-error +import styles from "./App.module.css"; + +const App: Component = () => { + return ( +
+ +
+ +
+
+ ); +}; + +export default App; diff --git a/packages/cacti-cmd-gui-app/src/assets/favicon.ico b/packages/cacti-cmd-gui-app/src/assets/favicon.ico new file mode 100644 index 0000000000..b836b2bcca Binary files /dev/null and b/packages/cacti-cmd-gui-app/src/assets/favicon.ico differ diff --git a/packages/cacti-cmd-gui-app/src/components/AccountCard/AccountCard.module.css b/packages/cacti-cmd-gui-app/src/components/AccountCard/AccountCard.module.css new file mode 100644 index 0000000000..45da5dea56 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/AccountCard/AccountCard.module.css @@ -0,0 +1,19 @@ +.card { + display: flex; + justify-content: center; + background-color: rgb(252, 249, 249); + align-items: center; + height: 5rem; + padding: 0 2rem; + margin-top: 5px; + border-radius: 10px; + border: .5px solid rgb(224, 228, 224); + width: 35rem; + font-size: 18px; +} + +.card:hover { + cursor: pointer; + background-color: #ffffff; + border:1px solid rgb(39, 153, 39); +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/components/AccountCard/AccountCard.tsx b/packages/cacti-cmd-gui-app/src/components/AccountCard/AccountCard.tsx new file mode 100644 index 0000000000..322125ca5e --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/AccountCard/AccountCard.tsx @@ -0,0 +1,19 @@ +import { useParams, useNavigate } from "@solidjs/router"; +import { Component } from "solid-js"; +// @ts-expect-error +import styles from "./AccountCard.module.css"; + +const AccountCard: Component<{ address: string }> = (props) => { + const params = useParams(); + const navigate = useNavigate(); + const handleClick = () => { + navigate(`/${params.standard}/${props.address}`); + }; + return ( +
+ {props.address} +
+ ); +}; + +export default AccountCard; diff --git a/packages/cacti-cmd-gui-app/src/components/BlockCard/BlockCard.module.css b/packages/cacti-cmd-gui-app/src/components/BlockCard/BlockCard.module.css new file mode 100644 index 0000000000..3e90ee76fa --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/BlockCard/BlockCard.module.css @@ -0,0 +1,33 @@ +.block-card { + display: flex; + gap: 1rem; + background-color: rgb(252, 249, 249); + height: 5rem; + align-items: center; + justify-content: space-around; + width: 100%; + padding: 1rem 0rem; + margin-top: 5px; + border-radius: 10px; + border: .5px solid rgb(242, 245, 242); + max-height: 100vh; + font-size: 14px; +} + +.block-card:hover { + cursor: pointer; + background-color: #ffffff; +} + +.block-num { + color: rgb(12, 105, 12); +} + +.block-hash { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: min-content; + max-width: 50%; +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/components/BlockCard/BlockCard.tsx b/packages/cacti-cmd-gui-app/src/components/BlockCard/BlockCard.tsx new file mode 100644 index 0000000000..774b7163f6 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/BlockCard/BlockCard.tsx @@ -0,0 +1,28 @@ +import { useNavigate } from "@solidjs/router"; +import { HiSolidHashtag } from "solid-icons/hi"; +import { Component } from "solid-js"; +// @ts-expect-error +import styles from "./BlockCard.module.css"; + +const BlockCard: Component<{ + number: string; + created_at: Date; + hash: string; +}> = (props) => { + const navigate = useNavigate(); + const handleClick = () => { + navigate(`/blockDetails/${props.number}`); + }; + + return ( +
+

{props.created_at.toLocaleString()}

+

{props.number}

+

+ {props.hash} +

+
+ ); +}; + +export default BlockCard; diff --git a/packages/cacti-cmd-gui-app/src/components/CardWrapper/CardWrapper.module.css b/packages/cacti-cmd-gui-app/src/components/CardWrapper/CardWrapper.module.css new file mode 100644 index 0000000000..d20ba47158 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/CardWrapper/CardWrapper.module.css @@ -0,0 +1,85 @@ +.wrapper { + background-color: rgb(253, 253, 253); + padding: 1rem; + border-radius: 10px; + border: 1px solid rgb(233, 236, 233); + height: fit-content; +} + +.wrapper-half-width { + width: 50%; +} + +.wrapper-full-width { + width: 100%; +} + +.wrapper-cards { + width: 100%; + display: flex; + justify-content: center; + padding: 1rem; +} + +.wrapper-title { + margin-top: .5rem; + display: flex; + gap:5px; + align-items: center; + font-weight: 700; + font-size: 1.2rem; + color: rgb(9, 75, 9); +} + +.wrapper-btns { + display: flex; + justify-content: flex-end; + padding-right: 1rem; +} + +.wrapper-header { + width: 100%; + display: flex; + justify-content: space-between; + padding: 0 1rem; +} + +.wrapper-columns { + display: flex; + justify-content: space-around; + background-color: rgb(243, 239, 239); + align-items: center; + border-radius: 10px; + border: 1px solid rgb(233, 236, 233); + height: 50px; +} + +.wrapper-columns span { + display: flex; + width: 150px; +} + +.wrapper-search { + display: flex; + gap: 5px; +} + +@media (max-width: 1699px) { + .wrapper { + width: 100%; + } + + .wrapper-header { + padding-left: 0; + padding-right: 0; + } + + .wrapper-cards { + flex-direction: column; + padding: 1rem 0; + } + + .wrapper-title svg { + margin-bottom: -3px; + } +} diff --git a/packages/cacti-cmd-gui-app/src/components/CardWrapper/CardWrapper.tsx b/packages/cacti-cmd-gui-app/src/components/CardWrapper/CardWrapper.tsx new file mode 100644 index 0000000000..2d4633e752 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/CardWrapper/CardWrapper.tsx @@ -0,0 +1,168 @@ +import { + createSignal, + createEffect, + ParentComponent, + onCleanup, +} from "solid-js"; +import { TbCactus } from "solid-icons/tb"; +import { useNavigate } from "@solidjs/router"; +import Button from "../UI/Button/Button"; +import Search from "../UI/Search/Search"; +import CustomTable from "../UI/CustomTable/CustomTable"; +import { TableProps } from "../../schema/supabase-types"; +import Pagination from "../Pagination/Pagination"; +// @ts-expect-error +import styles from "./CardWrapper.module.css"; +import EmptyTablePlaceholder from "../UI/CustomTable/EmptyTablePlaceholder/EmptyTablePlaceholder"; + +type cardWrapperProp = { + filters?: string[]; + data: any[]; + display: string; + trimmed?: boolean; + columns?: TableProps; + title: string; + getSearchValue?: (val: string) => {}; +}; + +const pageSize: number = 6; + +const CardWrapper: ParentComponent = (props) => { + const navigate = useNavigate(); + const [searchKey, setSearchKey] = createSignal(""); + const [filteredData, setFilteredData] = createSignal([]); + const [paginatedData, setPaginatedData] = createSignal([]); + const [currentPage, setCurrentPage] = createSignal(1); + const [totalPages, setTotalPages] = createSignal(1); + const [viewport, setViewport] = createSignal(""); + + const handleGoToPage = (pageNumber: number) => { + if (pageNumber < 1 || pageNumber > totalPages()) return; + setCurrentPage(pageNumber); + }; + + const handleNextPage = () => { + if (currentPage() === totalPages()) return; + setCurrentPage((prev) => prev + 1); + }; + + const handlePrevPage = () => { + if (currentPage() === 1) return; + setCurrentPage((prev) => prev - 1); + }; + + const filterData = () => { + const { filters, data } = props; + if (searchKey().length === 0) { + setFilteredData(data); + return; + } + let newData = data.filter((row) => { + let isMatch: boolean = false; + filters?.forEach((property) => { + if (row[property]?.toString().toLowerCase().includes(searchKey())) { + isMatch = true; + } + }); + return isMatch; + }); + setFilteredData(newData); + }; + + const handleSearch = () => { + filterData(); + if (props.getSearchValue) { + props.getSearchValue(searchKey()); + } + }; + + createEffect(() => { + setFilteredData(props.data); + }); + + createEffect(() => { + const screenResized = () => + setViewport(window.innerWidth <= 1699 ? "small" : "wide"); + screenResized(); + window.addEventListener("resize", screenResized, true); + onCleanup(() => { + window.removeEventListener("resize", screenResized, true); + }); + }); + + createEffect(() => { + if (filteredData().length <= pageSize) { + setPaginatedData(filteredData()); + } else { + const firstEl = currentPage() * pageSize - pageSize; + setPaginatedData(filteredData().slice(firstEl, firstEl + pageSize)); + } + }); + + createEffect(() => { + const pageNum = Math.ceil(filteredData().length / pageSize); + setTotalPages(pageNum); + }); + + return ( +
+
+ + {props.title} + + {props.trimmed && viewport() === "small" && ( + + )} + {props.filters && ( +
+ setSearchKey(e)} + type="text" + placeholder="Type to search" + /> + +
+ )} +
+
+ {props?.columns && props.data?.length > 0 && ( + + )} + {props?.data?.length === 0 && } +
+
+ {" "} + {props.trimmed && viewport() === "wide" && ( + + )} +
+ {!props.trimmed && ( + + )} +
+ ); +}; + +export default CardWrapper; diff --git a/packages/cacti-cmd-gui-app/src/components/Chart/Chart.module.css b/packages/cacti-cmd-gui-app/src/components/Chart/Chart.module.css new file mode 100644 index 0000000000..be4a07ddb5 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/Chart/Chart.module.css @@ -0,0 +1,29 @@ +.chart-wrapper { + width: 100%; + height:fit-content; + display: flex; + flex-direction: column; + gap: 1rem; + justify-content: center; + align-items: center; + background-color: rgb(253, 253, 253); + border-radius: 10px; + border: 1px solid rgb(238, 238, 238); +} + +.chart-wrapper > span { + margin-top: 1rem; + font-size: 24px; +} + +.chart-wrapper-line{ + padding:1rem; + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: rgb(253, 253, 253); + border-radius: 10px; + border: 1px solid rgb(238, 238, 238); +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/components/Chart/Chart.tsx b/packages/cacti-cmd-gui-app/src/components/Chart/Chart.tsx new file mode 100644 index 0000000000..23c1bf9188 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/Chart/Chart.tsx @@ -0,0 +1,66 @@ +import { SolidApexCharts } from "solid-apexcharts"; +import { createSignal, createEffect, Component } from "solid-js"; +import { ERC20Txn } from "../../schema/supabase-types"; +// @ts-expect-error +import styles from "./Chart.module.css"; + +const Chart: Component<{ chartData: any }> = (props) => { + const [chartProps, setChartProps] = createSignal<{ + series: any; + options: any; + }>({ + series: { + list: [ + { + name: "balance", + data: [], + }, + ], + }, + options: { + chart: { + id: "solidchart-example", + }, + xaxis: { + type: "datetime", + }, + }, + }); + + createEffect(async () => { + const { chartData } = props; + + setChartProps({ + options: { + chart: { + id: "solidchart-example", + }, + xaxis: { + categories: chartData()?.map((txn: ERC20Txn) => txn.token_address), + }, + }, + series: { + list: [ + { + name: "balance", + data: chartData()?.map((txn: ERC20Txn) => txn.balance), + }, + ], + }, + }); + }); + + return ( +
+ Balance + +
+ ); +}; + +export default Chart; diff --git a/packages/cacti-cmd-gui-app/src/components/Chart/LineChart.tsx b/packages/cacti-cmd-gui-app/src/components/Chart/LineChart.tsx new file mode 100644 index 0000000000..4ee210d8fb --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/Chart/LineChart.tsx @@ -0,0 +1,80 @@ +import { SolidApexCharts } from "solid-apexcharts"; +import { createSignal, createEffect, Component } from "solid-js"; +import moment from "moment"; +import { balanceDate } from "../../schema/supabase-types"; +// @ts-expect-error +import styles from "./Chart.module.css"; + +const LineChart: Component<{ chartData: any }> = (props) => { + const [chartProps, setChartProps] = createSignal({ + series: { + list: [ + { + name: "balance", + data: [], + }, + ], + }, + options: { + chart: { + id: "solidchart-example", + }, + xaxis: { + categories: [], + }, + }, + }); + + createEffect(async () => { + const { chartData } = props; + + setChartProps({ + options: { + chart: { + id: "solidchart-example", + }, + stroke: { + curve: "stepline", + }, + tooltip: { + x: { + show: true, + format: "dd MM yyyy h:mm", + formatter: undefined, + }, + }, + xaxis: { + type: "datetime", + categories: chartData()?.map((txn: balanceDate) => + moment(txn.created_at).format("YYYY-MM-DD h:mm:ss a"), + ), + labels: { + format: "dd MM yyyy h:mm", + }, + }, + }, + series: { + list: [ + { + name: "balance", + data: chartData()?.map((txn: balanceDate) => txn.balance), + }, + ], + }, + }); + }); + + return ( +
+ +
+ ); +}; + +export default LineChart; diff --git a/packages/cacti-cmd-gui-app/src/components/Pagination/Pagination.module.css b/packages/cacti-cmd-gui-app/src/components/Pagination/Pagination.module.css new file mode 100644 index 0000000000..0632e5e845 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/Pagination/Pagination.module.css @@ -0,0 +1,51 @@ +.pagination { + width: 100%; + padding: 1rem; + justify-content: flex-end; + display: flex; + align-items: center; + gap: 10px; +} + +.pagination-counter { + height: 2.5rem; + display: flex; + align-items: center; + padding: 0 1rem; + border-radius: 10px; + border: 1px solid rgb(204, 206, 205); +} + +.pagination-jump { + display: flex; + gap: 10px; + padding: 9px 1rem; + background-color: rgb(233, 229, 229); + border-radius: 10px; +} + +input { + border-radius: 10px; + border: 1px solid rgb(54, 51, 224); + padding: 0 0.5rem; + width: 7rem; + text-align: center; + font-size: 1rem; +} + +@media (max-width: 1699px) { + .pagination { + padding: 0; + justify-content: center; + position: relative; + } + + .pagination button { + min-width: 85px; + } + + .pagination-jump { + position: absolute; + top: 2.75rem; + } +} diff --git a/packages/cacti-cmd-gui-app/src/components/Pagination/Pagination.tsx b/packages/cacti-cmd-gui-app/src/components/Pagination/Pagination.tsx new file mode 100644 index 0000000000..abc483bb06 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/Pagination/Pagination.tsx @@ -0,0 +1,66 @@ +import Button from "../UI/Button/Button"; +import { + FaSolidAngleRight, + FaSolidAngleLeft, + FaSolidAnglesLeft, + FaSolidAnglesRight, +} from "solid-icons/fa"; +import { Component, createSignal } from "solid-js"; +// @ts-expect-error +import styles from "./Pagination.module.css"; + +type pagination = { + current: number; + total: number; + goToPage: (pageNumber: number) => void; + goNextPage: () => void; + goPrevPage: () => void; +}; + +const Pagination: Component = (props) => { + let inputRef: any; + const getInputValue = () => + inputRef?.value ? inputRef.value : props.current; + const [goToPageVisible, setGoToPageVisible] = createSignal(false); + + return ( +
+ + + + {goToPageVisible() === true && ( +
+ + +
+ )} + + +
+ ); +}; + +export default Pagination; diff --git a/packages/cacti-cmd-gui-app/src/components/TokenHeader/TokenAccount.tsx b/packages/cacti-cmd-gui-app/src/components/TokenHeader/TokenAccount.tsx new file mode 100644 index 0000000000..8a7125a1a7 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/TokenHeader/TokenAccount.tsx @@ -0,0 +1,20 @@ +import { BiRegularWallet } from "solid-icons/bi"; +import { Component } from "solid-js"; +// @ts-expect-error +import styles from "./TokenHeader.module.css"; + +const TokenAccount: Component<{ accountNum: string }> = (props) => { + return ( +
+ + {" "} + {" "} + {props.accountNum} + +
+ ); +}; + +export default TokenAccount; diff --git a/packages/cacti-cmd-gui-app/src/components/TokenHeader/TokenHeader.module.css b/packages/cacti-cmd-gui-app/src/components/TokenHeader/TokenHeader.module.css new file mode 100644 index 0000000000..2d9ef9600c --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/TokenHeader/TokenHeader.module.css @@ -0,0 +1,57 @@ +.token-header { + display: flex; + flex-direction: column; + width: 100%; + gap: 1rem; +} + +.token-details { + width: 100%; + height: min-content; + border: 1px solid rgb(240, 236, 236); + border-radius: 10px; + gap: 3rem; + padding: 1rem 2rem; + display: flex; + justify-content: flex-start; + align-items: center; + background-color: rgb(247, 245, 245); +} + +.token-details div { + display: flex; + align-items: center; + gap: 1rem; +} + +.token-icon { + height: 100%; + transform: translateY(10%); + color: rgb(34, 70, 70); +} + +.token-account { + font-size: 16px; + width: 100%; + height: min-content; + display: flex; + align-items: center; + justify-content: center; + background-color: rgb(247, 245, 245); + border-radius: 10px; + padding: 1rem; + padding-left: 2rem; +} + +.token-account span { + display: flex; + align-items: center; + gap: .5rem; +} + +.token-account-icon { + color: rgb(22, 92, 65); + font-size: 28px; + height: 30px; + width: 30px; +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/components/TokenHeader/TokenHeader.tsx b/packages/cacti-cmd-gui-app/src/components/TokenHeader/TokenHeader.tsx new file mode 100644 index 0000000000..9224480d95 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/TokenHeader/TokenHeader.tsx @@ -0,0 +1,52 @@ +import TokenAccount from './TokenAccount' +import { Component } from 'solid-js' +import { createSignal, createEffect } from 'solid-js' +import { TokenMetadata20 } from '../../schema/supabase-types' +import { supabase } from '../../supabase-client' +// @ts-expect-error +import styles from "./TokenHeader.module.css"; + +const TokenHeader: Component<{ accountNum: string; token_address: string }> = ( + props, +) => { + const [tokenData, setTokenData] = createSignal(); + + createEffect(async () => { + try { + const { data, error } = await supabase + .from(`token_metadata_erc20`) + .select("*") + .match({ address: props.token_address }); + console.log(data); + if (data?.[0]) { + setTokenData(data[0]); + } else { + throw new Error("Failed to load token details"); + } + } catch (error: any) { + console.error(error.message); + } + }, []); + + return ( +
+ +
+

+ Address: {props.token_address} +

+

+ Created at: {tokenData()?.created_at} +

+

+ Total supply: + {tokenData()?.total_supply} +

+
+
+ ); +}; + +export default TokenHeader; + + diff --git a/packages/cacti-cmd-gui-app/src/components/UI/Button/Button.module.css b/packages/cacti-cmd-gui-app/src/components/UI/Button/Button.module.css new file mode 100644 index 0000000000..eab9eabd84 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/Button/Button.module.css @@ -0,0 +1,77 @@ +.button { + color: rgb(14, 48, 23); + background-color: rgb(248, 248, 250); + height: 2.5rem; + display: flex; + align-items: center; + justify-content: center; + width: max-content; + min-width: 100px; + padding: 10px; + border: 1px solid rgb(32, 133, 77); + font-family: 'Roboto'; + border-radius: 10px; +} + +.button:hover { + background-color: rgb(219, 219, 224); + transform: scale(1.01); + cursor: pointer; +} + +.button-primary { + background-color: rgb(244, 247, 245); + color:rgb(14, 44, 14); + border-radius: 5px; + width:150px; +} + +.button-primary:hover { background-color: rgb(226, 253, 219);} + +.button-warn { background-color: rgb(155, 22, 13);} + +.button-warn:hover { background-color: rgb(114, 22, 16);} + +.button-menu{ + border:none; + background: transparent; + height: 100%; + transition: background-color 0.5s ease-out; + position:relative; + border-radius: 0; + +} +.button-menu:hover{ +color:rgb(0, 0, 0); +background-color: rgb(243, 242, 242); +} +.button-menu:hover:after { + content: ''; + display: block; + position: absolute; + left: 0; + right: 0; + bottom: 1px; + width: 100%; + height: 1px; + border-bottom: 2px solid green; + +} + +.button-link { + background: transparent; + border:none; + height: min-content; + color:rgb(64, 64, 228); +} + +.button-link:hover{ + background: transparent; + color:rgb(78, 78, 236); +} + +@media (max-width: 1699px) { + .button-primary { + font-size: 1rem; + } +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/components/UI/Button/Button.tsx b/packages/cacti-cmd-gui-app/src/components/UI/Button/Button.tsx new file mode 100644 index 0000000000..c69ad06233 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/Button/Button.tsx @@ -0,0 +1,28 @@ +import { ParentComponent } from "solid-js"; +// @ts-expect-error +import styles from "./Button.module.css"; + +const Button: ParentComponent<{ + type?: string; + onClick: () => void; + disabled?: boolean; +}> = (props) => { + type ObjectKey = keyof typeof styles; + const buttonTypeStyle = `button-${props.type}` as ObjectKey; + + const handleClick = (e: MouseEvent) => { + e.stopPropagation(); + props.onClick(); + }; + + return ( + + ); +}; + +export default Button; diff --git a/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/CustomTable.css b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/CustomTable.css new file mode 100644 index 0000000000..d94c8789f0 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/CustomTable.css @@ -0,0 +1,53 @@ +table { + border-collapse: separate; + border-spacing: 0; + width: 100%; + } + + tbody tr { + background-color: rgb(248, 248, 248); + border: 1px solid rgb(219, 241, 232); + border-radius: 10px; + } + + tbody tr:hover { + cursor: pointer; + background-color: rgb(235, 240, 237); + } + + th { + background-color: rgb(240, 235, 235); + border-style: none; + border-bottom: solid 1px rgb(223, 218, 218); + padding: 10px; + } + + td { + min-height: 2rem; + border-style: none; + border-bottom: solid 4px rgb(255, 255, 255); + padding: 1.5rem .5rem; + text-align: center; + } + + tr { + min-height: 20rem; + background-color: rgb(90, 103, 116); + padding: 1rem; + } + + tr:first-child th:first-child { + border-top-left-radius: 10px; + } + + tr:first-child th:last-child { + border-top-right-radius: 10px; + } + + tr:last-child td:first-child { + border-bottom-left-radius: 10px; + } + + tr:last-child td:last-child { + border-bottom-right-radius: 10px; + } \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/CustomTable.module.css b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/CustomTable.module.css new file mode 100644 index 0000000000..306f13ac57 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/CustomTable.module.css @@ -0,0 +1,137 @@ +table { + border-collapse: separate; + border-spacing: 0; + width: 100%; +} + +tbody tr { + background-color: rgb(248, 248, 248); + border: 1px solid rgb(219, 241, 232); + border-radius: 10px; +} + +tbody tr:hover { + cursor: pointer; + background-color: rgb(235, 240, 237); +} + +th { + background-color: rgb(240, 235, 235); + border-style: none; + border-bottom: solid 1px rgb(155, 153, 153); + padding: 10px; +} + +td { + min-height: 2rem; + border-style: none; + border-bottom: solid 2px rgb(255, 255, 255); + padding: 1.5rem 0.5rem; + text-align: center; +} + +tr { + min-height: 20rem; + background-color: rgb(90, 103, 116); + padding: 1rem; +} + +tr:first-child th:first-child { + border-top-left-radius: 10px; +} + +tr:first-child th:last-child { + border-top-right-radius: 10px; +} + +tr:last-child td:first-child { + border-bottom-left-radius: 10px; +} + +tr:last-child td:last-child { + border-bottom-right-radius: 10px; +} + +@media (max-width: 1699px) { + table { + width: 100%; + margin-bottom: 0.75rem; + table-layout: fixed; + } + + table:hover td:nth-child(even) { + cursor: pointer; + background-color: rgb(235, 240, 237); + } + + td { + position: relative; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + padding: 0.75rem; + } + + .table-rwd { + border: solid 1px rgb(223, 218, 218); + overflow: hidden; + } + + .table-rwd td { + border-bottom: solid 2px rgb(253, 253, 253); + } + + .table-rwd-heading { + background-color: rgb(240, 235, 235); + border-style: none; + padding: 0.5rem; + width: 140px; + font-weight: 700; + font-size: 0.9rem; + border-right: solid 1px rgb(223, 218, 218); + } + + .table-rwd:first-child { + border-top-left-radius: 10px; + } + + .table-rwd:first-child { + border-top-right-radius: 10px; + } + + .table-rwd:last-child { + border-bottom-left-radius: 10px; + } + + .table-rwd:last-child { + border-bottom-right-radius: 10px; + } + + .table-rwd tr:last-child > td:nth-last-of-type(2) { + border-bottom: 0; + } + + .table-rwd tr:last-child > td:last-of-type { + border-bottom: 0; + } + + .table-rwd td:last-child { + text-align: left; + } + + tr:first-child th:first-child { + border-top-left-radius: 0; + } + + tr:first-child th:last-child { + border-top-right-radius: 0; + } + + tr:last-child td:first-child { + border-bottom-left-radius: 0; + } + + tr:last-child td:last-child { + border-bottom-right-radius: 0; + } +} diff --git a/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/CustomTable.tsx b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/CustomTable.tsx new file mode 100644 index 0000000000..e549ff40ea --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/CustomTable.tsx @@ -0,0 +1,108 @@ +import { Component, createSignal, createEffect, onCleanup } from "solid-js"; +import { TableProps, TableProperty } from "../../../schema/supabase-types"; + +import EmptyTablePlaceholder from "./EmptyTablePlaceholder/EmptyTablePlaceholder"; +// @ts-expect-error +import styles from "./CustomTable.module.css"; + + +const CustomTable: Component<{ cols: TableProps; data: any[] }> = (props) => { + + const [viewport, setViewport] = createSignal(""); + +createEffect(() => { + const screenResized = () => + setViewport(window.innerWidth <= 1699 ? "small" : "wide"); + screenResized(); + window.addEventListener("resize", screenResized, true); + onCleanup(() => { + window.removeEventListener("resize", screenResized, true); + }); +}); + + + + const getObjPropVal = (objProp: string[], row: any) => { + if (objProp.length === 1) return row[objProp[0]]; + else { + return objProp.map((prop) => ( + <> + {row[prop]} +

+ + )); + } + }; + + const handleRowClick = (row: any) => { + props.cols.onClick.action(row[props.cols.onClick.prop]); + }; + + return ( + <> + {props.data.length === 0 ? ( + + ) : ( + <> + {viewport() === "wide" && ( + + + + {props.cols.schema.map((col) => ( + + ))} + + + + {props.data.map((row) => { + return ( + + {props.cols.schema.map((col: TableProperty) => ( + + ))} + + ); + })} + +
{col.display}
handleRowClick(row)}> + {getObjPropVal(col.objProp, row)} +
+ )} + + {viewport() === "small" && ( + <> + {props.data.map((row) => { + return ( + handleRowClick(row)} + > + + {props.cols.schema.map((heading, idx) => { + return ( + + + + + ); + })} + +
+ {heading.display} + + {getObjPropVal( + props.cols.schema[idx].objProp, + row, + )} +
+ ); + })} + + )} + + )} + + ); +}; + +export default CustomTable; diff --git a/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/EmptyTablePlaceholder/EmptyTablePlaceholder.module.css b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/EmptyTablePlaceholder/EmptyTablePlaceholder.module.css new file mode 100644 index 0000000000..8d9bbfa661 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/EmptyTablePlaceholder/EmptyTablePlaceholder.module.css @@ -0,0 +1,7 @@ +.placeholder-container { + display: flex; + justify-content: center; + font-size: 2rem; + font-weight: bold; + color: rgb(9, 75, 9); +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/EmptyTablePlaceholder/EmptyTablePlaceholder.tsx b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/EmptyTablePlaceholder/EmptyTablePlaceholder.tsx new file mode 100644 index 0000000000..0a70ca9c27 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/CustomTable/EmptyTablePlaceholder/EmptyTablePlaceholder.tsx @@ -0,0 +1,10 @@ +import { Component } from "solid-js"; + +// @ts-ignore +import styles from "./EmptyTablePlaceholder.module.css"; + +const EmptyTablePlaceholder: Component = () => { + return
No data available
; +}; + +export default EmptyTablePlaceholder; diff --git a/packages/cacti-cmd-gui-app/src/components/UI/Menu/Menu.module.css b/packages/cacti-cmd-gui-app/src/components/UI/Menu/Menu.module.css new file mode 100644 index 0000000000..ab9a7591e0 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/Menu/Menu.module.css @@ -0,0 +1,22 @@ +.nav-bar{ + width: 100%; + display: flex; + justify-content: flex-start; + padding-left: 8rem; + height: 4rem; + background-color: rgb(180, 218, 167); + align-items: center; + gap: 2rem; +} + +.navigation { + height: 100%; + display: flex; + gap: 1rem; +} + +@media (max-width: 1699px) { + .nav-bar{ + padding-left: 1rem; + } +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/components/UI/Menu/Menu.tsx b/packages/cacti-cmd-gui-app/src/components/UI/Menu/Menu.tsx new file mode 100644 index 0000000000..970c4dc367 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/Menu/Menu.tsx @@ -0,0 +1,60 @@ +import Button from "../Button/Button"; +import { createSignal, createEffect, For, Show } from "solid-js"; +import { useNavigate, useLocation } from "@solidjs/router"; +// @ts-expect-error +import styles from "./Menu.module.css"; +import Select from "../Select/Select"; + +const pathsEth = [ + { title: "DASHBOARD", path: "/eth" }, + { title: "ERC20", path: "/eth/accounts/erc20" }, + { title: "NFT ERC721", path: "/eth/accounts/erc721" }, +]; +const pathsFabric = [{ title: "DASHBOARD", path: "/fabric" }]; + +const Menu = () => { + const navigate = useNavigate(); + const location = useLocation(); + const [activeLedger, setActiveLedger] = createSignal(""); + + const handleSelect = (selectedValue: string) => { + setActiveLedger(selectedValue); + navigate(`/${activeLedger()}`) + }; + + createEffect(()=>{ + if(activeLedger().length > 0) return + const currentPath = location.pathname + const ledgers = ['eth', 'fabric'] + + ledgers.forEach(ledger => { + if(currentPath.includes(ledger)){ + setActiveLedger(ledger) + } + }) + }) + + return ( +
+ handleInput(e)} + onPaste={(e) => handleInput(e)} + /> + +
+ ); +}; + +export default Search; diff --git a/packages/cacti-cmd-gui-app/src/components/UI/Select/Select.module.css b/packages/cacti-cmd-gui-app/src/components/UI/Select/Select.module.css new file mode 100644 index 0000000000..19e2f837af --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/Select/Select.module.css @@ -0,0 +1,73 @@ +.select-wrapper { + height: 2.2rem; + width: 13rem; +} + +.select { + width: 100%; + height: 100%; + display: flex; +} + +.select-icon { + position: relative; + right: 30px; + height: 10px; + width: 10px; + transform: translateY(-50%) rotate(45deg); + pointer-events: none; +} + +.select-icon-up { + top: 60%; + border-top: 2px solid rgb(32, 133, 77); + border-left: 2px solid rgb(32, 133, 77); +} + +.select-icon-down { + top: 40%; + border-bottom: 2px solid rgb(32, 133, 77); + border-right: 2px solid rgb(32, 133, 77); +} + +.select-input { + width: 100%; + height: 100%; + font-family: "Roboto"; + font-size: 1rem; + color: rgb(102, 117, 106); + background-color: rgb(248, 248, 250); + border: 1px solid rgb(32, 133, 77); + border-radius: 10px; + cursor: pointer; + text-align: start; +} + +.select-input:hover { + background-color: rgb(235, 240, 237); +} + +.options-container { + z-index: 1000; + width: 100%; + position: relative; + overflow-y: auto; + border: 1px solid rgb(32, 133, 77); + border-radius: 10px; + color: rgb(102, 117, 106); + background: white; + cursor: pointer; +} + +.option { + padding: 5px; +} + +.selected-option { + padding: 5px; + background-color: rgb(235, 240, 237); +} + +.option:hover { + background-color: rgb(235, 240, 237); +} diff --git a/packages/cacti-cmd-gui-app/src/components/UI/Select/Select.tsx b/packages/cacti-cmd-gui-app/src/components/UI/Select/Select.tsx new file mode 100644 index 0000000000..b2ca834bcf --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/components/UI/Select/Select.tsx @@ -0,0 +1,78 @@ +import { For, Component, createSignal } from "solid-js"; +import { useLocation } from "@solidjs/router"; + +// @ts-ignore +import styles from "./Select.module.css"; + +const options = [ + { value: "", display: "Select the ledger" }, + { value: "eth", display: "Ethereum" }, + { value: "fabric", display: "Fabric" }, +]; + +const Select: Component<{ + onSelect: (selectedOption: string) => void; + value: string; +}> = (props) => { + const selectStartLedgerByUrl = (path: string) => + options.find((option) => option.value === path); + + const [dropdownVisible, setDropdownVisible] = createSignal(false); + const startLocation = useLocation().pathname.split("/")[1]; + const [selectedOption, setSelectedOption] = createSignal( + selectStartLedgerByUrl(startLocation) || options[0], + ); + + const selectOption = (item: any) => { + setSelectedOption(item); + props.onSelect(item.value); + setDropdownVisible(false); + }; + + return ( +
+
setDropdownVisible(!dropdownVisible())} + > + setDropdownVisible(true)} + onBlur={() => setDropdownVisible(false)} + class={styles["select-input"]} + /> +
+
+ {dropdownVisible() ? ( +
+ + {(item) => ( +
selectOption(item)} + > + {item.display} +
+ )} +
+
+ ) : null} +
+ ); +}; + +export default Select; diff --git a/packages/cacti-cmd-gui-app/src/index.css b/packages/cacti-cmd-gui-app/src/index.css new file mode 100644 index 0000000000..46ecb129e1 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/index.css @@ -0,0 +1,16 @@ +@import url('https://fonts.googleapis.com/css?family=Roboto'); +*{ + box-sizing: border-box; + padding: 0; + margin: 0; +} + +body { + margin: 0; + font-family: 'Roboto'; +} + +h1,h2,h3,h4,h5 { + color:rgb(36, 143, 36); +} + diff --git a/packages/cacti-cmd-gui-app/src/index.tsx b/packages/cacti-cmd-gui-app/src/index.tsx new file mode 100644 index 0000000000..388b856121 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/index.tsx @@ -0,0 +1,14 @@ +/* @refresh reload */ +import { render } from "solid-js/web"; +import { Router } from "@solidjs/router"; +import "./index.css"; +import App from "./App"; + +render( + () => ( + + + + ), + document.getElementById("root")!, +); diff --git a/packages/cacti-cmd-gui-app/src/logo.svg b/packages/cacti-cmd-gui-app/src/logo.svg new file mode 100644 index 0000000000..025aa303c5 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/Pages.tsx b/packages/cacti-cmd-gui-app/src/pages/Pages.tsx new file mode 100644 index 0000000000..06ae9707d3 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/Pages.tsx @@ -0,0 +1,65 @@ +import { Routes, Route } from "@solidjs/router"; +import TransactionDetails from "./eth/Details/TransactionDetails"; +import BlockDetails from "./eth/Details/BlockDetails"; +import TokenTransactionDetails from "./eth/Details/TokenTransactionDetails"; +import TokenDetails from "./eth/Details/TokenDetails"; +import Dashboard from "./eth/Dashboard/Dashboard"; +import Transactions from "./eth/Transactions/Transactions"; +import Blocks from "./eth/Blocks/Blocks"; +import Accounts from "./eth/Accounts/Accounts"; +import ERC20 from "./eth/ERC20/ERC20"; +import ERC721 from "./eth/ERC721/ERC721"; +import SingleTokenHistory from "./eth/SingleTokenHistory/SingleTokenHistory"; + +import TransactionsFabric from "./fabric/TransactionsFabric/TransactionsFabric"; +import BlocksFabric from "./fabric/BlocksFabric/BlocksFabric"; +import DashFabric from "./fabric/DashFabric/DashFabric"; +import FabricBlock from "./fabric/FabricBlock/FabricBlock"; +import FabricTransaction from "./fabric/FabricTransaction/FabricTransaction"; + +import Home from "./shared/Home/Home"; + +const Pages = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; +export default Pages; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Accounts/Accounts.module.css b/packages/cacti-cmd-gui-app/src/pages/eth/Accounts/Accounts.module.css new file mode 100644 index 0000000000..43c52de4a5 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Accounts/Accounts.module.css @@ -0,0 +1,6 @@ +.accounts-wrapper{ + width:100%; + display:grid; + grid-template-columns: repeat(auto-fit, 35rem); + gap: 1rem; +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Accounts/Accounts.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/Accounts/Accounts.tsx new file mode 100644 index 0000000000..2964bd0f3c --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Accounts/Accounts.tsx @@ -0,0 +1,63 @@ +import { createSignal, createEffect } from "solid-js"; +import { useParams, useNavigate } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; + +const Accounts = () => { + const params = useParams(); + const navigate = useNavigate(); + const [accounts, setAccounts] = createSignal<{ address: string }[]>([]); + const [searchKey, setSearchKey] = createSignal(""); + + const tableProps = { + onClick: { + action: (param: string) => navigate(`/eth/${params.standard}/${param}`), + prop: "address", + }, + schema: [ + { + display: "Account address", + objProp: ["address"], + }, + ], + }; + + const fetchAccounts = async () => { + try { + const { data, error } = await supabase + .from(`token_${params.standard.toLowerCase()}`) + .select("account_address"); + if (data) { + const objData = [ + ...new Set(data.map((el) => el.account_address)), + ].map((el) => ({ address: el })); + setAccounts(objData); + } + if (error) { + console.error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchAccounts(); + }, []); + + return ( +
+ setSearchKey(e)} + > +
+ ); +}; + +export default Accounts; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Blocks/Blocks.module.css b/packages/cacti-cmd-gui-app/src/pages/eth/Blocks/Blocks.module.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Blocks/Blocks.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/Blocks/Blocks.tsx new file mode 100644 index 0000000000..f3b9ff15dc --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Blocks/Blocks.tsx @@ -0,0 +1,60 @@ +import { createSignal, createEffect } from "solid-js"; +import { useNavigate } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import { Block } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./Blocks.module.css"; + +type ObjectKey = keyof typeof styles; + +const Blocks = () => { + const navigate = useNavigate(); + const [block, setBlock] = createSignal([]); + + const blocksTableProps = { + onClick: { + action: (param: string) => navigate(`/eth/block-details/${param}`), + prop: "number", + }, + schema: [ + { display: "created at", objProp: ["created_at"] }, + { display: "block number", objProp: ["number"] }, + { display: "hash", objProp: ["hash"] }, + ], + }; + + const fetchBlock = async () => { + try { + const { data, error } = await supabase.from("block").select("*"); + if (data) { + console.log(JSON.stringify(data)) + setBlock(data); + } + if (error) { + console.error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchBlock(); + }, []); + + return ( +
+ +
+ ); +}; + +export default Blocks; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Dashboard/Dashboard.module.css b/packages/cacti-cmd-gui-app/src/pages/eth/Dashboard/Dashboard.module.css new file mode 100644 index 0000000000..f2443fc562 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Dashboard/Dashboard.module.css @@ -0,0 +1,10 @@ +.dashboard-wrapper { + display: flex; + gap: 1rem; +} + +@media (max-width: 1699px) { + .dashboard-wrapper { + flex-direction: column; + } +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Dashboard/Dashboard.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/Dashboard/Dashboard.tsx new file mode 100644 index 0000000000..47fd54a779 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Dashboard/Dashboard.tsx @@ -0,0 +1,100 @@ +import { createSignal, createEffect, Show } from "solid-js"; +import { useNavigate, useParams } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import { Transaction } from "../../../schema/supabase-types"; +import { Block } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./Dashboard.module.css"; + +const Dashboard = () => { + const navigate = useNavigate(); + const [transaction, setTransaction] = createSignal([]); + const [block, setBlock] = createSignal([]); + + const txnTableProps = { + onClick: { + action: (param: string) => { + navigate(`/eth/txn-details/${param}`); + }, + prop: "id", + }, + schema: [ + { display: "transaction id", objProp: ["id"] }, + { display: "sender/recipient", objProp: ["from", "to"] }, + { display: "token value", objProp: ["eth_value"] }, + ], + }; + const blocksTableProps = { + onClick: { + action: (param: string) => { + navigate(`/eth/block-details/${param}`); + }, + prop: "number", + }, + schema: [ + { display: "created at", objProp: ["created_at"] }, + { display: "block number", objProp: ["number"] }, + { display: "hash", objProp: ["hash"] }, + ], + }; + + const fetchTransactions = async () => { + try { + const { data, error } = await supabase.from("transaction").select("*"); + if (data) { + setTransaction(data); + } + if (error) { + console.error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + const fetchBlock = async () => { + try { + const { data, error } = await supabase.from("block").select("*"); + if (data) { + setBlock(data); + } + if (error) { + console.error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchBlock(); + }, []); + + createEffect(async () => { + await fetchTransactions(); + }, []); + + return ( +
+
+ + +
+
+ ); +}; + +export default Dashboard; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Details/BlockDetails.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/Details/BlockDetails.tsx new file mode 100644 index 0000000000..4b53ad9eb6 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Details/BlockDetails.tsx @@ -0,0 +1,59 @@ +import { createEffect, createSignal, Show } from "solid-js"; +import { useParams } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import { Block } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./Details.module.css"; + +const blockDetails = () => { + const [details, setDetails] = createSignal({}); + const params = useParams(); + + createEffect(async () => { + try { + const { data, error } = await supabase + .from("block") + .select("*") + .match({ number: params.number }); + if (data?.[0]) { + setDetails(data[0]); + } else { + throw new Error("Failed to load block details"); + } + } catch (error:any) { + console.error(error.message); + } + }, []); + + return ( +
+
+ Failed to load details
}> +

Block Details

+

+ Address: {details().number}{" "} +

+

+ {" "} + Created at: + {details().created_at} +

+

+ Hash: + {details().hash} +

+

+ Number of transaction: + {details().number_of_tx} +

+

+ Sync at: + {details().sync_at} +

+ +
+
+ ); +}; + +export default blockDetails; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Details/Details.module.css b/packages/cacti-cmd-gui-app/src/pages/eth/Details/Details.module.css new file mode 100644 index 0000000000..899ff99432 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Details/Details.module.css @@ -0,0 +1,32 @@ +h1{ + padding: 0; + margin: 0; + margin-bottom: .5rem; +} +.details { + display: flex; + gap: .75rem; +} +.details-card{ + display: flex; + flex-direction: column; + gap: 15px; + border: 1px solid rgb(230, 224, 224); + border-radius: 10px; + padding: 1.5rem 2rem; + width:45%; +} +span { + display: inline-block; + font-size: 1.1rem; +} + +@media (max-width: 1699px) { + .details{ + flex-direction: column; + } + + .details-card { + width: 100%; + } +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Details/TokenDetails.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/Details/TokenDetails.tsx new file mode 100644 index 0000000000..14a6b4b814 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Details/TokenDetails.tsx @@ -0,0 +1,59 @@ +import { createEffect, createSignal } from "solid-js"; +import { useParams } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import { STANDARDS } from "../../../schema/token-standards"; +import { TokenMetadata20, TokenMetadata721 } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./Details.module.css"; + +const TokenDetails = () => { + const [tokenData, setTokenData] = createSignal< + TokenMetadata20 | TokenMetadata721 | any + >(); + + const params = useParams(); + + createEffect(async () => { + try { + const { data, error } = await supabase + .from(`token_metadata_${params.standard.toLowerCase()}`) + .select("*") + .match({ address: params.address }); + if (data?.[0]) { + setTokenData(data[0]); + } else { + throw new Error("Failed to load token details"); + } + } catch (error:any) { + console.error(error.message); + } + }, []); + + return ( +
+
+

Token Details

+

+ Adress: {tokenData()?.address}{" "} +

+

+ Created at: + {tokenData()?.created_at} +

+

+ Name: + {tokenData()?.name} +

+

+ Symbol: + {tokenData()?.symbol} +

+ {params.standard === STANDARDS.erc20 && ( +

total_supply : {tokenData()?.total_supply}

+ )} +
+
+ ); +}; + +export default TokenDetails; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Details/TokenTransactionDetails.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/Details/TokenTransactionDetails.tsx new file mode 100644 index 0000000000..69af02c901 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Details/TokenTransactionDetails.tsx @@ -0,0 +1,62 @@ +import { createEffect, createSignal } from "solid-js"; +import { useParams } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import { STANDARDS } from "../../../schema/token-standards"; +import { ERC20Txn, ERC721Txn } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./Details.module.css"; + +const TokenTransactionDetails = () => { + const [txnData, setTxnData] = createSignal({}); + const params = useParams(); + + createEffect(async () => { + try { + const { data, error } = await supabase + .from(`token_${params.standard.toLowerCase()}`) + .select("*") + .match({ account_address: params.address }); + if (data?.[0]) { + setTxnData(data[0]); + } else { + throw new Error("Failed to load transaction details"); + } + } catch (error:any) { + console.error(error.message); + } + }, []); + + return ( +
+
+

Details of Transaction

+

+ {" "} + Address: + {txnData()?.account_address}{" "} +

+

+ {" "} + Created_at: + {txnData()?.token_address} +

+ {params.standard === STANDARDS.erc20 && ( +

+ {" "} + Balance: + {txnData()?.balance} +

+ )} + {params.standard === STANDARDS.erc721 && ( +

+ {" "} + Uri: + {txnData()?.uri} +

+ )} +
+
+ ); +}; + +export default TokenTransactionDetails; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Details/TransactionDetails.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/Details/TransactionDetails.tsx new file mode 100644 index 0000000000..b0187c3219 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Details/TransactionDetails.tsx @@ -0,0 +1,100 @@ +import { createEffect, createSignal } from "solid-js"; +import { useParams, useNavigate } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import { Transaction, TokenTransfer } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./Details.module.css"; + +const TransactionsDetails = () => { + const [details, setDetails] = createSignal({}); + const [transfers, setTransfers] = createSignal([]); + const params = useParams(); + + const detailsTableProps = { + onClick: { + action: () => {}, + prop: "id", + }, + schema: [ + { display: "transfer id", objProp: ["id"] }, + { display: "sender/recipient", objProp: ["sender", "recipient"] }, + { display: "value", objProp: ["value"] }, + ], + }; + + const fetchDetails = async () => { + try { + const { data, error } = await supabase + .from("transaction") + .select("*") + .match({ id: params.id }); + if (data?.[0]) { + setDetails(data[0]); + } else { + throw new Error("Failed to load transaction details"); + } + } catch (error:any) { + console.error(error.message); + } + }; + + const fetchTransfers = async () => { + try { + const { data, error } = await supabase + .from("token_transfer") + .select("*") + .match({ transaction_id: params.id }); + if (data) { + setTransfers(data); + } else { + throw new Error("Failed to load transfers"); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchDetails(); + await fetchTransfers(); + }, []); + + return ( +
+
+

Details of Transaction

+

+ {" "} + Hash: {details().hash}{" "} +

+

+ Block: + {details().block_number} +

+

+ From: + {details().from} +

+

+ To: + {details().to}{" "} +

+

+ {" "} + Value:   {details().eth_value} +

+
+ +
+ ); +}; + +export default TransactionsDetails; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/ERC20/ERC20.module.css b/packages/cacti-cmd-gui-app/src/pages/eth/ERC20/ERC20.module.css new file mode 100644 index 0000000000..92eaa56f56 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/ERC20/ERC20.module.css @@ -0,0 +1,19 @@ +.erc-content { + display: flex; + gap:2rem; +} + +.erc-wrap{ + width:100%; + display: flex; + gap:1rem; + flex-direction: column; + +} + + +@media (max-width: 1699px) { + .erc-content{ + flex-direction: column; + } +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/ERC20/ERC20.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/ERC20/ERC20.tsx new file mode 100644 index 0000000000..d32c5956ae --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/ERC20/ERC20.tsx @@ -0,0 +1,75 @@ +import { useParams, useNavigate } from "@solidjs/router"; +import { createSignal, createEffect } from "solid-js"; +import TokenAccount from "../../../components/TokenHeader/TokenAccount"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import Chart from "../../../components/Chart/Chart"; +import { ERC20Txn } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./ERC20.module.css"; + +const ERC20 = () => { + const params = useParams(); + const navigate = useNavigate(); + const [token_erc20, setToken_erc20] = createSignal([]); + + const ercTableProps = { + onClick: { + action: (token_address: string) => + navigate(`/eth/erc20/trend/${params.account}/${token_address}`), + prop: "token_address", + }, + schema: [ + { + display: "token address", + objProp: ["token_address"], + }, + { + display: "balance", + objProp: ["balance"], + }, + ], + }; + + const fetchERC20 = async () => { + try { + const { data, error } = await supabase + .from("token_erc20") + .select() + .eq("account_address", params.account); + if (data) { + setToken_erc20(data); + } + if (error) { + throw new Error(error.message); + } + } catch (error: any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchERC20(); + }, []); + + return ( +
+
+ + +
+
+ + +
+
+ ); +}; + +export default ERC20; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/ERC721/ERC721.module.css b/packages/cacti-cmd-gui-app/src/pages/eth/ERC721/ERC721.module.css new file mode 100644 index 0000000000..fb8d75ce96 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/ERC721/ERC721.module.css @@ -0,0 +1,18 @@ +.erc-content { + display: flex; + gap:2rem; +} + +.erc-wrap{ + display: flex; + gap:1rem; + flex-direction: column; + align-items: center; +} + +@media (max-width: 1699px) { + + .erc-content{ + flex-direction: column; + } +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/ERC721/ERC721.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/ERC721/ERC721.tsx new file mode 100644 index 0000000000..29caf8be4d --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/ERC721/ERC721.tsx @@ -0,0 +1,117 @@ +import { useParams, useNavigate } from "@solidjs/router"; +import { createSignal, createEffect, Show } from "solid-js"; +import TokenAccount from "../../../components/TokenHeader/TokenAccount"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import { ERC721Txn } from "../../../schema/supabase-types"; +import { TokenMetadata721 } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./ERC721.module.css"; + +const ERC721 = () => { + const params = useParams(); + const navigate = useNavigate(); + const [token_erc721, setToken_erc721] = createSignal([]); + const [tokenMetadata, setTokenMetadata] = createSignal( + [], + ); + + const ercTableProps = { + onClick: { + action: (param: string) => navigate(`/eth/token-details/erc721/${param}`), + prop: "token_address", + }, + schema: [ + { + display: "symbol", + objProp: ["symbol"], + }, + { + display: "URI", + objProp: ["uri"], + }, + ], + }; + const metaProps = { + onClick: { + action: () => {}, + prop: "", + }, + schema: [ + { + display: "created at", + objProp: ["created_at"], + }, + { + display: "sender/recipient", + objProp: ["sender", "recipient"], + }, + { + display: "token address", + objProp: ["token_address"], + }, + ], + }; + + const fetchERC721 = async () => { + try { + const { data, error } = await supabase + .from("erc721_txn_meta_view") + .select() + .eq("account_address", params.account); + if (data) { + setToken_erc721(data); + } + if (error) { + throw new Error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + const fetchMetadata = async () => { + try { + const { data, error } = await supabase + .from(`erc721_token_history_view`) + .select("*"); + if (data) { + setTokenMetadata(data); + } + if (error) { + console.error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchERC721(); + await fetchMetadata(); + }, []); + + return ( +
+ +
+ + +
+
+ ); +}; + +export default ERC721; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/SingleTokenHistory/SingleTokenHistory.module.css b/packages/cacti-cmd-gui-app/src/pages/eth/SingleTokenHistory/SingleTokenHistory.module.css new file mode 100644 index 0000000000..aa59d7de7e --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/SingleTokenHistory/SingleTokenHistory.module.css @@ -0,0 +1,12 @@ +.token-history { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.transactions { + display: flex; + flex-direction: column; + gap: 2rem; + margin-top: 2rem; +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/SingleTokenHistory/SingleTokenHistory.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/SingleTokenHistory/SingleTokenHistory.tsx new file mode 100644 index 0000000000..d1dfbaa54f --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/SingleTokenHistory/SingleTokenHistory.tsx @@ -0,0 +1,119 @@ +import { createSignal, createEffect, Show } from "solid-js"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import LineChart from "../../../components/Chart/LineChart"; +import TokenHeader from "../../../components/TokenHeader/TokenHeader"; +import { useNavigate, useParams } from "@solidjs/router"; +import { TokenHistoryItem20 } from "../../../schema/supabase-types"; +import { balanceDate } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./SingleTokenHistory.module.css"; +import EmptyTablePlaceholder from "../../../components/UI/CustomTable/EmptyTablePlaceholder/EmptyTablePlaceholder"; + +const SingleTokenHistory = () => { + type ObjectKey = keyof typeof styles; + const [transactions, setTransactions] = createSignal( + [], + ); + const [balanceHistory, setBalanceHistory] = createSignal([]); + const navigate = useNavigate(); + const params = useParams(); + + const tokenTableProps = { + onClick: { + action: (param: string) => navigate(`/view/${param}`), + prop: "id", + }, + schema: [ + { + display: "created at", + objProp: ["created_at"], + }, + { + display: "transaction hash", + objProp: ["transaction_hash"], + }, + { + display: "sender/recipient", + objProp: ["sender", "recipient"], + }, + { + display: "token address", + objProp: ["token_address"], + }, + { + display: "token value", + objProp: ["value"], + }, + ], + }; + + const calcTokenBalance = (txnData: TokenHistoryItem20[]) => { + let balance = 0; + const balances = txnData.map((txn) => { + let txn_value = txn.value || 0; + let account = params.account; + if (txn.recipient !== account) { + txn_value *= -1; + } + balance += txn_value; + return { + created_at: txn.created_at + "Z", + balance: balance, + }; + }); + return balances; + }; + + const fetchTransactions = async () => { + try { + const { data, error } = await supabase + .from("erc20_token_history_view") + .select("*") + .match({ token_address: params.address }).or(`sender.eq.${params.account}, recipient.eq.${params.account}`); + if (data) { + setTransactions(data); + setBalanceHistory(calcTokenBalance(data)); + } + if (error) { + console.error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchTransactions(); + }, []); + + return ( +
+ +
+ 0 } + fallback={}> + + + + + +
+
+ ); +}; + +export default SingleTokenHistory; diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Transactions/Transactions.module.css b/packages/cacti-cmd-gui-app/src/pages/eth/Transactions/Transactions.module.css new file mode 100644 index 0000000000..7ff183b0da --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Transactions/Transactions.module.css @@ -0,0 +1,12 @@ +.transactions{ + display: flex; + flex-direction: column; + +}.transactions-search { + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 2rem; + + +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/eth/Transactions/Transactions.tsx b/packages/cacti-cmd-gui-app/src/pages/eth/Transactions/Transactions.tsx new file mode 100644 index 0000000000..1679f054cc --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/eth/Transactions/Transactions.tsx @@ -0,0 +1,66 @@ +import { createSignal, createEffect, Show } from "solid-js"; +import { useNavigate, useParams } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import { Transaction } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./Transactions.module.css"; + +const Transactions = () => { + const navigate = useNavigate(); + const [transactions, setTransactions] = createSignal([]); + + const txnTableProps = { + onClick: { + action: (param: string) => navigate(`/eth/txn-details/${param}`), + prop: "id", + }, + schema: [ + { + display: "transaction id", + objProp: ["id"], + }, + { + display: "sender/recipient", + objProp: ["from", "to"], + }, + { + display: "token value", + objProp: ["eth_value"], + }, + ], + }; + + const fetchTransactions = async () => { + try { + const { data } = await supabase.from("transaction").select("*"); + if (data) { + console.log(JSON.stringify(data)) + setTransactions(data); + } else { + throw new Error("Failed to load transactions"); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchTransactions(); + }, []); + + return ( +
+ +
+ ); +}; + +export default Transactions; diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/BlocksFabric/BlocksFabric.module.css b/packages/cacti-cmd-gui-app/src/pages/fabric/BlocksFabric/BlocksFabric.module.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/BlocksFabric/BlocksFabric.tsx b/packages/cacti-cmd-gui-app/src/pages/fabric/BlocksFabric/BlocksFabric.tsx new file mode 100644 index 0000000000..d3c9b4b547 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/fabric/BlocksFabric/BlocksFabric.tsx @@ -0,0 +1,63 @@ +import { createSignal, createEffect } from "solid-js"; +import { useNavigate } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import { Block } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./BlocksFabric.module.css"; + +type ObjectKey = keyof typeof styles; + +const BlocksFabric = () => { + const navigate = useNavigate(); + const [block, setBlock] = createSignal([]); + + const blocksTableProps = { + onClick: { + action: (param: string) => { + navigate(`/fabric/block-details/${param}`); + }, + prop: "id", + }, + schema: [ + { display: "created at", objProp: ["created_at"] }, + { display: "block number", objProp: ["block_number"] }, + { display: "channel name", objProp: ["channel_id"] }, + { display: "hash", objProp: ["data_hash"] }, + { display: "transactions count", objProp: ["tx_count"] }, + ], + }; + + const fetchBlock = async () => { + try { + const { data, error } = await supabase.from("fabric_blocks").select("*"); + if (data) { + setBlock(data); + } + if (error) { + console.error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchBlock(); + }, []); + + return ( +
+ +
+ ); +}; + +export default BlocksFabric; diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/DashFabric/DashFabric.module.css b/packages/cacti-cmd-gui-app/src/pages/fabric/DashFabric/DashFabric.module.css new file mode 100644 index 0000000000..f75a30b767 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/fabric/DashFabric/DashFabric.module.css @@ -0,0 +1,11 @@ +.dashboard-wrapper { + display: flex; + gap: 1rem; + flex-direction: column; +} + +@media (max-width: 1699px) { + .dashboard-wrapper { + flex-direction: column; + } +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/DashFabric/DashFabric.tsx b/packages/cacti-cmd-gui-app/src/pages/fabric/DashFabric/DashFabric.tsx new file mode 100644 index 0000000000..1ebf142f39 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/fabric/DashFabric/DashFabric.tsx @@ -0,0 +1,102 @@ +import { createSignal, createEffect, Show } from "solid-js"; +import { useNavigate, useParams } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import { Transaction } from "../../../schema/supabase-types"; +import { Block } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./DashFabric.module.css"; + +const DashFabric = () => { + const navigate = useNavigate(); + const [transaction, setTransaction] = createSignal([]); + const [block, setBlock] = createSignal([]); + + const txnTableProps = { + onClick: { + action: (param: string) => { + navigate(`/fabric/txn-details/${param}`); + }, + prop: "id", + }, + schema: [ + { display: "created at", objProp: ["created_at"] }, + { display: "transaction id", objProp: ["transaction_id"] }, + { display: "channel name", objProp: ["channel_id"] }, + { display: "block id", objProp: ["block_id"] }, + { display: "status", objProp: ["status"] }, + ], + }; + + const blocksTableProps = { + onClick: { + action: (param: string) => { + navigate(`/fabric/block-details/${param}`); + }, + prop: "id", + }, + schema: [ + { display: "created at", objProp: ["created_at"] }, + { display: "block number", objProp: ["block_number"] }, + { display: "channel name", objProp: ["channel_id"] }, + { display: "hash", objProp: ["data_hash"] }, + { display: "transactions count", objProp: ["tx_count"] }, + ], + }; + + const fetchTransactions = async () => { + try { + const { data, error } = await supabase.from("fabric_transactions").select("*"); + if (data) { + setTransaction(data); + } + if (error) { + console.error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + const fetchBlock = async () => { + try { + const { data, error } = await supabase.from("fabric_blocks").select("*"); + if (data) { + setBlock(data); + } + if (error) { + console.error(error.message); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchBlock(); + await fetchTransactions(); + }, []); + + return ( +
+
+ + +
+
+ ); +} + +export default DashFabric \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/FabricBlock/FabricBlock.module.css b/packages/cacti-cmd-gui-app/src/pages/fabric/FabricBlock/FabricBlock.module.css new file mode 100644 index 0000000000..5bfa3e269e --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/fabric/FabricBlock/FabricBlock.module.css @@ -0,0 +1,8 @@ +.details-card { + display: flex; + flex-direction: column; + gap: 15px; + border: 1px solid rgb(230, 224, 224); + border-radius: 10px; + padding: 1.5rem 2rem; +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/FabricBlock/FabricBlock.tsx b/packages/cacti-cmd-gui-app/src/pages/fabric/FabricBlock/FabricBlock.tsx new file mode 100644 index 0000000000..5a5b56ce38 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/fabric/FabricBlock/FabricBlock.tsx @@ -0,0 +1,82 @@ +// @ts-expect-error +import styles from './FabricBlock.module.css' +import { createEffect, createSignal, Show } from 'solid-js'; +import { useParams } from '@solidjs/router'; +import { supabase } from '../../../supabase-client'; + +const FabricBlock = () => { + const [details, setDetails] = createSignal({}); + const params = useParams(); + + createEffect(async () => { + try { + const { data, error } = await supabase + .from("fabric_blocks") + .select("*") + .match({ id: params.id}); + if (data?.[0]) { + setDetails(data[0]); + } else { + throw new Error("Failed to load block details"); + } + } catch (error:any) { + console.error(error.message); + } + }, []); + + return ( +
+
+ Failed to load details
}> +

Block Details

+

+ ID: {details().id}{" "} +

+

+ {" "} + Block Number: + {details().block_number} +

+

+ Hash: + {details().data_hash} +

+

+ Tx Count: + {details().tx_count} +

+

+ Created at: + {details().created_at} +

+

+ {" "} + Previous Blockhash: + {details().prev_blockhash} +

+ +

+ {" "} + Channel name: + {details().channel_id} +

+ {/* +

+ {" "} + Blk Size: + {details().blksize} +

+

+ {" "} + Network name: + {details().network_name} +

*/ + } + +
+ + ); + }; +export default FabricBlock + + diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/FabricTransaction/FabricTransaction.module.css b/packages/cacti-cmd-gui-app/src/pages/fabric/FabricTransaction/FabricTransaction.module.css new file mode 100644 index 0000000000..15fab73ab7 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/fabric/FabricTransaction/FabricTransaction.module.css @@ -0,0 +1,22 @@ +.details-card { + display: flex; + flex-direction: column; + gap: 15px; + border: 1px solid rgb(230, 224, 224); + border-radius: 10px; + padding: 1.5rem 2rem; +} + +.details-bytes-wrap { + display: flex; + align-items: center; + gap: 1rem; +} +.details-bytes { + width: 30vw; + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + resize: horizontal; +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/FabricTransaction/FabricTransaction.tsx b/packages/cacti-cmd-gui-app/src/pages/fabric/FabricTransaction/FabricTransaction.tsx new file mode 100644 index 0000000000..832eb9bf0d --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/fabric/FabricTransaction/FabricTransaction.tsx @@ -0,0 +1,123 @@ +// @ts-expect-error +import styles from './FabricTransaction.module.css' +import { createEffect, createSignal, Show } from 'solid-js'; +import { AiOutlineCopy } from 'solid-icons/ai' +import toast, { Toaster } from 'solid-toast'; +import { useParams } from '@solidjs/router'; +import { supabase } from '../../../supabase-client'; +import Button from '../../../components/UI/Button/Button'; + +const notify = () => toast('Success! Creator ID Bytes was successfully copied to the clipboard.'); + +const FabricTransaction = () => { + const [details, setDetails] = createSignal({}); + const params = useParams(); + + createEffect(async () => { + try { + const { data, error } = await supabase + .from("fabric_transactions") + .select("*") + .match({ id: params.id}); + if (data?.[0]) { + console.log(data) + setDetails(data[0]); + } else { + throw new Error("Failed to load block details"); + } + } catch (error:any) { + console.error(error.message); + } + }, []); + + const copyIdToClipboard = () => { + navigator.clipboard.writeText(details().creator_id_bytes) + notify() + } + + return ( +
+
+ Failed to load details
}> +

Transaction Details

+

+ Created at: + {details().created_at} +

+

+ Block ID: {details().block_id}{" "} +

+

+ {" "} + Transaction ID: + {details().transaction_id} +

+

+ {" "} + Channel name: + {details().channel_id} +

+ +

+ {" "} + Status + {details().status} +

+ +

+ {" "} + Type + {details().type} +

+ {/* +

+ {" "} + Validation Code + {details().validation_code} +

+

+ {" "} + Network name: + {details().network_name} +

+ */} +

+ {" "} + Chaincode Name: + {details().chaincode_name} +

+

+ {" "} + Creator ID Bytes: + {details().creator_id_bytes} + +

+

+ {" "} + Creator nonce: + {details().creator_nonce} +

+

+ {" "} + Creator MSP ID: + {details().creator_msp_id} +

+

+ {" "} + Endorser MSP ID: + {details().endorser_msp_id} +

+

+ {" "} + Payload Proposal Hash: + {details().payload_proposal_hash} +

+ +
+ + + ); + }; +export default FabricTransaction + + diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/TransactionsFabric/TransactionsFabric.module.css b/packages/cacti-cmd-gui-app/src/pages/fabric/TransactionsFabric/TransactionsFabric.module.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/cacti-cmd-gui-app/src/pages/fabric/TransactionsFabric/TransactionsFabric.tsx b/packages/cacti-cmd-gui-app/src/pages/fabric/TransactionsFabric/TransactionsFabric.tsx new file mode 100644 index 0000000000..a8b7690db5 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/fabric/TransactionsFabric/TransactionsFabric.tsx @@ -0,0 +1,60 @@ +import { createSignal, createEffect, Show } from "solid-js"; +import { useNavigate, useParams } from "@solidjs/router"; +import { supabase } from "../../../supabase-client"; +import CardWrapper from "../../../components/CardWrapper/CardWrapper"; +import { Transaction } from "../../../schema/supabase-types"; +// @ts-expect-error +import styles from "./TransactionsFabric.module.css"; + +const TransactionsFabric = () => { + const navigate = useNavigate(); + const [transactions, setTransactions] = createSignal([]); + + const txnTableProps = { + onClick: { + action: (param: string) => { + navigate(`/fabric/txn-details/${param}`); + }, + prop:"id", + }, + schema: [ + { display: "created at", objProp: ["created_at"] }, + { display: "transaction id", objProp: ["transaction_id"] }, + { display: "channel name", objProp: ["channel_id"] }, + { display: "block id", objProp: ["block_id"] }, + { display: "status", objProp: ["status"] }, + ], + }; + + const fetchTransactions = async () => { + try { + const { data } = await supabase.from("fabric_transactions").select("*"); + if (data) { + setTransactions(data); + } else { + throw new Error("Failed to load transactions"); + } + } catch (error:any) { + console.error(error.message); + } + }; + + createEffect(async () => { + await fetchTransactions(); + }, []); + + return ( +
+ +
+ ); +}; + +export default TransactionsFabric; diff --git a/packages/cacti-cmd-gui-app/src/pages/shared/Home/Home.module.css b/packages/cacti-cmd-gui-app/src/pages/shared/Home/Home.module.css new file mode 100644 index 0000000000..0c0f242381 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/shared/Home/Home.module.css @@ -0,0 +1,16 @@ +.home { + font-weight: 700; + margin:5rem; + font-size: 3rem; + color:rgb(14, 87, 32); + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + gap:2rem; +} + +.home-icon { + font-size: 20rem; + color:rgb(194, 202, 194); +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/pages/shared/Home/Home.tsx b/packages/cacti-cmd-gui-app/src/pages/shared/Home/Home.tsx new file mode 100644 index 0000000000..faac0f5b52 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/pages/shared/Home/Home.tsx @@ -0,0 +1,12 @@ +// @ts-expect-error +import styles from "./Home.module.css" +import { TbCactus } from 'solid-icons/tb' +const Home = () => { + return ( +

Select ledger from the dropdown menu

+ +
+ ) +} + +export default Home \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/src/schema/supabase-types.ts b/packages/cacti-cmd-gui-app/src/schema/supabase-types.ts new file mode 100644 index 0000000000..45b608e68e --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/schema/supabase-types.ts @@ -0,0 +1,103 @@ +export interface ERC20Txn { + account_address: string; + token_address: string; + uri: string; + token_id: number; + id: string; + balance: number; + last_owner_change: string; +} + +export interface ERC721Txn { + account_address: string; + token_address: string; + uri: string; + token_id: number; + id: string; + last_owner_change: string; +} + +export interface TokenMetadata20 { + address: string; + name: string; + symbol: string; + total_supply: number; + created_at: string; +} + +export interface TokenMetadata721 { + address: string; + name: string; + symbol: string; + created_at: string; +} + +export interface Block { + number: number; + created_at: string; + hash: string; + number_of_tx: number; + sync_at: string; +} + +export interface TokenTransfer { + transaction_id: string; + sender: string; + recipient: string; + value: number; + id: string; +} + +export interface Transaction { + index: number; + hash: string; + block_number: number; + from: string; + to: string; + eth_value: number; + method_signature: string; + method_name: string; + id: string; +} + +export interface TokenHistoryItem { + transaction_hash: string | null; + token_address: string | null; + created_at: string | null; + sender: string | null; + recipient: string | null; +} + +export interface TokenHistoryItem721 extends TokenHistoryItem { + token_id: number | null; +} + +export interface TokenHistoryItem20 extends TokenHistoryItem { + value: number | null; +} + +export interface TokenTransactionMetadata721 { + account_address: string; + token_address: string; + uri: string; + symbol: string; +} + +export interface TableProperty { + display: string; + objProp: string[]; +} + +export interface TableRowClick { + action: (param: string) => void; + prop: string; +} +export interface TableProps { + onClick: TableRowClick; + schema: TableProperty[]; +} + +export interface balanceDate { + created_at: string; + balance: number; +} diff --git a/packages/cacti-cmd-gui-app/src/schema/token-standards.ts b/packages/cacti-cmd-gui-app/src/schema/token-standards.ts new file mode 100644 index 0000000000..65ad5a514c --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/schema/token-standards.ts @@ -0,0 +1,4 @@ +export const STANDARDS = { + erc20: "ERC20", + erc721: "ERC721", +}; diff --git a/packages/cacti-cmd-gui-app/src/supabase-client.tsx b/packages/cacti-cmd-gui-app/src/supabase-client.tsx new file mode 100644 index 0000000000..11dbd850c8 --- /dev/null +++ b/packages/cacti-cmd-gui-app/src/supabase-client.tsx @@ -0,0 +1,7 @@ +import { createClient } from "@supabase/supabase-js"; + +let supabaseUrl = "http://localhost:8000"; +let supabaseKey = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE"; + +export const supabase = createClient(supabaseUrl, supabaseKey); diff --git a/packages/cacti-cmd-gui-app/styles.d.ts b/packages/cacti-cmd-gui-app/styles.d.ts new file mode 100644 index 0000000000..f2d12bb56c --- /dev/null +++ b/packages/cacti-cmd-gui-app/styles.d.ts @@ -0,0 +1,4 @@ +declare module "*.module.css" { + const classes: { [key: string]: string }; + export default classes; +} diff --git a/packages/cacti-cmd-gui-app/tailwind.config.ts b/packages/cacti-cmd-gui-app/tailwind.config.ts new file mode 100644 index 0000000000..06602b374a --- /dev/null +++ b/packages/cacti-cmd-gui-app/tailwind.config.ts @@ -0,0 +1,11 @@ +module.exports = { + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + darkMode: false, // or 'media' or 'class' + theme: { + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [], +}; diff --git a/packages/cacti-cmd-gui-app/tsconfig.json b/packages/cacti-cmd-gui-app/tsconfig.json new file mode 100644 index 0000000000..d8f03f3180 --- /dev/null +++ b/packages/cacti-cmd-gui-app/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [ + "./src" + ], + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "solid-js", + "plugins": [ + { + "name": "typescript-plugin-css-modules" + } + ], + "outDir": "./dist/out-tsc", + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cactus-cmd-gui-app.tsbuildinfo" + }, + +} \ No newline at end of file diff --git a/packages/cacti-cmd-gui-app/vite.config.ts b/packages/cacti-cmd-gui-app/vite.config.ts new file mode 100644 index 0000000000..279d6a4fca --- /dev/null +++ b/packages/cacti-cmd-gui-app/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vite"; +import solidPlugin from "vite-plugin-solid"; + +export default defineConfig({ + plugins: [solidPlugin()], + server: { + port: 3001, + }, + build: { + target: "esnext", + outDir: "./dist/lib", + }, +}); diff --git a/tsconfig.json b/tsconfig.json index e50f5124de..09266812d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,11 +5,14 @@ "path": "./packages/cactus-api-client/tsconfig.json" }, { - "path": "./packages/cactus-cmd-api-server/tsconfig.json" + "path": "./packages/cacti-cmd-api-server/tsconfig.json" }, { "path": "./packages/cactus-cmd-socketio-server/tsconfig.json" }, + { + "path": "./packages/cactus-cmd-gui-app/tsconfig.json" + }, { "path": "./packages/cactus-common/tsconfig.json" },