From 4141c176e41fcea409a9d89c62dca2c0fd5ec567 Mon Sep 17 00:00:00 2001 From: Ricki Moore Date: Thu, 11 Jul 2024 17:05:06 +0200 Subject: [PATCH 01/37] v2.0.0 (#263) Co-authored-by: antondlr Co-authored-by: Jimmy Chen Co-authored-by: Age Manning --- .dockerignore | 2 + .env.example | 14 +- .eslintignore | 1 - .eslintrc.json | 60 +- .github/ISSUE_TEMPLATE/bug_report.md | 18 +- .github/ISSUE_TEMPLATE/feature_request.md | 1 - .github/workflows/docker.yml | 205 +- .github/workflows/release.yml | 199 +- .github/workflows/ui-tests.yml | 52 - .gitignore | 6 + .husky/pre-commit | 19 +- .prettierrc | 2 +- .storybook/main.js | 20 - .storybook/preview.js | 35 - Dockerfile | 43 +- Dockerfile.dev | 12 - Dockerfile.release | 3 - Makefile | 23 - README.md | 129 +- app/Main.tsx | 163 + app/Providers.tsx | 28 + app/Wrapper.tsx | 16 + app/api/authenticate/route.ts | 26 + app/api/beacon-heartbeat/route.ts | 20 + app/api/beacon-version/route.ts | 13 + app/api/beacon.ts | 26 + app/api/bls-execution/route.ts | 21 + app/api/config.ts | 11 + app/api/dismiss-log/[index]/route.ts | 25 + app/api/execute-validator-exit/route.ts | 15 + app/api/lighthouse-version/route.ts | 13 + app/api/logs.ts | 5 + app/api/node-health/route.ts | 13 + app/api/node-sync/route.ts | 13 + app/api/peer-data/route.ts | 13 + app/api/priority-logs/route.ts | 13 + app/api/sign-validator-exit/route.ts | 21 + app/api/update-graffiti/route.ts | 15 + app/api/validator-cache/route.ts | 13 + app/api/validator-duties/route.ts | 13 + app/api/validator-graffiti/[index]/route.ts | 25 + app/api/validator-heartbeat/route.ts | 20 + app/api/validator-inclusion/route.ts | 13 + .../validator-metrics/[[...index]]/route.ts | 21 + app/api/validator-network/route.ts | 13 + app/api/validator-states/route.ts | 13 + app/api/validator.ts | 22 + app/dashboard/Main.tsx | 264 + app/dashboard/Wrapper.tsx | 16 + app/dashboard/logs/Content.tsx | 13 + app/dashboard/logs/Main.tsx | 88 + app/dashboard/logs/Wrapper.tsx | 17 + app/dashboard/logs/page.tsx | 21 + app/dashboard/page.tsx | 52 + app/dashboard/settings/Main.tsx | 183 + app/dashboard/settings/Wrapper.tsx | 16 + app/dashboard/settings/page.tsx | 30 + app/dashboard/validators/Main.tsx | 245 + app/dashboard/validators/Wrapper.tsx | 16 + app/dashboard/validators/page.tsx | 39 + app/error/Main.tsx | 22 + app/error/page.tsx | 18 + app/icon.png | Bin 0 -> 157922 bytes app/layout.tsx | 120 + app/page.tsx | 8 + app/setup/health-check/Main.tsx | 65 + app/setup/health-check/Wrapper.tsx | 16 + app/setup/health-check/page.tsx | 19 + app/setup/node-sync/Main.tsx | 66 + app/setup/node-sync/Wrapper.tsx | 16 + app/setup/node-sync/page.tsx | 18 + backend/.eslintrc.js | 25 + backend/.gitignore | 57 + backend/.prettierrc | 4 + backend/nest-cli.json | 9 + backend/package.json | 88 + backend/src/app.controller.spec.ts | 39 + backend/src/app.controller.ts | 13 + backend/src/app.module.ts | 38 + backend/src/app.service.ts | 25 + backend/src/auth.guard.ts | 24 + backend/src/beacon/beacon.controller.ts | 62 + backend/src/beacon/beacon.module.ts | 12 + backend/src/beacon/beacon.service.ts | 250 + .../beacon/tests/beacon.controller.spec.ts | 242 + backend/src/database/database.config.ts | 9 + backend/src/logs/entities/log.entity.ts | 26 + backend/src/logs/logs.controller.ts | 33 + backend/src/logs/logs.module.ts | 14 + backend/src/logs/logs.service.ts | 111 + .../src/logs/tests/logs.controller.spec.ts | 90 + backend/src/main.ts | 10 + backend/src/node/node.controller.ts | 14 + backend/src/node/node.module.ts | 12 + backend/src/node/node.service.ts | 143 + .../src/node/tests/node.controller.spec.ts | 93 + backend/src/session.guard.ts | 38 + backend/src/tasks/tasks.module.ts | 14 + backend/src/tasks/tasks.service.ts | 194 + backend/src/utilities.ts | 11 + backend/src/utils/utils.module.ts | 11 + backend/src/utils/utils.service.ts | 143 + .../src/validator/entities/metric.entity.ts | 18 + .../tests/validator.controller.spec.ts | 179 + backend/src/validator/validator.controller.ts | 57 + backend/src/validator/validator.module.ts | 12 + backend/src/validator/validator.service.ts | 199 + backend/tsconfig.build.json | 4 + backend/tsconfig.json | 21 + backend/yarn.lock | 6063 ++++ custom.d.ts | 15 +- docker-assets/docker-entrypoint.sh | 57 + docker-assets/siren-http.conf | 7 + docker-assets/siren-https.conf | 13 + docker-compose.yml | 11 + forge.config.js | 32 - jest.config.js | 13 +- local-testnet/.gitignore | 1 + local-testnet/README.md | 119 +- local-testnet/beacon_node.sh | 69 - local-testnet/bootnode.sh | 36 - local-testnet/clean.sh | 13 - local-testnet/dump_logs.sh | 17 - local-testnet/el_bootnode.sh | 3 - .../generate_bls_to_execution_change.sh | 22 +- local-testnet/genesis.json | 860 - local-testnet/geth.sh | 54 - local-testnet/kill_processes.sh | 19 - local-testnet/network_params.yaml | 20 + local-testnet/reset_genesis_time.sh | 18 - local-testnet/setup.sh | 53 - local-testnet/start_local_testnet.sh | 154 +- local-testnet/stop_local_testnet.sh | 17 +- local-testnet/validator_client.sh | 37 - local-testnet/vars.env | 64 - middleware.ts | 41 + next.config.js | 14 + package.json | 147 +- postcss.config.js | 10 +- .../Fonts/Archivo/Archivo-Bold.ttf | Bin .../Fonts/Archivo/Archivo-Regular.ttf | Bin .../Fonts/OpenSauce/OpenSauceOne-Black.ttf | Bin .../OpenSauce/OpenSauceOne-BlackItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceOne-Bold.ttf | Bin .../OpenSauce/OpenSauceOne-BoldItalic.ttf | Bin .../OpenSauce/OpenSauceOne-ExtraBold.ttf | Bin .../OpenSauceOne-ExtraBoldItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceOne-Italic.ttf | Bin .../Fonts/OpenSauce/OpenSauceOne-Light.ttf | Bin .../OpenSauce/OpenSauceOne-LightItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceOne-Medium.ttf | Bin .../OpenSauce/OpenSauceOne-MediumItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceOne-Regular.ttf | Bin .../Fonts/OpenSauce/OpenSauceOne-SemiBold.ttf | Bin .../OpenSauce/OpenSauceOne-SemiBoldItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceSans-Black.ttf | Bin .../OpenSauce/OpenSauceSans-BlackItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceSans-Bold.ttf | Bin .../OpenSauce/OpenSauceSans-BoldItalic.ttf | Bin .../OpenSauce/OpenSauceSans-ExtraBold.ttf | Bin .../OpenSauceSans-ExtraBoldItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceSans-Italic.ttf | Bin .../Fonts/OpenSauce/OpenSauceSans-Light.ttf | Bin .../OpenSauce/OpenSauceSans-LightItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceSans-Medium.ttf | Bin .../OpenSauce/OpenSauceSans-MediumItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceSans-Regular.ttf | Bin .../OpenSauce/OpenSauceSans-SemiBold.ttf | Bin .../OpenSauceSans-SemiBoldItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceTwo-Black.ttf | Bin .../OpenSauce/OpenSauceTwo-BlackItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceTwo-Bold.ttf | Bin .../OpenSauce/OpenSauceTwo-BoldItalic.ttf | Bin .../OpenSauce/OpenSauceTwo-ExtraBold.ttf | Bin .../OpenSauceTwo-ExtraBoldItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceTwo-Italic.ttf | Bin .../Fonts/OpenSauce/OpenSauceTwo-Light.ttf | Bin .../OpenSauce/OpenSauceTwo-LightItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceTwo-Medium.ttf | Bin .../OpenSauce/OpenSauceTwo-MediumItalic.ttf | Bin .../Fonts/OpenSauce/OpenSauceTwo-Regular.ttf | Bin .../Fonts/OpenSauce/OpenSauceTwo-SemiBold.ttf | Bin .../OpenSauce/OpenSauceTwo-SemiBoldItalic.ttf | Bin .../Fonts/Roboto/Roboto-Black.ttf | Bin .../Fonts/Roboto/Roboto-BlackItalic.ttf | Bin .../Fonts/Roboto/Roboto-Bold.ttf | Bin .../Fonts/Roboto/Roboto-BoldItalic.ttf | Bin .../Fonts/Roboto/Roboto-Italic.ttf | Bin .../Fonts/Roboto/Roboto-Light.ttf | Bin .../Fonts/Roboto/Roboto-LightItalic.ttf | Bin .../Fonts/Roboto/Roboto-Medium.ttf | Bin .../Fonts/Roboto/Roboto-MediumItalic.ttf | Bin .../Fonts/Roboto/Roboto-Regular.ttf | Bin .../Fonts/Roboto/Roboto-Thin.ttf | Bin .../Fonts/Roboto/Roboto-ThinItalic.ttf | Bin public/index.html | 7 +- public/siren.png | Bin 0 -> 157922 bytes setup-test.ts | 2 + siren.js | 75 + src/App.test.tsx | 2 +- src/App.tsx | 45 - src/api/beacon.ts | 41 - src/api/lighthouse.ts | 36 - src/assets/images/eth-outline.svg | 2 +- src/assets/images/lightEth.svg | 2 +- src/assets/images/lightHouseFull.svg | 2 +- src/assets/images/spinner.svg | 2 +- src/assets/images/waves.png | Bin 92667 -> 0 bytes .../AccountEarnings/AccountEarning.tsx | 78 +- .../AccountEarnings/EarningsLayout.tsx | 8 +- .../ActiveValidatorCount.tsx | 15 +- src/components/AlertCard/AlertCard.tsx | 2 +- .../AlertFilterSettings.tsx | 4 +- src/components/AlertIcon/AlertIcon.tsx | 31 + .../AlertInfo.tsx | 50 +- .../AlertInfo/PriorityLogAlerts.tsx | 72 + .../AnimatedHeader/AnimatedHeader.tsx | 36 + .../AppDescription/AppDescription.tsx | 4 +- src/components/AppGreeting/AppGreeting.tsx | 55 + src/components/AppVersion/AppVersion.tsx | 18 +- src/components/AuthPrompt/AuthPrompt.tsx | 90 + .../BasicValidatorMetrics.tsx | 18 +- .../BeaconChaLink/BeaconChaLink.tsx | 4 +- .../BeaconNetwork/BeaconNetwork.spec.tsx | 8 +- .../BeaconNetwork/BeaconNetwork.tsx | 8 +- .../BeaconSyncCard/BeaconSyncCard.tsx | 24 +- .../BlsExecutionModal/BlsExecutionModal.tsx | 187 +- src/components/BreadCrumb/BreadCrumb.tsx | 7 +- src/components/Button/Button.stories.tsx | 42 - src/components/Button/Button.tsx | 15 +- src/components/CheckBox/CheckBox.spec.tsx | 2 +- src/components/CheckBox/CheckBox.tsx | 2 +- src/components/ConfigModal/ConfigModal.tsx | 52 + .../CurrencySelect/CurrencySelect.spec.tsx | 36 - .../CurrencySelect/CurrencySelect.tsx | 33 +- .../DashboardOptions/DashboardOptions.tsx | 48 +- .../DashboardWrapper/DashboardWrapper.tsx | 72 + src/components/DeviceName/DeviceName.tsx | 4 +- src/components/DeviceSelect/DeviceSelect.tsx | 179 - .../DiagnosticCard/DiagnosticCard.stories.tsx | 40 - .../DiagnosticCard/DiagnosticCard.tsx | 44 +- .../DiagnosticOverviewText.tsx | 2 +- .../DiagnosticSummaryCard.tsx | 8 +- .../DiagnosticTable/DiagnosticTable.tsx | 21 +- .../DiagnosticTable/HardwareInfo.tsx | 47 +- src/components/DiagnosticTable/LogsInfo.tsx | 55 +- .../DiagnosticTable/TableColumnFallback.tsx | 2 +- .../DisabledTooltip/DisabledTooltip.tsx | 21 +- .../DisclosureModal/DisclosureModal.tsx | 4 +- src/components/Disclosures/ExitDisclosure.tsx | 11 +- .../Disclosures/HealthDisclosure.tsx | 8 +- src/components/Disclosures/SyncDisclosure.tsx | 8 +- .../Disclosures/ValidatorDisclosure.tsx | 11 +- src/components/DropDown/DropDown.tsx | 2 +- src/components/DropDown/DropDownItem.tsx | 17 +- .../EditValidator/EditValidator.tsx | 86 - .../EditValidatorModal/EditValidatorModal.tsx | 86 + .../EffectivenessBreakdown.tsx | 10 +- src/components/ExternalLink/ExternalLink.tsx | 13 - src/components/Fallback/AppLoadFallback.tsx | 14 + src/components/FootBar/FootBar.stories.tsx | 12 - src/components/FootBar/FootBar.tsx | 16 +- src/components/FootBar/HealthMetric.tsx | 29 +- .../GradientHeader/GradientHeader.tsx | 15 +- src/components/HealthCheck/DeviceHealth.tsx | 22 +- src/components/HealthCheck/HealthFallBack.tsx | 2 +- src/components/HealthCheck/HealthOverview.tsx | 31 +- src/components/HealthCheck/NetworkHealth.tsx | 31 +- .../IdenticonIcon/IdenticonIcon.tsx | 9 +- src/components/InfoBox/InfoBox.tsx | 16 +- src/components/Input/Input.stories.tsx | 41 - src/components/Input/Input.tsx | 13 +- .../LoadingSpinner/LoadingSpinner.tsx | 4 +- src/components/LogControls/LogControls.tsx | 89 + src/components/LogDisplay/LogDisplay.tsx | 62 +- src/components/LogDisplay/LogRow.tsx | 10 +- src/components/LogStats/LogStats.tsx | 61 +- .../NetworkErrorModal.spec.tsx | 28 +- .../NetworkErrorModal/NetworkErrorModal.tsx | 33 +- .../NetworkPeerSpeedometer.tsx | 49 +- .../NetworkStats/NetworkStatBlock.tsx | 13 +- src/components/NetworkStats/NetworkStats.tsx | 72 +- .../OverallEffectiveness.tsx | 33 +- src/components/PillIcon/PillIcon.tsx | 2 +- .../ProgressCircle/ProgressCircle.stories.tsx | 48 - src/components/ProposerAlerts/AlertGroup.tsx | 76 + .../ProposerAlerts/ProposalAlert.tsx | 41 + .../ProposerAlerts/ProposerAlerts.tsx | 60 + .../ProtocolInput/ProtocolInput.stories.tsx | 76 - .../ProtocolInput/ProtocolInput.tsx | 185 - .../ProviderCard/ProviderCard.stories.tsx | 32 - src/components/RodalModal/RodalModal.tsx | 19 +- .../SSELogProvider/SSELogProvider.tsx | 61 +- .../SelectDropDown/SelectDropDown.stories.tsx | 20 - .../SelectDropDown/SelectDropDown.tsx | 6 +- .../SessionAuthModal/SessionAuthModal.tsx | 23 +- src/components/SideBar/SideBar.tsx | 68 +- src/components/SideBar/SideBarText.tsx | 35 +- src/components/SideBar/SideItem.tsx | 32 +- src/components/SocialIcon/SocialIcon.tsx | 2 +- src/components/Status/Status.stories.tsx | 25 - src/components/StatusIcon/StatusIcon.tsx | 6 +- src/components/StepChart/StepChart.tsx | 16 +- src/components/SyncCard/SyncCard.tsx | 10 +- .../SyncMetric/SyncMetric.stories.tsx | 27 - src/components/SyncMetric/SyncMetric.tsx | 6 +- src/components/TabOption/TabOption.tsx | 4 +- src/components/Toggle/Toggle.stories.tsx | 24 - src/components/Toggle/Toggle.tsx | 3 +- src/components/ToolTip/Tooltip.tsx | 17 +- src/components/TopBar/BeaconMetric.tsx | 33 +- src/components/TopBar/TopBar.tsx | 42 +- src/components/TopBar/ValidatorMetric.tsx | 34 +- src/components/Topography/Topography.tsx | 101 + .../Typography/Typography.stories.tsx | 81 - src/components/Typography/Typography.tsx | 10 +- src/components/UiModeIcon/UiModeIcon.tsx | 8 +- .../ValidatorActionIcon.tsx | 5 +- .../ValidatorBalances/ValidatorBalances.tsx | 95 +- .../ValidatorDetailTable.tsx | 34 +- .../ValidatorGraffiti/ValidatorGraffiti.tsx | 86 + .../ValidatorGraffitiInput.tsx | 9 +- .../ValidatorIncomeSummary.tsx | 27 +- .../ValidatorInfoCard/ValidatorInfoCard.tsx | 99 +- .../ValidatorInfoHeader.tsx | 33 +- .../ValidatorModal/ValidatorActions.tsx | 15 +- .../ValidatorModal/ValidatorCardAction.tsx | 6 +- .../ValidatorModal/ValidatorModal.tsx | 58 +- .../ValidatorModal/views/ValidatorDetails.tsx | 219 +- .../ValidatorModal/views/ValidatorExit.tsx | 187 +- .../ValidatorSearchInput.tsx | 19 +- .../ValidatorSetupLayout.tsx | 38 +- .../ValidatorStatusProgress.tsx | 8 +- .../ValidatorSummary/ValidatorSummary.tsx | 65 +- .../ValidatorSyncCard/ValidatorSyncCard.tsx | 16 +- .../ValidatorTable/ValidatorRow.tsx | 154 +- .../ValidatorTable/ValidatorTable.tsx | 415 +- src/components/VersionModal/VersionModal.tsx | 60 + .../ViewDisclosures/ViewDisclosures.tsx | 47 +- src/components/Wallet/Wallet.tsx | 4 +- src/constants/constants.ts | 37 +- src/constants/enums.ts | 17 +- src/forms/AuthenticationForm.tsx | 63 + src/forms/ConfigConnectionForm.tsx | 325 - src/forms/EditValidatorForm.tsx | 39 +- src/forms/SessionAuthForm.tsx | 104 - src/global.css | 227 +- .../__tests__/useDeviceDiagnostics.spec.ts | 140 - .../__tests__/useEarningsEstimate.spec.ts | 98 - .../__tests__/useEpochAprEstimate.spec.ts | 117 +- src/hooks/__tests__/useExitValidator.spec.ts | 82 - .../useFilteredValidatorCacheData.spec.ts | 32 +- .../__tests__/useParticipationRate.spec.ts | 145 - src/hooks/__tests__/useUiMode.spec.ts | 2 +- src/hooks/__tests__/useValidatorCount.spec.ts | 61 - .../useValidatorEpochBalance.spec.ts | 62 - .../__tests__/useValidatorGraffiti.spec.ts | 62 - src/hooks/useApiValidation.ts | 56 - src/hooks/useAtomCleanup.ts | 70 - src/hooks/useBeaconHealthPolling.ts | 31 - src/hooks/useBeaconSyncPolling.ts | 38 - src/hooks/useClickOutside.ts | 10 +- src/hooks/useDeviceDiagnostics.ts | 130 - src/hooks/useDiagnosticAlerts.ts | 88 +- src/hooks/useDivDimensions.ts | 20 +- src/hooks/useEarningsEstimate.ts | 7 +- src/hooks/useEpochAprEstimate.ts | 30 +- src/hooks/useExchangeRatePolling.ts | 31 - src/hooks/useExitValidator.ts | 47 - src/hooks/useFilteredValidatorCacheData.ts | 13 +- src/hooks/useLocalStorage.ts | 2 +- src/hooks/useNetworkMonitor.ts | 21 + src/hooks/useParticipationRate.ts | 62 - src/hooks/usePollApi.ts | 66 - src/hooks/usePrevious.ts | 13 - src/hooks/useSSE.ts | 41 - src/hooks/useSWRPolling.ts | 38 + src/hooks/useTrackLogs.ts | 181 +- src/hooks/useUiMode.ts | 23 +- src/hooks/useValidatorCachePolling.ts | 45 - src/hooks/useValidatorCount.ts | 31 - src/hooks/useValidatorEarnings.ts | 65 +- src/hooks/useValidatorEffectiveness.ts | 37 - src/hooks/useValidatorEpochBalance.ts | 55 - src/hooks/useValidatorGraffiti.ts | 71 - src/hooks/useValidatorHealthPolling.ts | 30 - src/hooks/useValidatorInfoPolling.ts | 39 - src/hooks/useValidatorMetrics.ts | 63 - src/hooks/useValidatorName.ts | 11 + src/hooks/useValidatorPeerPolling.ts | 29 - src/hooks/useValidatorSyncPolling.ts | 30 - src/index.tsx | 18 - src/locales/translations/en-US.json | 53 +- src/locales/translations/es.json | 2 +- src/main.js | 73 - src/mocks/beacon.ts | 62 + src/mocks/beaconHealthResult.ts | 27 - src/mocks/beaconSpec.ts | 41 + src/mocks/beaconSyncResults.ts | 7 - src/mocks/logs.ts | 7 + src/mocks/validatorResults.ts | 301 +- src/recoil/atoms.ts | 91 +- .../selectors/selectActiveValidators.ts | 27 - .../selectors/selectBeaconChaBaseUrl.ts | 26 +- src/recoil/selectors/selectBeaconSyncInfo.ts | 22 - src/recoil/selectors/selectBeaconVersion.ts | 12 - src/recoil/selectors/selectBnChain.ts | 8 +- src/recoil/selectors/selectBnSpec.ts | 19 - src/recoil/selectors/selectCurrencyPrefix.ts | 17 - .../selectors/selectFilteredValidators.ts | 22 - src/recoil/selectors/selectGenesisBlock.ts | 13 - .../selectors/selectSlicedActiveValidators.ts | 12 - .../selectors/selectValidatorDetails.ts | 14 - src/recoil/selectors/selectValidatorInfos.ts | 37 - .../selectors/selectValidatorSyncInfo.ts | 29 - .../selectors/selectValidatorVersion.ts | 12 - src/recoil/selectors/selectValidators.ts | 16 - src/types/beacon.ts | 23 +- src/types/diagnostic.ts | 27 +- src/types/index.ts | 65 +- src/types/storage.ts | 10 +- src/types/validator.ts | 38 +- src/utilities/debounce.ts | 18 - src/utilities/getValuesFromObjArray.ts | 14 - src/utilities/parseEndpointString.ts | 22 - src/validation/configValidation.ts | 2 +- src/validation/editValidatorValidation.ts | 2 +- src/validation/sessionAuthValidation.ts | 2 +- src/views/ChangeScreen.tsx | 28 - src/views/DashBoard/Content/Grafana.tsx | 11 - src/views/DashBoard/Content/Logs.tsx | 111 - src/views/DashBoard/Content/MainContent.tsx | 91 - src/views/DashBoard/Content/Settings.tsx | 185 - src/views/DashBoard/Content/Validators.tsx | 70 - src/views/DashBoard/Dashboard.tsx | 98 - src/views/InitScreen.tsx | 191 - src/views/Onboard/Onboard.tsx | 35 - .../Onboard/views/ConfigureConnection.tsx | 206 - src/views/Onboard/views/SelectProvider.tsx | 52 - src/views/Onboard/views/SessionConfig.tsx | 74 - .../ValidatorSetup/Steps/HealthCheck.tsx | 58 - .../views/ValidatorSetup/Steps/NodeSync.tsx | 65 - .../views/ValidatorSetup/ValidatorSetup.tsx | 59 - src/wrappers/MainPollingWrapper.tsx | 34 - src/wrappers/SyncPollingWrapper.tsx | 25 - src/wrappers/ValidatorPollingWrapper.tsx | 29 - tailwind.config.js | 10 +- tsconfig.jest.json | 11 + tsconfig.json | 23 +- .../__tests__/addClassString.spec.ts | 0 .../__tests__/addSuffixString.spec.ts | 0 .../__tests__/calculateAprPercentage.spec.ts | 0 .../__tests__/calculateEpochEstimate.spec.ts | 12 +- .../__tests__/debouce.spec.ts | 0 .../__tests__/formatAtHeadSlotStatus.spec.ts | 2 +- .../__tests__/formatBalanceColor.spec.ts | 0 .../__tests__/formatEndpoint.spec.ts | 2 +- .../__tests__/formatEthAddress.spec.ts | 0 .../__tests__/formatGigBytes.spec.ts | 0 .../__tests__/formatLocalCurrency.spec.ts | 0 .../__tests__/formatNodeVersion.spec.ts | 0 .../__tests__/formatSemanticVersion.spec.ts | 0 .../__tests__/generateId.spec.ts | 0 .../__tests__/getAverageValue.spec.ts | 0 .../getAvgEffectivenessStatus.spec.ts | 2 +- .../__tests__/getPercentage.spec.ts | 0 .../__tests__/getRandBetween.spec.ts | 0 .../__tests__/getTimeRemaining.spec.ts | 0 .../__tests__/getValidatorStatusStage.spec.ts | 0 .../__tests__/getValuesFromObjArray.spec.ts | 0 .../__tests__/isRequiredVersion.spec.ts | 0 .../__tests__/isValidJSONArray.spec.ts | 0 .../__tests__/secondsToShortHand.spec.ts | 0 .../utilities => utilities}/addClassString.ts | 0 .../addSuffixString.ts | 0 .../calculateAprPercentage.ts | 0 .../calculateEpochEstimate.ts | 14 +- utilities/debounce.ts | 23 + {src/utilities => utilities}/displayToast.ts | 8 +- utilities/fetchFromApi.ts | 28 + .../formatAtHeadSlotStatus.ts | 2 +- .../formatBalanceColor.ts | 2 +- .../formatDefaultValName.ts | 0 .../utilities => utilities}/formatEndpoint.ts | 2 +- .../formatEthAddress.ts | 0 utilities/formatExecSyncInfo.ts | 24 + .../utilities => utilities}/formatGigBytes.ts | 0 .../formatLocalCurrency.ts | 5 +- .../formatNodeVersion.ts | 0 .../formatSemanticVersion.ts | 2 +- utilities/formatUniqueObjectArray.ts | 9 + utilities/formatValidatorEffectiveness.ts | 27 + utilities/formatValidatorEpochData.ts | 26 + {src/utilities => utilities}/generateId.ts | 0 utilities/getAverageKeyValue.ts | 5 + .../getAverageValue.ts | 0 .../getAvgEffectivenessStatus.ts | 2 +- utilities/getInclusionRateStatus.ts | 15 + {src/utilities => utilities}/getPercentage.ts | 0 .../utilities => utilities}/getRandBetween.ts | 0 utilities/getReqAuthToken.ts | 7 + utilities/getSessionCookie.ts | 8 + utilities/getSlotTimeData.ts | 17 + .../getTimeRemaining.ts | 0 .../getValidatorStatusStage.ts | 2 +- utilities/getValuesFromObjArray.ts | 10 + utilities/groupArray.ts | 9 + utilities/hashToRange.ts | 6 + {src/utilities => utilities}/isBlsAddress.ts | 0 utilities/isExpiredToken.ts | 14 + .../isRequiredVersion.ts | 2 +- {src/utilities => utilities}/isValidJson.ts | 0 utilities/parseEndpointString.ts | 22 + {src/utilities => utilities}/reduceAddNum.ts | 0 .../secondsToShortHand.ts | 0 {src/utilities => utilities}/sortAlerts.ts | 2 +- utilities/stringToHash.ts | 11 + utilities/swrGetFetcher.ts | 9 + utilities/timeFilterObjArray.ts | 16 + .../toFixedIfNecessary.ts | 0 yarn.lock | 23885 ++++------------ 521 files changed, 20569 insertions(+), 28188 deletions(-) create mode 100644 .dockerignore delete mode 100644 .github/workflows/ui-tests.yml delete mode 100644 .storybook/main.js delete mode 100644 .storybook/preview.js delete mode 100644 Dockerfile.dev delete mode 100644 Dockerfile.release delete mode 100644 Makefile create mode 100644 app/Main.tsx create mode 100644 app/Providers.tsx create mode 100644 app/Wrapper.tsx create mode 100644 app/api/authenticate/route.ts create mode 100644 app/api/beacon-heartbeat/route.ts create mode 100644 app/api/beacon-version/route.ts create mode 100644 app/api/beacon.ts create mode 100644 app/api/bls-execution/route.ts create mode 100644 app/api/config.ts create mode 100644 app/api/dismiss-log/[index]/route.ts create mode 100644 app/api/execute-validator-exit/route.ts create mode 100644 app/api/lighthouse-version/route.ts create mode 100644 app/api/logs.ts create mode 100644 app/api/node-health/route.ts create mode 100644 app/api/node-sync/route.ts create mode 100644 app/api/peer-data/route.ts create mode 100644 app/api/priority-logs/route.ts create mode 100644 app/api/sign-validator-exit/route.ts create mode 100644 app/api/update-graffiti/route.ts create mode 100644 app/api/validator-cache/route.ts create mode 100644 app/api/validator-duties/route.ts create mode 100644 app/api/validator-graffiti/[index]/route.ts create mode 100644 app/api/validator-heartbeat/route.ts create mode 100644 app/api/validator-inclusion/route.ts create mode 100644 app/api/validator-metrics/[[...index]]/route.ts create mode 100644 app/api/validator-network/route.ts create mode 100644 app/api/validator-states/route.ts create mode 100644 app/api/validator.ts create mode 100644 app/dashboard/Main.tsx create mode 100644 app/dashboard/Wrapper.tsx create mode 100644 app/dashboard/logs/Content.tsx create mode 100644 app/dashboard/logs/Main.tsx create mode 100644 app/dashboard/logs/Wrapper.tsx create mode 100644 app/dashboard/logs/page.tsx create mode 100644 app/dashboard/page.tsx create mode 100644 app/dashboard/settings/Main.tsx create mode 100644 app/dashboard/settings/Wrapper.tsx create mode 100644 app/dashboard/settings/page.tsx create mode 100644 app/dashboard/validators/Main.tsx create mode 100644 app/dashboard/validators/Wrapper.tsx create mode 100644 app/dashboard/validators/page.tsx create mode 100644 app/error/Main.tsx create mode 100644 app/error/page.tsx create mode 100644 app/icon.png create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 app/setup/health-check/Main.tsx create mode 100644 app/setup/health-check/Wrapper.tsx create mode 100644 app/setup/health-check/page.tsx create mode 100644 app/setup/node-sync/Main.tsx create mode 100644 app/setup/node-sync/Wrapper.tsx create mode 100644 app/setup/node-sync/page.tsx create mode 100644 backend/.eslintrc.js create mode 100644 backend/.gitignore create mode 100644 backend/.prettierrc create mode 100644 backend/nest-cli.json create mode 100644 backend/package.json create mode 100644 backend/src/app.controller.spec.ts create mode 100644 backend/src/app.controller.ts create mode 100644 backend/src/app.module.ts create mode 100644 backend/src/app.service.ts create mode 100644 backend/src/auth.guard.ts create mode 100644 backend/src/beacon/beacon.controller.ts create mode 100644 backend/src/beacon/beacon.module.ts create mode 100644 backend/src/beacon/beacon.service.ts create mode 100644 backend/src/beacon/tests/beacon.controller.spec.ts create mode 100644 backend/src/database/database.config.ts create mode 100644 backend/src/logs/entities/log.entity.ts create mode 100644 backend/src/logs/logs.controller.ts create mode 100644 backend/src/logs/logs.module.ts create mode 100644 backend/src/logs/logs.service.ts create mode 100644 backend/src/logs/tests/logs.controller.spec.ts create mode 100644 backend/src/main.ts create mode 100644 backend/src/node/node.controller.ts create mode 100644 backend/src/node/node.module.ts create mode 100644 backend/src/node/node.service.ts create mode 100644 backend/src/node/tests/node.controller.spec.ts create mode 100644 backend/src/session.guard.ts create mode 100644 backend/src/tasks/tasks.module.ts create mode 100644 backend/src/tasks/tasks.service.ts create mode 100644 backend/src/utilities.ts create mode 100644 backend/src/utils/utils.module.ts create mode 100644 backend/src/utils/utils.service.ts create mode 100644 backend/src/validator/entities/metric.entity.ts create mode 100644 backend/src/validator/tests/validator.controller.spec.ts create mode 100644 backend/src/validator/validator.controller.ts create mode 100644 backend/src/validator/validator.module.ts create mode 100644 backend/src/validator/validator.service.ts create mode 100644 backend/tsconfig.build.json create mode 100644 backend/tsconfig.json create mode 100644 backend/yarn.lock create mode 100755 docker-assets/docker-entrypoint.sh create mode 100644 docker-assets/siren-http.conf create mode 100644 docker-assets/siren-https.conf create mode 100644 docker-compose.yml delete mode 100644 forge.config.js create mode 100644 local-testnet/.gitignore delete mode 100755 local-testnet/beacon_node.sh delete mode 100755 local-testnet/bootnode.sh delete mode 100755 local-testnet/clean.sh delete mode 100755 local-testnet/dump_logs.sh delete mode 100755 local-testnet/el_bootnode.sh delete mode 100644 local-testnet/genesis.json delete mode 100755 local-testnet/geth.sh delete mode 100755 local-testnet/kill_processes.sh create mode 100644 local-testnet/network_params.yaml delete mode 100755 local-testnet/reset_genesis_time.sh delete mode 100755 local-testnet/setup.sh delete mode 100755 local-testnet/validator_client.sh delete mode 100644 local-testnet/vars.env create mode 100644 middleware.ts create mode 100644 next.config.js rename {src/assets => public}/Fonts/Archivo/Archivo-Bold.ttf (100%) rename {src/assets => public}/Fonts/Archivo/Archivo-Regular.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-Black.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-BlackItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-Bold.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-BoldItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-ExtraBold.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-ExtraBoldItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-Italic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-Light.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-LightItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-Medium.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-MediumItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-Regular.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-SemiBold.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceOne-SemiBoldItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-Black.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-BlackItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-Bold.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-BoldItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-ExtraBold.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-ExtraBoldItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-Italic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-Light.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-LightItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-Medium.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-MediumItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-Regular.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-SemiBold.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceSans-SemiBoldItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-Black.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-BlackItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-Bold.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-BoldItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-ExtraBold.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-ExtraBoldItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-Italic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-Light.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-LightItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-Medium.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-MediumItalic.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-Regular.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-SemiBold.ttf (100%) rename {src/assets => public}/Fonts/OpenSauce/OpenSauceTwo-SemiBoldItalic.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-Black.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-BlackItalic.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-Bold.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-BoldItalic.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-Italic.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-Light.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-LightItalic.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-Medium.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-MediumItalic.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-Regular.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-Thin.ttf (100%) rename {src/assets => public}/Fonts/Roboto/Roboto-ThinItalic.ttf (100%) create mode 100644 public/siren.png create mode 100644 siren.js delete mode 100644 src/App.tsx delete mode 100644 src/api/beacon.ts delete mode 100644 src/api/lighthouse.ts delete mode 100644 src/assets/images/waves.png create mode 100644 src/components/AlertIcon/AlertIcon.tsx rename src/components/{DiagnosticTable => AlertInfo}/AlertInfo.tsx (58%) create mode 100644 src/components/AlertInfo/PriorityLogAlerts.tsx create mode 100644 src/components/AnimatedHeader/AnimatedHeader.tsx create mode 100644 src/components/AppGreeting/AppGreeting.tsx create mode 100644 src/components/AuthPrompt/AuthPrompt.tsx delete mode 100644 src/components/Button/Button.stories.tsx create mode 100644 src/components/ConfigModal/ConfigModal.tsx delete mode 100644 src/components/CurrencySelect/CurrencySelect.spec.tsx create mode 100644 src/components/DashboardWrapper/DashboardWrapper.tsx delete mode 100644 src/components/DeviceSelect/DeviceSelect.tsx delete mode 100644 src/components/DiagnosticCard/DiagnosticCard.stories.tsx delete mode 100644 src/components/EditValidator/EditValidator.tsx create mode 100644 src/components/EditValidatorModal/EditValidatorModal.tsx delete mode 100644 src/components/ExternalLink/ExternalLink.tsx create mode 100644 src/components/Fallback/AppLoadFallback.tsx delete mode 100644 src/components/FootBar/FootBar.stories.tsx delete mode 100644 src/components/Input/Input.stories.tsx create mode 100644 src/components/LogControls/LogControls.tsx delete mode 100644 src/components/ProgressCircle/ProgressCircle.stories.tsx create mode 100644 src/components/ProposerAlerts/AlertGroup.tsx create mode 100644 src/components/ProposerAlerts/ProposalAlert.tsx create mode 100644 src/components/ProposerAlerts/ProposerAlerts.tsx delete mode 100644 src/components/ProtocolInput/ProtocolInput.stories.tsx delete mode 100644 src/components/ProtocolInput/ProtocolInput.tsx delete mode 100644 src/components/ProviderCard/ProviderCard.stories.tsx delete mode 100644 src/components/SelectDropDown/SelectDropDown.stories.tsx delete mode 100644 src/components/Status/Status.stories.tsx delete mode 100644 src/components/SyncMetric/SyncMetric.stories.tsx delete mode 100644 src/components/Toggle/Toggle.stories.tsx create mode 100644 src/components/Topography/Topography.tsx delete mode 100644 src/components/Typography/Typography.stories.tsx create mode 100644 src/components/ValidatorGraffiti/ValidatorGraffiti.tsx create mode 100644 src/components/VersionModal/VersionModal.tsx create mode 100644 src/forms/AuthenticationForm.tsx delete mode 100644 src/forms/ConfigConnectionForm.tsx delete mode 100644 src/forms/SessionAuthForm.tsx delete mode 100644 src/hooks/__tests__/useDeviceDiagnostics.spec.ts delete mode 100644 src/hooks/__tests__/useEarningsEstimate.spec.ts delete mode 100644 src/hooks/__tests__/useExitValidator.spec.ts delete mode 100644 src/hooks/__tests__/useParticipationRate.spec.ts delete mode 100644 src/hooks/__tests__/useValidatorCount.spec.ts delete mode 100644 src/hooks/__tests__/useValidatorEpochBalance.spec.ts delete mode 100644 src/hooks/__tests__/useValidatorGraffiti.spec.ts delete mode 100644 src/hooks/useApiValidation.ts delete mode 100644 src/hooks/useAtomCleanup.ts delete mode 100644 src/hooks/useBeaconHealthPolling.ts delete mode 100644 src/hooks/useBeaconSyncPolling.ts delete mode 100644 src/hooks/useDeviceDiagnostics.ts delete mode 100644 src/hooks/useExchangeRatePolling.ts delete mode 100644 src/hooks/useExitValidator.ts create mode 100644 src/hooks/useNetworkMonitor.ts delete mode 100644 src/hooks/useParticipationRate.ts delete mode 100644 src/hooks/usePollApi.ts delete mode 100644 src/hooks/usePrevious.ts delete mode 100644 src/hooks/useSSE.ts create mode 100644 src/hooks/useSWRPolling.ts delete mode 100644 src/hooks/useValidatorCachePolling.ts delete mode 100644 src/hooks/useValidatorCount.ts delete mode 100644 src/hooks/useValidatorEffectiveness.ts delete mode 100644 src/hooks/useValidatorEpochBalance.ts delete mode 100644 src/hooks/useValidatorGraffiti.ts delete mode 100644 src/hooks/useValidatorHealthPolling.ts delete mode 100644 src/hooks/useValidatorInfoPolling.ts delete mode 100644 src/hooks/useValidatorMetrics.ts create mode 100644 src/hooks/useValidatorName.ts delete mode 100644 src/hooks/useValidatorPeerPolling.ts delete mode 100644 src/hooks/useValidatorSyncPolling.ts delete mode 100644 src/index.tsx delete mode 100644 src/main.js create mode 100644 src/mocks/beacon.ts delete mode 100644 src/mocks/beaconHealthResult.ts create mode 100644 src/mocks/beaconSpec.ts delete mode 100644 src/mocks/beaconSyncResults.ts create mode 100644 src/mocks/logs.ts delete mode 100644 src/recoil/selectors/selectActiveValidators.ts delete mode 100644 src/recoil/selectors/selectBeaconSyncInfo.ts delete mode 100644 src/recoil/selectors/selectBeaconVersion.ts delete mode 100644 src/recoil/selectors/selectBnSpec.ts delete mode 100644 src/recoil/selectors/selectCurrencyPrefix.ts delete mode 100644 src/recoil/selectors/selectFilteredValidators.ts delete mode 100644 src/recoil/selectors/selectGenesisBlock.ts delete mode 100644 src/recoil/selectors/selectSlicedActiveValidators.ts delete mode 100644 src/recoil/selectors/selectValidatorDetails.ts delete mode 100644 src/recoil/selectors/selectValidatorInfos.ts delete mode 100644 src/recoil/selectors/selectValidatorSyncInfo.ts delete mode 100644 src/recoil/selectors/selectValidatorVersion.ts delete mode 100644 src/recoil/selectors/selectValidators.ts delete mode 100644 src/utilities/debounce.ts delete mode 100644 src/utilities/getValuesFromObjArray.ts delete mode 100644 src/utilities/parseEndpointString.ts delete mode 100644 src/views/ChangeScreen.tsx delete mode 100644 src/views/DashBoard/Content/Grafana.tsx delete mode 100644 src/views/DashBoard/Content/Logs.tsx delete mode 100644 src/views/DashBoard/Content/MainContent.tsx delete mode 100644 src/views/DashBoard/Content/Settings.tsx delete mode 100644 src/views/DashBoard/Content/Validators.tsx delete mode 100644 src/views/DashBoard/Dashboard.tsx delete mode 100644 src/views/InitScreen.tsx delete mode 100644 src/views/Onboard/Onboard.tsx delete mode 100644 src/views/Onboard/views/ConfigureConnection.tsx delete mode 100644 src/views/Onboard/views/SelectProvider.tsx delete mode 100644 src/views/Onboard/views/SessionConfig.tsx delete mode 100644 src/views/Onboard/views/ValidatorSetup/Steps/HealthCheck.tsx delete mode 100644 src/views/Onboard/views/ValidatorSetup/Steps/NodeSync.tsx delete mode 100644 src/views/Onboard/views/ValidatorSetup/ValidatorSetup.tsx delete mode 100644 src/wrappers/MainPollingWrapper.tsx delete mode 100644 src/wrappers/SyncPollingWrapper.tsx delete mode 100644 src/wrappers/ValidatorPollingWrapper.tsx create mode 100644 tsconfig.jest.json rename {src/utilities => utilities}/__tests__/addClassString.spec.ts (100%) rename {src/utilities => utilities}/__tests__/addSuffixString.spec.ts (100%) rename {src/utilities => utilities}/__tests__/calculateAprPercentage.spec.ts (100%) rename {src/utilities => utilities}/__tests__/calculateEpochEstimate.spec.ts (67%) rename {src/utilities => utilities}/__tests__/debouce.spec.ts (100%) rename {src/utilities => utilities}/__tests__/formatAtHeadSlotStatus.spec.ts (92%) rename {src/utilities => utilities}/__tests__/formatBalanceColor.spec.ts (100%) rename {src/utilities => utilities}/__tests__/formatEndpoint.spec.ts (91%) rename {src/utilities => utilities}/__tests__/formatEthAddress.spec.ts (100%) rename {src/utilities => utilities}/__tests__/formatGigBytes.spec.ts (100%) rename {src/utilities => utilities}/__tests__/formatLocalCurrency.spec.ts (100%) rename {src/utilities => utilities}/__tests__/formatNodeVersion.spec.ts (100%) rename {src/utilities => utilities}/__tests__/formatSemanticVersion.spec.ts (100%) rename {src/utilities => utilities}/__tests__/generateId.spec.ts (100%) rename {src/utilities => utilities}/__tests__/getAverageValue.spec.ts (100%) rename {src/utilities => utilities}/__tests__/getAvgEffectivenessStatus.spec.ts (94%) rename {src/utilities => utilities}/__tests__/getPercentage.spec.ts (100%) rename {src/utilities => utilities}/__tests__/getRandBetween.spec.ts (100%) rename {src/utilities => utilities}/__tests__/getTimeRemaining.spec.ts (100%) rename {src/utilities => utilities}/__tests__/getValidatorStatusStage.spec.ts (100%) rename {src/utilities => utilities}/__tests__/getValuesFromObjArray.spec.ts (100%) rename {src/utilities => utilities}/__tests__/isRequiredVersion.spec.ts (100%) rename {src/utilities => utilities}/__tests__/isValidJSONArray.spec.ts (100%) rename {src/utilities => utilities}/__tests__/secondsToShortHand.spec.ts (100%) rename {src/utilities => utilities}/addClassString.ts (100%) rename {src/utilities => utilities}/addSuffixString.ts (100%) rename {src/utilities => utilities}/calculateAprPercentage.ts (100%) rename {src/utilities => utilities}/calculateEpochEstimate.ts (56%) create mode 100644 utilities/debounce.ts rename {src/utilities => utilities}/displayToast.ts (77%) create mode 100644 utilities/fetchFromApi.ts rename {src/utilities => utilities}/formatAtHeadSlotStatus.ts (90%) rename {src/utilities => utilities}/formatBalanceColor.ts (72%) rename {src/utilities => utilities}/formatDefaultValName.ts (100%) rename {src/utilities => utilities}/formatEndpoint.ts (91%) rename {src/utilities => utilities}/formatEthAddress.ts (100%) create mode 100644 utilities/formatExecSyncInfo.ts rename {src/utilities => utilities}/formatGigBytes.ts (100%) rename {src/utilities => utilities}/formatLocalCurrency.ts (77%) rename {src/utilities => utilities}/formatNodeVersion.ts (100%) rename {src/utilities => utilities}/formatSemanticVersion.ts (91%) create mode 100644 utilities/formatUniqueObjectArray.ts create mode 100644 utilities/formatValidatorEffectiveness.ts create mode 100644 utilities/formatValidatorEpochData.ts rename {src/utilities => utilities}/generateId.ts (100%) create mode 100644 utilities/getAverageKeyValue.ts rename {src/utilities => utilities}/getAverageValue.ts (100%) rename {src/utilities => utilities}/getAvgEffectivenessStatus.ts (90%) create mode 100644 utilities/getInclusionRateStatus.ts rename {src/utilities => utilities}/getPercentage.ts (100%) rename {src/utilities => utilities}/getRandBetween.ts (100%) create mode 100644 utilities/getReqAuthToken.ts create mode 100644 utilities/getSessionCookie.ts create mode 100644 utilities/getSlotTimeData.ts rename {src/utilities => utilities}/getTimeRemaining.ts (100%) rename {src/utilities => utilities}/getValidatorStatusStage.ts (86%) create mode 100644 utilities/getValuesFromObjArray.ts create mode 100644 utilities/groupArray.ts create mode 100644 utilities/hashToRange.ts rename {src/utilities => utilities}/isBlsAddress.ts (100%) create mode 100644 utilities/isExpiredToken.ts rename {src/utilities => utilities}/isRequiredVersion.ts (91%) rename {src/utilities => utilities}/isValidJson.ts (100%) create mode 100644 utilities/parseEndpointString.ts rename {src/utilities => utilities}/reduceAddNum.ts (100%) rename {src/utilities => utilities}/secondsToShortHand.ts (100%) rename {src/utilities => utilities}/sortAlerts.ts (89%) create mode 100644 utilities/stringToHash.ts create mode 100644 utilities/swrGetFetcher.ts create mode 100644 utilities/timeFilterObjArray.ts rename {src/utilities => utilities}/toFixedIfNecessary.ts (100%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..b299feac --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +node_modules +backend/node_modules diff --git a/.env.example b/.env.example index e02d83a6..485185bc 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,9 @@ -PORT= -ELECTRON_START_URL= -APPLE_ID=YOUR-APPLE-ID -APPLE_PASSWORD=APP-SPECIFIC-PASSWORD -APPLE_TEAM_ID=YOUR-TEAM-ID \ No newline at end of file +BEACON_URL=http://your-BN-ip:5052 +VALIDATOR_URL=http://your-VC-ip:5062 +API_TOKEN=get-it-from-'.lighthouse/validators/api-token.txt' +SESSION_PASSWORD=default-siren-password +SSL_ENABLED=true +DEBUG=false +# don't change these when building the docker image, only change when running outside of docker +PORT=3000 +BACKEND_URL=http://127.0.0.1:3001 diff --git a/.eslintignore b/.eslintignore index f7bbf2a9..e2d3d5ce 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1 @@ -src/main.js src/scripts/release.js \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 7b7ea1a3..25846b0c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,39 +1,29 @@ { - "env": { - "browser": true, - "es2021": true - }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:@typescript-eslint/recommended", - "prettier" - ], - "overrides": [ + "plugins": ["@typescript-eslint", "import", "react-hooks", "unused-imports"], + "extends": ["next/core-web-vitals"], + "rules": { + "@typescript-eslint/ban-ts-comment": "off", + "import/order": [ + "error", + { + "alphabetize": { + "order": "asc", + "caseInsensitive": true + } + } ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": [ - "react", - "@typescript-eslint", - "prettier" + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "off", + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": [ + "warn", + { + "vars": "all", + "varsIgnorePattern": "^_", + "args": "after-used", + "argsIgnorePattern": "^_" + } ], - "rules": { - "react/react-in-jsx-scope": "off", - "spaced-comment": "error", - "quotes": ["error", "single"], - "no-duplicate-imports": "error", - "react/prop-types": 0, - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] - }, - "settings": { - "import/resolver": { - "typescript": {} - } - } + "react/react-in-jsx-scope": "off" + } } diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dcb15153..68834499 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: bug assignees: rickimoore - --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index a097ed20..63e411e0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: enhancement assignees: rickimoore - --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 888ff512..76eaab3f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,129 +1,96 @@ name: docker on: - push: - branches: - - unstable - - stable - tags: - - v* + push: + branches: + - unstable + - stable + tags: + - v* env: - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/siren + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/siren jobs: - # Extract the VERSION which is either `latest` or `vX.Y.Z`, and the VERSION_SUFFIX - # which is either empty or `-unstable`. - # - # It would be nice if the arch didn't get spliced into the version between `latest` and - # `unstable`, but for now we keep the two parts of the version separate for backwards - # compatibility. - extract-version: - runs-on: ubuntu-22.04 - steps: - - name: Extract version (if stable) - if: github.event.ref == 'refs/heads/stable' - run: | - echo "VERSION=latest" >> $GITHUB_ENV - echo "VERSION_SUFFIX=" >> $GITHUB_ENV - - name: Extract version (if unstable) - if: github.event.ref == 'refs/heads/unstable' - run: | - echo "VERSION=latest" >> $GITHUB_ENV - echo "VERSION_SUFFIX=-unstable" >> $GITHUB_ENV - - name: Extract version (if tagged release) - if: startsWith(github.event.ref, 'refs/tags') - run: | - echo "VERSION=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_ENV - echo "VERSION_SUFFIX=" >> $GITHUB_ENV - outputs: - VERSION: ${{ env.VERSION }} - VERSION_SUFFIX: ${{ env.VERSION_SUFFIX }} - build-html: - name: build html - runs-on: ubuntu-22.04 - needs: [extract-version] - steps: - - name: Checkout sources - uses: actions/checkout@v3 - - name: Use node 18 - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'yarn' - - name: Install dependencies - env: - NODE_ENV: development - run: | - yarn - - name: Build Siren - env: - NODE_ENV: production - run: yarn build - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: html - path: build/ + # Extract the VERSION which is either `latest` or `vX.Y.Z`, and the VERSION_SUFFIX + # which is either empty or `-unstable`. + # + # It would be nice if the arch didn't get spliced into the version between `latest` and + # `unstable`, but for now we keep the two parts of the version separate for backwards + # compatibility. + extract-version: + runs-on: ubuntu-22.04 + steps: + - name: Extract version (if stable) + if: github.event.ref == 'refs/heads/stable' + run: | + echo "VERSION=latest" >> $GITHUB_ENV + echo "VERSION_SUFFIX=" >> $GITHUB_ENV + - name: Extract version (if unstable) + if: github.event.ref == 'refs/heads/unstable' + run: | + echo "VERSION=latest" >> $GITHUB_ENV + echo "VERSION_SUFFIX=-unstable" >> $GITHUB_ENV + - name: Extract version (if tagged release) + if: startsWith(github.event.ref, 'refs/tags') + run: | + echo "VERSION=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_ENV + echo "VERSION_SUFFIX=" >> $GITHUB_ENV + outputs: + VERSION: ${{ env.VERSION }} + VERSION_SUFFIX: ${{ env.VERSION_SUFFIX }} - build-docker-single-arch: - name: build-docker-${{ matrix.binary }} - runs-on: ubuntu-22.04 - strategy: - matrix: - binary: [aarch64, x86_64] + build-docker-single-arch: + name: build-docker-${{ matrix.binary }} + runs-on: ubuntu-22.04 + strategy: + matrix: + binary: [aarch64, x86_64] - needs: [extract-version, build-html] - env: - # We need to enable experimental docker features in order to use `docker buildx` - DOCKER_CLI_EXPERIMENTAL: enabled - VERSION: ${{ needs.extract-version.outputs.VERSION }} - VERSION_SUFFIX: ${{ needs.extract-version.outputs.VERSION_SUFFIX }} - steps: - - uses: actions/checkout@v3 - - uses: docker/setup-qemu-action@v2 - - name: Dockerhub login - run: | - echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin - - name: Map aarch64 to arm64 short arch - if: startsWith(matrix.binary, 'aarch64') - run: echo "SHORT_ARCH=arm64" >> $GITHUB_ENV - - name: Map x86_64 to amd64 short arch - if: startsWith(matrix.binary, 'x86_64') - run: echo "SHORT_ARCH=amd64" >> $GITHUB_ENV; - - name: Download artifacts - uses: actions/download-artifact@v3 - with: - name: html - path: html/ - - name: Build Dockerfile and push - run: | - docker buildx build \ - --platform=linux/${SHORT_ARCH} \ - --file ./Dockerfile.release . \ - --tag ${IMAGE_NAME}:${VERSION}-${SHORT_ARCH}${VERSION_SUFFIX} \ - --provenance=false \ - --push - - build-docker-multiarch: - name: build-docker-multiarch - runs-on: ubuntu-22.04 - needs: [build-docker-single-arch, extract-version] - env: - # We need to enable experimental docker features in order to use `docker manifest` - DOCKER_CLI_EXPERIMENTAL: enabled - VERSION: ${{ needs.extract-version.outputs.VERSION }} - VERSION_SUFFIX: ${{ needs.extract-version.outputs.VERSION_SUFFIX }} - steps: - - name: Dockerhub login - run: | - echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin - - name: Create and push multiarch manifest - run: | - docker manifest create ${IMAGE_NAME}:${VERSION}${VERSION_SUFFIX} \ - --amend ${IMAGE_NAME}:${VERSION}-arm64${VERSION_SUFFIX} \ - --amend ${IMAGE_NAME}:${VERSION}-amd64${VERSION_SUFFIX}; - docker manifest push ${IMAGE_NAME}:${VERSION}${VERSION_SUFFIX} + needs: [extract-version] + env: + # We need to enable experimental docker features in order to use `docker buildx` + DOCKER_CLI_EXPERIMENTAL: enabled + VERSION: ${{ needs.extract-version.outputs.VERSION }} + VERSION_SUFFIX: ${{ needs.extract-version.outputs.VERSION_SUFFIX }} + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-qemu-action@v3 + - name: Dockerhub login + run: | + echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin + - name: Map aarch64 to arm64 short arch + if: startsWith(matrix.binary, 'aarch64') + run: echo "SHORT_ARCH=arm64" >> $GITHUB_ENV + - name: Map x86_64 to amd64 short arch + if: startsWith(matrix.binary, 'x86_64') + run: echo "SHORT_ARCH=amd64" >> $GITHUB_ENV; + - name: Build Dockerfile and push + run: | + docker buildx build \ + --platform=linux/${SHORT_ARCH} \ + --file ./Dockerfile . \ + --tag ${IMAGE_NAME}:${VERSION}-${SHORT_ARCH}${VERSION_SUFFIX} \ + --push + build-docker-multiarch: + name: build-docker-multiarch + runs-on: ubuntu-22.04 + needs: [build-docker-single-arch, extract-version] + env: + # We need to enable experimental docker features in order to use `docker manifest` + DOCKER_CLI_EXPERIMENTAL: enabled + VERSION: ${{ needs.extract-version.outputs.VERSION }} + VERSION_SUFFIX: ${{ needs.extract-version.outputs.VERSION_SUFFIX }} + steps: + - name: Dockerhub login + run: | + echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin + - name: Create and push multiarch manifest + run: | + docker manifest create ${IMAGE_NAME}:${VERSION}${VERSION_SUFFIX} \ + --amend ${IMAGE_NAME}:${VERSION}-arm64${VERSION_SUFFIX} \ + --amend ${IMAGE_NAME}:${VERSION}-amd64${VERSION_SUFFIX}; + docker manifest push ${IMAGE_NAME}:${VERSION}${VERSION_SUFFIX} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a57a97f4..20b129a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,14 +7,16 @@ on: env: REPO_NAME: ${{ github.repository_owner }}/siren - IMAGE_NAME: sigmaprime/siren + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/siren jobs: extract-version: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Extract version run: echo "VERSION=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT # Used for running without tags @@ -22,152 +24,20 @@ jobs: id: extract_version outputs: VERSION: ${{ steps.extract_version.outputs.VERSION }} - build: - name: Build Release - strategy: - matrix: - arch: [ - aarch64-unknown-linux-gnu, - x86_64-unknown-linux-gnu, - # Requires apple signature secrets - # x86_64-apple-darwin, - x86_64-windows - ] - include: - - arch: aarch64-unknown-linux-gnu - platform: ubuntu-latest - - arch: x86_64-unknown-linux-gnu - platform: ubuntu-latest - # Requires apple signature secrets - #- arch: x86_64-apple-darwin - # platform: macos-latest - - arch: x86_64-windows - platform: windows-2019 - - runs-on: ${{ matrix.platform }} - needs: extract-version - steps: - - name: Checkout sources - uses: actions/checkout@v3 - - name: Use node 18 - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Configure Yarn Timeout - run: yarn config set network-timeout 300000 - - # ============================== - # Windows & Mac dependencies - # ============================== - - # ============================== - # Builds - # ============================== - - name: Build Siren - run: make release - - - name: Move unsigned packages (*nix) - if: startsWith(matrix.arch, 'x86_64-windows') != true - run: | - mv out/make/zip/*/*/*.zip ./siren-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.zip - - - name: Move unsigned packages (windows) - if: startsWith(matrix.arch, 'x86_64-windows') - run: | - move out/make/zip/*/*/*.zip ./siren-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.zip - - # ======================================================================= - # Upload artifacts - # This is required to share artifacts between different jobs - # ======================================================================= - - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: siren-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.zip - path: ./siren-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.zip - - - sign: - name: Sign Release - runs-on: ubuntu-latest - needs: [extract-version, build] - strategy: - matrix: - arch: [ - aarch64-unknown-linux-gnu, - x86_64-unknown-linux-gnu, - # Requires apple signature secrets - # x86_64-apple-darwin, - x86_64-windows - ] - steps: - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: siren-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.zip - - - name: Move artifacts - run: | - mkdir artifacts - #find siren-*/ -type f -name "*.zip" -exec mv {} artifacts/ \; - mv siren-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.zip artifacts/ - ls -hal artifacts - - name: Configure GPG and create artifacts - env: - GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - run: | - export GPG_TTY=$(tty) - echo "$GPG_SIGNING_KEY" | gpg --batch --import - #for file in $(ls artifacts); do - # echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab artifacts/$file ; - #done - echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab artifacts/siren-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.zip - ls -hal artifacts - - - name: Upload signature (${{ matrix.arch }}) - uses: actions/upload-artifact@v3 - with: - name: siren-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.zip.asc - path: artifacts/siren-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.arch }}.zip.asc - - # - name: Upload signature (aarch64-unknown-linux-gnu) - # uses: actions/upload-artifact@v3 - # with: - # name: siren-${{ needs.extract-version.outputs.VERSION }}-aarch64-unknown-linux-gnu.zip.asc - # path: artifacts//siren-${{ needs.extract-version.outputs.VERSION }}-aarch64-unknown-linux-gnu.zip.asc - # - name: Upload signature (x86_64-unknown-linux-gnu) - # uses: actions/upload-artifact@v3 - # with: - # name: siren-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.zip.asc - # path: artifacts/siren-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.zip.asc - # - name: Upload signature (x86_64-windows) - # uses: actions/upload-artifact@v3 - # with: - # name: siren-${{ needs.extract-version.outputs.VERSION }}-x86_64-windows.zip.asc - # path: artifacts/siren-${{ needs.extract-version.outputs.VERSION }}-x86_64-windows.zip.asc draft-release: name: Draft Release - needs: [build, extract-version] + needs: [extract-version] runs-on: ubuntu-latest env: VERSION: ${{ needs.extract-version.outputs.VERSION }} steps: # This is necessary for generating the changelog. It has to come before "Download Artifacts" or else it deletes the artifacts. - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - # ============================== - # Download artifacts - # ============================== - - - name: Download artifacts - uses: actions/download-artifact@v3 - # ============================== # Create release draft # ============================== @@ -188,50 +58,45 @@ jobs: run: | body=$(cat <<- "ENDBODY" - + ## Release Checklist (DELETE ME) - + - [ ] Merge `unstable` -> `stable`. - - [ ] Ensure docker images are published (check `latest` and the version tag). + - [ ] Ensure docker images are published to `sigp` namespace (instructions below) - [ ] Prepare Discord post. - + + Someone with appropriate access rights should run these commands: + 0. sanity check: did the `docker` workflow complete correctly, are the images published under the `sigmaprime` namespace? + see: https://hub.docker.com/r/${{ env.DOCKER_USERNAME }}/siren/tags?name=${{ env.VERSION }} and https://hub.docker.com/r/${{ env.DOCKER_USERNAME }}/siren/tags?name=latest + 1. publish `latest`: + `docker manifest create sigp/siren:latest --amend sigmaprime/siren:latest-amd64 --amend sigmaprime/siren:latest-arm64` + `docker manifest push sigp/siren:latest` + 2. publish `${{ env.VERSION }}`: + `docker manifest create sigp/siren:${{ env.VERSION }} --amend sigmaprime/siren:${{ env.VERSION }}-amd64 --amend sigmaprime/siren:${{ env.VERSION }}-arm64` + `docker manifest push sigp/siren:${{ env.VERSION }}` + 3. verify: https://hub.docker.com/r/siren/siren/tags?name=${{ env.VERSION }} and https://hub.docker.com/r/sigp/siren/tags?name=latest + ## Summary - + Add a summary. - + ## Update Priority - + This table provides priorities for which classes of users should update particular components. - + |User Class |Beacon Node | Validator Client| --- | --- | --- |Staking Users| | | |Non-Staking Users| |---| - + ## All Changes - + ${{ steps.changelog.outputs.CHANGELOG }} - - ## Binaries - - [See pre-built binaries documentation.](https://lighthouse-book.sigmaprime.io/installation-binaries.html) - - The binaries are signed with Sigma Prime's PGP key: `15E66D941F697E28F49381F426416DC3F30674B0` - - | System | Architecture | Binary | PGP Signature | - |:---:|:---:|:---:|:---| - | | x86_64 | [siren-${{ env.VERSION }}-x86_64-apple-darwin.zip](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/siren-${{ env.VERSION }}-x86_64-apple-darwin.zip) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/siren-${{ env.VERSION }}-x86_64-apple-darwin.zip.asc) | - | | x86_64 | [siren-${{ env.VERSION }}-x86_64-unknown-linux-gnu.zip](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/siren-${{ env.VERSION }}-x86_64-unknown-linux-gnu.zip) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/siren-${{ env.VERSION }}-x86_64-unknown-linux-gnu.zip.asc) | - | | aarch64 | [siren-${{ env.VERSION }}-aarch64-unknown-linux-gnu.zip](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/siren-${{ env.VERSION }}-aarch64-unknown-linux-gnu.zip) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/siren-${{ env.VERSION }}-aarch64-unknown-linux-gnu.zip.asc) | - | | x86_64 | [siren-${{ env.VERSION }}-x86_64-windows.zip](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/siren-${{ env.VERSION }}-x86_64-windows.zip) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/siren-${{ env.VERSION }}-x86_64-windows.zip.asc) | - | | | | | - | **System** | **Option** | - | **Resource** | - | | Docker | [${{ env.VERSION }}](https://hub.docker.com/r/${{ env.IMAGE_NAME }}/tags?page=1&ordering=last_updated&name=${{ env.VERSION }}) | [${{ env.IMAGE_NAME }}](https://hub.docker.com/r/${{ env.IMAGE_NAME }}) | + + ## Docker Hub + https://hub.docker.com/r/sigp/siren/tags?name=${{ env.VERSION }} + ENDBODY ) - assets=() - for asset in ./siren-*.zip*; do - assets+=("-a" "$asset/$asset") - done tag_name="${{ env.VERSION }}" - echo "$body" | hub release create --draft "${assets[@]}" -F "-" "$tag_name" \ No newline at end of file + echo "$body" | gh release create --draft -F "-" "$tag_name" diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml deleted file mode 100644 index c94ba70d..00000000 --- a/.github/workflows/ui-tests.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: 'UI Tests' - -on: push - -jobs: - target-branch-check: - name: target-branch-check - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - steps: - - name: Check that the pull request is not targeting the stable branch - run: test ${{ github.base_ref }} != "stable" - # Run interaction and accessibility tests - build-storybook: - runs-on: ubuntu-latest - outputs: - deploy_storybook: ${{ steps.final.outputs.deploy_storybook }} - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: '14.x' - - name: Install dependencies - run: yarn - - name: Install Playwright - run: npx playwright install --with-deps - - name: Build Storybook - run: yarn build-storybook --quiet - - id: final - name: Serve Storybook and run tests - run: | - npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \ - "npx http-server storybook-static --port 6006 --silent" \ - "npx wait-on tcp:6006 && yarn test-storybook" - echo "::set-output name=deploy_storybook::storybookUrl" - # Run visual and composition tests with Chromatic - visual-and-composition: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # Required to retrieve git history - - name: Install dependencies - run: yarn - - id: publish - name: Publish to Chromatic - uses: chromaui/action@v1 - with: - # Grab this from the Chromatic manage page - projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - - name: print - run: echo ${{steps.publish.outputs.url}} diff --git a/.gitignore b/.gitignore index e7451e7d..089e036e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ .idea +certs + # testing /coverage @@ -31,3 +33,7 @@ build-storybook.log /local-testnet/bls_to_execution_changes /out .env + +.next +next-env.d.ts +dist diff --git a/.husky/pre-commit b/.husky/pre-commit index 71817319..86e2b7f6 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,21 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npx lint-staged && npx tsc --project tsconfig.json && yarn test +export PATH="/Users/rickimoore/lighthouse-ui:$PATH" + +# Run the Next.js linter with auto-fixing +npx next lint --fix + +# Run Jest tests using yarn +yarn test + +# Navigate to the backend directory +cd backend + +yarn test + +# Check if the Jest tests passed +if [ $? -ne 0 ]; then + echo "Jest tests failed. Aborting commit." + exit 1 +fi diff --git a/.prettierrc b/.prettierrc index 919cb979..61e4fa61 100644 --- a/.prettierrc +++ b/.prettierrc @@ -6,4 +6,4 @@ "trailingComma": "all", "jsxSingleQuote": true, "bracketSpacing": true -} \ No newline at end of file +} diff --git a/.storybook/main.js b/.storybook/main.js deleted file mode 100644 index c3321ba9..00000000 --- a/.storybook/main.js +++ /dev/null @@ -1,20 +0,0 @@ -const path = require('path'); - -module.exports = { - "stories": [ - "../src/**/*.stories.mdx", - "../src/**/*.stories.@(js|jsx|ts|tsx)" - ], - "addons": [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-interactions", - "@storybook/preset-create-react-app", - 'storybook-dark-mode/register', - 'storybook-tailwind-dark-mode' - ], - "framework": "@storybook/react", - "core": { - "builder": "@storybook/builder-webpack5" - }, -} \ No newline at end of file diff --git a/.storybook/preview.js b/.storybook/preview.js deleted file mode 100644 index e2df85e2..00000000 --- a/.storybook/preview.js +++ /dev/null @@ -1,35 +0,0 @@ -import '../src/i18n' -import '../src/global.css'; -import { themes } from '@storybook/theming' -import { - RecoilRoot -} from 'recoil'; - -export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - darkMode: { - dark: { - ...themes.dark, // copy existing values - appContentBg: '#1E1E1E', // override main story view frame - barBg: '#202020' // override top toolbar - } - } -} - -export const globalTypes = { - darkMode: true, -}; - -const withRecoil = (StoryFn) => ( - - {StoryFn()} - -); - -export const decorators = [withRecoil]; \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 33256115..c586dd49 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,42 @@ -ARG node_version=18 +ARG node_version=18.18 ARG node_image=node:${node_version} -# STAGE 1: builder FROM $node_image AS builder -COPY . /app/ +ENV NEXT_TELEMETRY_DISABLED=1 \ + NODE_ENV=development WORKDIR /app +COPY . /app/ + +RUN yarn --network-timeout 300000; \ + NODE_ENV=production yarn build +WORKDIR /app/backend +RUN yarn --network-timeout 300000; \ + NODE_ENV=production yarn build + +FROM alpine AS intermediate -ENV NODE_ENV=development +COPY ./docker-assets /app/docker-assets/ -# install (dev) deps -# on GitHub runners, timeouts occur in emulated containers -RUN yarn --network-timeout 300000 +COPY --from=builder /app/backend/package.json /app/backend/package.json +COPY --from=builder /app/backend/node_modules /app/backend/node_modules +COPY --from=builder /app/backend/dist /app/backend/dist + +COPY --from=builder /app/siren.js /app/package.json /app/ +COPY --from=builder /app/node_modules /app/node_modules +COPY --from=builder /app/public /app/public +COPY --from=builder /app/.next /app/.next + +FROM $node_image AS production ENV NODE_ENV=production -# build (prod) app -RUN yarn build +RUN npm install --global pm2; \ + apt update; \ + apt install -y nginx openssl curl ncat + +RUN rm /etc/nginx/sites-enabled/default; \ + ln -s /app/docker-assets/siren-http.conf /etc/nginx/conf.d/siren-http.conf -# STAGE 2 -FROM nginx:alpine AS production +COPY --from=intermediate /app /app/ -COPY --from=builder /app/build/ /usr/share/nginx/html/ +ENTRYPOINT /app/docker-assets/docker-entrypoint.sh diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index b67b0450..00000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,12 +0,0 @@ -ARG node_version=18 -ARG node_image=node:${node_version} -FROM $node_image - -ENV NODE_ENV=development - -EXPOSE 5000/tcp -COPY . /app/ -WORKDIR /app - -RUN yarn install -CMD ["yarn", "run", "dev"] \ No newline at end of file diff --git a/Dockerfile.release b/Dockerfile.release deleted file mode 100644 index 86e40597..00000000 --- a/Dockerfile.release +++ /dev/null @@ -1,3 +0,0 @@ -FROM nginx:alpine - -COPY html/ /usr/share/nginx/html/ diff --git a/Makefile b/Makefile deleted file mode 100644 index 366ab82a..00000000 --- a/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# Make the Siren App - - -# Default rule -# Builds the electron app and executes it -build: - yarn && yarn dev - -# Builds a development server -dev: - yarn && yarn start - -# Runs a docker production webserver -docker: - docker build -t siren . && docker run --rm -it --name siren -p 80:80 siren - -# Compile into a number of releases -release: - yarn && yarn build-all - -# Remove compiled artifacts -clean: - rm -r out diff --git a/README.md b/README.md index 4823a711..89e2233f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ and Validator Client. [Chat Badge]: https://img.shields.io/badge/chat-discord-%237289da [Chat Link]: https://discord.gg/jpqcHXPRVJ -[Book Status]:https://img.shields.io/badge/user--docs-unstable-informational +[Book Status]: https://img.shields.io/badge/user--docs-unstable-informational [Book Link]: https://lighthouse-book.sigmaprime.io/lighthouse-ui.html [stable]: https://github.com/sigp/siren/tree/stable [unstable]: https://github.com/sigp/siren/tree/unstable @@ -17,128 +17,43 @@ and Validator Client. The [Lighthouse Book](https://lighthouse-book.sigmaprime.io) contains information for users and developers. Specifically the [Lighthouse UI](https://lighthouse-book.sigmaprime.io/lighthouse-ui.html) section of the book. -## Building From Source - -### Requirements - -Building from source requires `Node v18` and `yarn`. - -### Building From Source - -The electron app can be built from source by first cloning the repository and -entering the directory: - -``` -$ git clone https://github.com/sigp/siren.git -$ cd siren -``` - -Once cloned, the electron app can be built and ran via the Makefile by: - -``` -$ make -``` - -alternatively it can be built via: - -``` -$ yarn -``` - -Once completed successfully the electron app can be run via: - -``` -$ yarn dev -``` +## Running Siren -### Running In The Browser +### Docker (Recommended) -#### Docker (Recommended) +Docker is the recommended way to run Siren. This will expose Siren as a webapp. -Docker is the recommended way to run a webserver that hosts Siren and can be -connected to via a web browser. We recommend this method as it established a -production-grade web-server to host the application. - -`docker` is required to be installed with the service running. - -The docker image can be built and run via the Makefile by running: -``` -$ make docker -``` - -Alternatively, to run with Docker, the image needs to be built. From the repository directory -run: -``` -$ docker build -t siren . -``` +Configuration is done through environment variables, the best way to get started is by copying `.env.example` to `.env` and editing the relevant sections (typically, this would at least include `BEACON_URL`, `VALIDATOR_URL` and `API_TOKEN`) Then to run the image: -``` -$ docker run --rm -ti --name siren -p 80:80 siren -``` - -This will open port 80 and allow your browser to connect. You can choose -another local port by modifying the command. For example `-p 8000:80` will open -port 8000. - -To view Siren, simply go to `http://localhost` in your web browser. - -# Running a Local Testnet -For development, one can spin up a local lighthouse testnet. This can be used -for the UI to connect to and retrieve real-time results from a local testnet. +`docker compose up` +or +`docker run --rm -ti --name siren -p 3443:443 --env-file $PWD/.env sigp/siren` -## Requirements +This will open port 3443 and allow your browser to connect. -In order to run a local lighthouse network, lighthouse needs to be installed on -the system. For detailed instructions see the [Lighthouse Book](https://lighthouse-book.sigmaprime.io/). -Both `lighthouse` and `lcli` are required to be installed. This can be done by -cloning the Lighthouse repository, entering the cloned repository and running: +To start Siren, visit `https://localhost:3443` in your web browser (ignore the certificate warning). -```bash -$ make -$ make install-lcli -``` +Advanced users can mount their own certificate (the config expects 3 files: `/certs/cert.pem` `/certs/key.pem` `/certs/key.pass`) -note: you need a version of lcli that includes [these](https://github.com/sigp/lighthouse/pull/3807) changes - -`ganache` is also required to be installed. This can be installed via `npm` or via the OS. If using `npm` it can be installed as: -``` -$ npm install ganache --global -``` - -## Starting the Testnet - -To start a local testnet, move into the `local-testnet` directory. Then run: -```bash -./start_local_testnet.sh genesis.json -``` - -This will spin up both a validator client and a beacon node. These will run in -the background and can be accessed via their local http APIs. - -## Stopping the Testnet +## Building From Source -A running local testnet can be stopped by running: +### Docker -```bash -./stop_local_testnet.sh -``` +The docker image can be built with the following command: +`docker build -f Dockerfile -t siren .` -## Configuring the Testnet +### Building locally -The default settings should be sufficient for a development network useful for -testing the UI. However various configurations can be modified by modifying the -`vars.env` file. +To build from source, ensure that your system has `Node v18.18` and `yarn` installed. Start by configuring your environment variables. The recommended approach is to duplicate the `.env.example` file, rename it to `.env`, and modify the necessary settings. Essential variables typically include `BEACON_URL`, `VALIDATOR_URL`, and `API_TOKEN`. -## Creating a new testnet +#### Build and run the backend +Navigate to the backend directory `cd backend`. Install all required Node packages by running `yarn`. Once the installation is complete, compile the backend with `yarn build`. Deploy the backend in a production environment, `yarn start:production`. This ensures optimal performance. -The data for a previously run testnet is stored at -`./local-testnet/testnet-data` (assuming the scripts were run inside the -`local-testnet` directory. Simply removing this directory and its -subdirectories will create a new testnet when running these commands again. -## Logs and Errors +#### Build and run the frontend +After initializing the backend, return to the root directory. Install all frontend dependencies by executing `yarn`. Build the frontend using `yarn build`. Start the frontend production server with `yarn start`. -Logs and errors can be found in the `./local-testnet/testnet-data` directory. +This will allow you to access siren at `http://localhost:3000` by default. diff --git a/app/Main.tsx b/app/Main.tsx new file mode 100644 index 00000000..f3947730 --- /dev/null +++ b/app/Main.tsx @@ -0,0 +1,163 @@ +'use client'; + +import axios from 'axios'; +import Cookies from 'js-cookie'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import AppDescription from '../src/components/AppDescription/AppDescription'; +import AuthPrompt from '../src/components/AuthPrompt/AuthPrompt'; +import ConfigModal from '../src/components/ConfigModal/ConfigModal'; +import LoadingSpinner from '../src/components/LoadingSpinner/LoadingSpinner'; +import Typography from '../src/components/Typography/Typography'; +import VersionModal from '../src/components/VersionModal/VersionModal'; +import { REQUIRED_VALIDATOR_VERSION } from '../src/constants/constants'; +import { UiMode } from '../src/constants/enums'; +import useLocalStorage from '../src/hooks/useLocalStorage'; +import { ToastType } from '../src/types'; +import displayToast from '../utilities/displayToast'; +import formatSemanticVersion from '../utilities/formatSemanticVersion'; +import isExpiredToken from '../utilities/isExpiredToken'; +import isRequiredVersion from '../utilities/isRequiredVersion'; + +const Main = () => { + const { t } = useTranslation() + const router = useRouter() + const searchParams = useSearchParams() + const redirect = searchParams.get('redirect') + const [isLoading, setLoading] = useState(false) + const [step] = useState(1) + const [isReady, setReady] = useState(false) + const [isVersionError, setVersionError] = useState(false) + const [sessionToken, setToken] = useState(Cookies.get('session-token')) + const [, setUsername] = useLocalStorage('username', 'Keeper') + const [healthCheck] = useLocalStorage('health-check', false) + + const [beaconNodeVersion, setBeaconVersion] = useState('') + const [lighthouseVersion, setLighthouseVersion] = useState('') + + useEffect(() => { + if(sessionToken) { + if(isExpiredToken(sessionToken)) { + setToken(undefined) + return + } + + (async () => { + try { + const config = { + headers: { + Authorization: `Bearer ${sessionToken}` + } + } + + const [beaconResults, lightResults] = await Promise.all([ + axios.get('/api/beacon-version', config), + axios.get('/api/lighthouse-version', config) + ]) + + setBeaconVersion(beaconResults.data.version) + setLighthouseVersion(lightResults.data.version) + + setReady(true) + + } catch (e) { + setReady(true) + console.error(e) + } + })() + } + }, [sessionToken]) + + useEffect(() => { + if(beaconNodeVersion && lighthouseVersion) { + + if (!isRequiredVersion(lighthouseVersion, REQUIRED_VALIDATOR_VERSION)) { + setVersionError(true) + return + } + + let nextRoute = '/setup/health-check' + + if(healthCheck) { + nextRoute = '/dashboard' + } + + router.push(redirect || nextRoute) + } + }, [beaconNodeVersion, lighthouseVersion, router, redirect]) + + const configError = !beaconNodeVersion || !lighthouseVersion + const vcVersion = beaconNodeVersion + ? formatSemanticVersion(beaconNodeVersion as string) + : undefined + + const storeSessionCookie = async (password: string, username: string) => { + try { + setLoading(true) + setUsername(username) + const {status, data} = await axios.post('/api/authenticate', {password}) + const token = data.token; + setLoading(false) + + if(status === 200) { + setToken(token) + Cookies.set('session-token', token) + } + + } catch (e: any) { + setLoading(false) + displayToast(t(e.response.data.error as string), ToastType.ERROR) + } + } + + return ( +
+ + {vcVersion && ( + + )} + +
+
+ +
+
+
+ + {`${t('initScreen.initializing')}...`} + +
+ {step >= 0 && ( + <> + + {`${t('initScreen.fetchingEndpoints')}...`} + + + {`${t('initScreen.connectingBeacon')}...`} + + + {`${t('initScreen.connectingValidator')}...`} + + + )} + {step > 1 && ( + + {`${t('initScreen.fetchBeaconSync')}...`} + + )} + + - - - + +
+
+
+ +
+
+ ) +} + +export default Main diff --git a/app/Providers.tsx b/app/Providers.tsx new file mode 100644 index 00000000..5890d212 --- /dev/null +++ b/app/Providers.tsx @@ -0,0 +1,28 @@ +'use client' + +import React, { FC, ReactElement } from 'react' +import { QueryClient, QueryClientProvider } from 'react-query' +import { ToastContainer } from 'react-toastify' +import { RecoilRoot } from 'recoil' +import 'react-tooltip/dist/react-tooltip.css' +import 'react-toastify/dist/ReactToastify.min.css' +import 'rodal/lib/rodal.css' + +const queryClient = new QueryClient() + +export interface ProviderProps { + children: ReactElement | ReactElement[] +} + +const Providers: FC = ({ children }) => { + return ( + + + {children} + + + + ) +} + +export default Providers diff --git a/app/Wrapper.tsx b/app/Wrapper.tsx new file mode 100644 index 00000000..fd6226fb --- /dev/null +++ b/app/Wrapper.tsx @@ -0,0 +1,16 @@ +'use client' + +import React from 'react' +import Main from './Main' +import Providers from './Providers' +import '../src/i18n' + +const Wrapper = () => { + return ( + +
+ + ) +} + +export default Wrapper diff --git a/app/api/authenticate/route.ts b/app/api/authenticate/route.ts new file mode 100644 index 00000000..b8e13496 --- /dev/null +++ b/app/api/authenticate/route.ts @@ -0,0 +1,26 @@ +import axios from 'axios'; +import { NextResponse } from 'next/server'; + +const backendUrl = process.env.BACKEND_URL + +export async function POST(req: Request) { + try { + const {password} = await req.json(); + const res = await axios.post(`${backendUrl}/authenticate`, {password}); + + if(!res?.data) { + return NextResponse.json({ error: 'authPrompt.unableToReach' }, { status: 500 }) + } + + const token = res.data.access_token + + return NextResponse.json({token}, {status: 200}) + } catch (error: any) { + let message = error?.response?.data?.message + + if(!message) { + message = 'authPrompt.defaultErrorMessage' + } + return NextResponse.json({ error: message }, { status: 500 }) + } +} \ No newline at end of file diff --git a/app/api/beacon-heartbeat/route.ts b/app/api/beacon-heartbeat/route.ts new file mode 100644 index 00000000..c3434f33 --- /dev/null +++ b/app/api/beacon-heartbeat/route.ts @@ -0,0 +1,20 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchBeaconNodeVersion } from '../config' + +const errorMessage = 'Failed to maintain beacon heartbeat' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const { version } = await fetchBeaconNodeVersion(token) + + if (version) { + return NextResponse.json({ data: 'success' }, { status: 200 }) + } + + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } catch (error) { + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +} diff --git a/app/api/beacon-version/route.ts b/app/api/beacon-version/route.ts new file mode 100644 index 00000000..ac5e8fc4 --- /dev/null +++ b/app/api/beacon-version/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchBeaconNodeVersion } from '../config'; + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const {version} = await fetchBeaconNodeVersion(token) + return NextResponse.json({ version }) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch beacon version' }, { status: 500 }) + } +} diff --git a/app/api/beacon.ts b/app/api/beacon.ts new file mode 100644 index 00000000..f3556045 --- /dev/null +++ b/app/api/beacon.ts @@ -0,0 +1,26 @@ +import fetchFromApi from '../../utilities/fetchFromApi' + +const backendUrl = process.env.BACKEND_URL + +export const fetchNodeHealth = async (token: string) => + await fetchFromApi(`${backendUrl}/node/health`, token) +export const fetchSyncData = async (token: string) => + await fetchFromApi(`${backendUrl}/beacon/sync`, token) +export const fetchInclusionRate = async (token: string) => + await fetchFromApi(`${backendUrl}/beacon/inclusion`, token) +export const fetchPeerData = async (token: string) => + await fetchFromApi(`${backendUrl}/beacon/peer`, token) +export const fetchBeaconSpec = async (token: string) => await fetchFromApi(`${backendUrl}/beacon/spec`, token) +export const fetchValidatorCountData = async (token: string) => + await fetchFromApi(`${backendUrl}/beacon/validator-count`, token) +export const fetchProposerDuties = async (token: string) => fetchFromApi(`${backendUrl}/beacon/proposer-duties`, token) +export const broadcastBlsChange = async (data: any, token: string) => + await fetchFromApi(`${backendUrl}/beacon/bls-execution`, token, { + method: 'POST', + body: JSON.stringify(data) + }) +export const submitSignedExit = async (data: any, token: string) => + await fetchFromApi(`${backendUrl}/beacon/execute-exit`, token,{ + method: 'POST', + body: JSON.stringify(data) + }) diff --git a/app/api/bls-execution/route.ts b/app/api/bls-execution/route.ts new file mode 100644 index 00000000..db5d6e38 --- /dev/null +++ b/app/api/bls-execution/route.ts @@ -0,0 +1,21 @@ +import { NextResponse } from 'next/server'; +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { broadcastBlsChange } from '../beacon'; + +export async function POST(req: Request) { + try { + const data = await req.json(); + const token = getReqAuthToken(req) + await broadcastBlsChange(data, token) + + return NextResponse.json('done', {status: 200}) + } catch (error) { + let status = 500 + let message = 'Unknown error occurred...' + if (error instanceof Error && error.message.includes('401')) { + status = 401; + message = error.message + } + return NextResponse.json({ error: message }, { status }) + } +} \ No newline at end of file diff --git a/app/api/config.ts b/app/api/config.ts new file mode 100644 index 00000000..b1f99759 --- /dev/null +++ b/app/api/config.ts @@ -0,0 +1,11 @@ +import fetchFromApi from '../../utilities/fetchFromApi' + +const backendUrl = process.env.BACKEND_URL + +export const fetchBeaconNodeVersion = async (token: string) => + await fetchFromApi(`${backendUrl}/beacon/version`, token) +export const fetchValidatorAuthKey = async (token: string) => + await fetchFromApi(`${backendUrl}/validator/auth-key`, token) +export const fetchValidatorVersion = async (token: string) => + await fetchFromApi(`${backendUrl}/validator/version`, token) +export const fetchGenesisData = async (token: string) => await fetchFromApi(`${backendUrl}/beacon/genesis`, token) diff --git a/app/api/dismiss-log/[index]/route.ts b/app/api/dismiss-log/[index]/route.ts new file mode 100644 index 00000000..5dfa1eeb --- /dev/null +++ b/app/api/dismiss-log/[index]/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../../utilities/getReqAuthToken'; +import { dismissLogAlert } from '../../logs'; + +export async function PUT(req: Request, context: any) { + try { + const { index } = context.params; + const token = getReqAuthToken(req); + + if (!index) { + return NextResponse.json({ error: 'No log index found' }, { status: 400 }); + } + + if (!token) { + return NextResponse.json({ error: 'Authentication token is missing' }, { status: 401 }); + } + + const data = await dismissLogAlert(token, index); + return NextResponse.json(data); + } catch (error: any) { + console.error('Error dismissing log alert:', error); + const errorMessage = error.message || 'Failed to dismiss log alert'; + return NextResponse.json({ error: errorMessage }, { status: 500 }); + } +} diff --git a/app/api/execute-validator-exit/route.ts b/app/api/execute-validator-exit/route.ts new file mode 100644 index 00000000..94088882 --- /dev/null +++ b/app/api/execute-validator-exit/route.ts @@ -0,0 +1,15 @@ +import { NextResponse } from 'next/server'; +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { submitSignedExit } from '../beacon'; + +export async function POST(req: Request) { + try { + const data = await req.json(); + const token = getReqAuthToken(req) + + const res = await submitSignedExit(data, token) + return NextResponse.json(res, {status: 200}) + } catch (error) { + return NextResponse.json({ error }, { status: 500 }) + } +} \ No newline at end of file diff --git a/app/api/lighthouse-version/route.ts b/app/api/lighthouse-version/route.ts new file mode 100644 index 00000000..07e94e60 --- /dev/null +++ b/app/api/lighthouse-version/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchValidatorVersion } from '../config' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const { version } = await fetchValidatorVersion(token) + return NextResponse.json({ version }) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch lighthouse version' }, { status: 500 }) + } +} diff --git a/app/api/logs.ts b/app/api/logs.ts new file mode 100644 index 00000000..8d27d496 --- /dev/null +++ b/app/api/logs.ts @@ -0,0 +1,5 @@ +import fetchFromApi from '../../utilities/fetchFromApi'; + +const backendUrl = process.env.BACKEND_URL +export const fetchLogMetrics = async (token: string) => fetchFromApi(`${backendUrl}/logs/metrics`, token) +export const dismissLogAlert = async (token: string, index: string) => fetchFromApi(`${backendUrl}/logs/dismiss/${index}`, token) \ No newline at end of file diff --git a/app/api/node-health/route.ts b/app/api/node-health/route.ts new file mode 100644 index 00000000..79f45893 --- /dev/null +++ b/app/api/node-health/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchNodeHealth } from '../beacon' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const data = await fetchNodeHealth(token) + return NextResponse.json(data) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch node health data' }, { status: 500 }) + } +} diff --git a/app/api/node-sync/route.ts b/app/api/node-sync/route.ts new file mode 100644 index 00000000..0425320a --- /dev/null +++ b/app/api/node-sync/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchSyncData } from '../beacon' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const data = await fetchSyncData(token) + return NextResponse.json(data) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch sync status' }, { status: 500 }) + } +} diff --git a/app/api/peer-data/route.ts b/app/api/peer-data/route.ts new file mode 100644 index 00000000..4261fd89 --- /dev/null +++ b/app/api/peer-data/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchPeerData } from '../beacon' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const data = await fetchPeerData(token) + return NextResponse.json(data) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch peer data' }, { status: 500 }) + } +} diff --git a/app/api/priority-logs/route.ts b/app/api/priority-logs/route.ts new file mode 100644 index 00000000..d64d0247 --- /dev/null +++ b/app/api/priority-logs/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchLogMetrics } from '../logs'; + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const data = await fetchLogMetrics(token) + return NextResponse.json(data) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch priority logs' }, { status: 500 }) + } +} diff --git a/app/api/sign-validator-exit/route.ts b/app/api/sign-validator-exit/route.ts new file mode 100644 index 00000000..349f4c7f --- /dev/null +++ b/app/api/sign-validator-exit/route.ts @@ -0,0 +1,21 @@ +import { NextResponse } from 'next/server'; +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { signVoluntaryExit } from '../validator'; + +export async function POST(req: Request) { + try { + const data = await req.json(); + const token = getReqAuthToken(req) + const res = await signVoluntaryExit(data, token) + + return NextResponse.json(res, {status: 200}) + } catch (error) { + let status = 500 + let message = 'Unknown error occurred...' + if (error instanceof Error && error.message.includes('401')) { + status = 401; + message = error.message + } + return NextResponse.json({ error: message }, { status }) + } +} \ No newline at end of file diff --git a/app/api/update-graffiti/route.ts b/app/api/update-graffiti/route.ts new file mode 100644 index 00000000..d68e5884 --- /dev/null +++ b/app/api/update-graffiti/route.ts @@ -0,0 +1,15 @@ +import { NextResponse } from 'next/server'; +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { updateValGraffiti } from '../validator'; + +export async function PUT(req: Request) { + try { + const data = await req.json(); + const token = getReqAuthToken(req) + + const res = await updateValGraffiti(token, data) + return NextResponse.json(res, {status: 200}) + } catch (error) { + return NextResponse.json({ error }, { status: 500 }) + } +} \ No newline at end of file diff --git a/app/api/validator-cache/route.ts b/app/api/validator-cache/route.ts new file mode 100644 index 00000000..37c95793 --- /dev/null +++ b/app/api/validator-cache/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchValCaches } from '../validator' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const data = await fetchValCaches(token) + return NextResponse.json(data) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch validator cache' }, { status: 500 }) + } +} diff --git a/app/api/validator-duties/route.ts b/app/api/validator-duties/route.ts new file mode 100644 index 00000000..f5fd1b05 --- /dev/null +++ b/app/api/validator-duties/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchProposerDuties } from '../beacon'; + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const data = await fetchProposerDuties(token) + return NextResponse.json(data) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch proposer data' }, { status: 500 }) + } +} diff --git a/app/api/validator-graffiti/[index]/route.ts b/app/api/validator-graffiti/[index]/route.ts new file mode 100644 index 00000000..38539e26 --- /dev/null +++ b/app/api/validator-graffiti/[index]/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../../utilities/getReqAuthToken'; +import { fetchValGraffiti } from '../../validator'; + +export async function GET(req: Request, context: any) { + try { + const { index } = context.params; + const token = getReqAuthToken(req) + + if (!index) { + return NextResponse.json({ error: 'No validator index found' }, { status: 400 }); + } + + if (!token) { + return NextResponse.json({ error: 'Authentication token is missing' }, { status: 401 }); + } + + const data = await fetchValGraffiti(token, index) + return NextResponse.json(data) + } catch (error: any) { + console.error('Error fetching val graffiti:', error); + const errorMessage = error.message || 'Failed to fetch validator graffiti'; + return NextResponse.json({ error: errorMessage }, { status: 500 }); + } +} diff --git a/app/api/validator-heartbeat/route.ts b/app/api/validator-heartbeat/route.ts new file mode 100644 index 00000000..54bad2e5 --- /dev/null +++ b/app/api/validator-heartbeat/route.ts @@ -0,0 +1,20 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchValidatorAuthKey } from '../config' + +const errorMessage = 'Failed to maintain validator heartbeat' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const {token_path} = await fetchValidatorAuthKey(token) + + if (token_path) { + return NextResponse.json({ data: 'success' }, { status: 200 }) + } + + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } catch (error) { + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +} diff --git a/app/api/validator-inclusion/route.ts b/app/api/validator-inclusion/route.ts new file mode 100644 index 00000000..fd2be35a --- /dev/null +++ b/app/api/validator-inclusion/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchInclusionRate } from '../beacon' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const data = await fetchInclusionRate(token) + return NextResponse.json(data) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch validator inclusion data' }, { status: 500 }) + } +} diff --git a/app/api/validator-metrics/[[...index]]/route.ts b/app/api/validator-metrics/[[...index]]/route.ts new file mode 100644 index 00000000..2d87a939 --- /dev/null +++ b/app/api/validator-metrics/[[...index]]/route.ts @@ -0,0 +1,21 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../../utilities/getReqAuthToken'; +import { fetchValMetrics } from '../../validator'; + +export async function GET(req: Request, context: any) { + try { + const { index } = context.params; + const token = getReqAuthToken(req) + + if (!token) { + return NextResponse.json({ error: 'Authentication token is missing' }, { status: 401 }); + } + + const data = await fetchValMetrics(token, index) + return NextResponse.json(data) + } catch (error: any) { + console.error('Error fetching val metrics:', error); + const errorMessage = error.message || 'Failed to fetch validator metrics'; + return NextResponse.json({ error: errorMessage }, { status: 500 }); + } +} diff --git a/app/api/validator-network/route.ts b/app/api/validator-network/route.ts new file mode 100644 index 00000000..88ddfd11 --- /dev/null +++ b/app/api/validator-network/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchValidatorCountData } from '../beacon' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const data = await fetchValidatorCountData(token) + return NextResponse.json(data) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch validator data' }, { status: 500 }) + } +} diff --git a/app/api/validator-states/route.ts b/app/api/validator-states/route.ts new file mode 100644 index 00000000..63069782 --- /dev/null +++ b/app/api/validator-states/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server' +import getReqAuthToken from '../../../utilities/getReqAuthToken'; +import { fetchValStates } from '../validator' + +export async function GET(req: Request) { + try { + const token = getReqAuthToken(req) + const data = await fetchValStates(token) + return NextResponse.json(data) + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch validator state data' }, { status: 500 }) + } +} diff --git a/app/api/validator.ts b/app/api/validator.ts new file mode 100644 index 00000000..c0297d72 --- /dev/null +++ b/app/api/validator.ts @@ -0,0 +1,22 @@ +import fetchFromApi from '../../utilities/fetchFromApi' + +const backendUrl = process.env.BACKEND_URL + +export const fetchValStates = async (token: string,) => + await fetchFromApi(`${backendUrl}/validator/states`, token) +export const fetchValCaches = async (token: string,) => + await fetchFromApi(`${backendUrl}/validator/caches`, token) +export const fetchValMetrics = async (token: string, index?: string | null) => + await fetchFromApi(`${backendUrl}/validator/metrics${index ? `/${index}` : ''}`, token) +export const signVoluntaryExit = async (data: any, token: string) => + await fetchFromApi(`${backendUrl}/validator/sign-exit`, token,{ + method: 'POST', + body: JSON.stringify(data) + }) +export const fetchValGraffiti = async (token: string, index: string) => + await fetchFromApi(`${backendUrl}/validator/graffiti/${index}`, token) +export const updateValGraffiti = async (token: string, data: any) => + await fetchFromApi(`${backendUrl}/validator/graffiti`, token,{ + method: 'PUT', + body: JSON.stringify(data) + }) \ No newline at end of file diff --git a/app/dashboard/Main.tsx b/app/dashboard/Main.tsx new file mode 100644 index 00000000..d3cff9c2 --- /dev/null +++ b/app/dashboard/Main.tsx @@ -0,0 +1,264 @@ +'use client' + +import React, { FC, useEffect } from 'react'; +import { useTranslation } from 'react-i18next' +import { useSetRecoilState } from 'recoil' +import pckJson from '../../package.json' +import AccountEarning from '../../src/components/AccountEarnings/AccountEarning' +import AppGreeting from '../../src/components/AppGreeting/AppGreeting' +import DashboardWrapper from '../../src/components/DashboardWrapper/DashboardWrapper' +import DiagnosticTable from '../../src/components/DiagnosticTable/DiagnosticTable' +import NetworkStats from '../../src/components/NetworkStats/NetworkStats' +import ValidatorBalances from '../../src/components/ValidatorBalances/ValidatorBalances' +import ValidatorTable from '../../src/components/ValidatorTable/ValidatorTable' +import { ALERT_ID, CoinbaseExchangeRateUrl } from '../../src/constants/constants' +import useDiagnosticAlerts from '../../src/hooks/useDiagnosticAlerts' +import useLocalStorage from '../../src/hooks/useLocalStorage' +import useNetworkMonitor from '../../src/hooks/useNetworkMonitor' +import useSWRPolling from '../../src/hooks/useSWRPolling' +import { exchangeRates, proposerDuties } from '../../src/recoil/atoms'; +import { LogMetric, ProposerDuty, StatusColor } from '../../src/types'; +import { BeaconNodeSpecResults, SyncData } from '../../src/types/beacon' +import { Diagnostics, PeerDataResults } from '../../src/types/diagnostic' +import { ValidatorCache, ValidatorInclusionData, ValidatorInfo } from '../../src/types/validator' +import formatUniqueObjectArray from '../../utilities/formatUniqueObjectArray'; + +export interface MainProps { + initNodeHealth: Diagnostics + initSyncData: SyncData + bnVersion: string + lighthouseVersion: string + beaconSpec: BeaconNodeSpecResults + initValStates: ValidatorInfo[] + genesisTime: number + initPeerData: PeerDataResults + initValCaches: ValidatorCache + initInclusionRate: ValidatorInclusionData + initProposerDuties: ProposerDuty[] + initLogMetrics: LogMetric +} + +const Main: FC = (props) => { + const { + initNodeHealth, + initSyncData, + initValStates, + initValCaches, + initPeerData, + initInclusionRate, + beaconSpec, + bnVersion, + lighthouseVersion, + genesisTime, + initProposerDuties, + initLogMetrics, + } = props + + const { t } = useTranslation() + + const { SECONDS_PER_SLOT, SLOTS_PER_EPOCH } = beaconSpec + const { version } = pckJson + const { updateAlert, storeAlert, removeAlert } = useDiagnosticAlerts() + const [username] = useLocalStorage('username', 'Keeper') + const setExchangeRate = useSetRecoilState(exchangeRates) + const setDuties = useSetRecoilState(proposerDuties) + + const { isValidatorError, isBeaconError } = useNetworkMonitor() + + const networkError = isValidatorError || isBeaconError + const slotInterval = SECONDS_PER_SLOT * 1000 + const halfEpochInterval = ((Number(SECONDS_PER_SLOT) * Number(SLOTS_PER_EPOCH)) / 2) * 1000 + + const { data: exchangeData } = useSWRPolling(CoinbaseExchangeRateUrl, { + refreshInterval: 60 * 1000, + networkError, + }) + + const { data: peerData } = useSWRPolling('/api/peer-data', { + refreshInterval: slotInterval, + fallbackData: initPeerData, + networkError, + }) + const { data: validatorCache } = useSWRPolling('/api/validator-cache', { + refreshInterval: slotInterval / 2, + fallbackData: initValCaches, + networkError, + }) + const { data: validatorStates } = useSWRPolling('/api/validator-states', { + refreshInterval: slotInterval, + fallbackData: initValStates, + networkError, + }) + const { data: nodeHealth } = useSWRPolling('/api/node-health', { + refreshInterval: 6000, + fallbackData: initNodeHealth, + networkError, + }) + const { data: syncData } = useSWRPolling('/api/node-sync', { + refreshInterval: slotInterval, + fallbackData: initSyncData, + networkError, + }) + const { data: valInclusion } = useSWRPolling('/api/validator-inclusion', { + refreshInterval: slotInterval, + fallbackData: initInclusionRate, + networkError, + }) + + const { data: valDuties } = useSWRPolling('/api/validator-duties', { + refreshInterval: halfEpochInterval, + fallbackData: initProposerDuties, + networkError, + }) + + const { data: logMetrics } = useSWRPolling('/api/priority-logs', { + refreshInterval: slotInterval / 2, + fallbackData: initLogMetrics, + networkError, + }) + + const { beaconSync, executionSync } = syncData + const { isSyncing } = beaconSync + const { isReady } = executionSync + const { connected } = peerData + const { natOpen } = nodeHealth + const warningCount = logMetrics.warningLogs?.length || 0 + + useEffect(() => { + setDuties(prev => formatUniqueObjectArray([...prev, ...valDuties])) + }, [valDuties]) + + useEffect(() => { + if (exchangeData) { + const { rates } = exchangeData.data + setExchangeRate({ + rates, + currencies: Object.keys(rates), + }) + } + }, [t, exchangeData, setExchangeRate]) + + useEffect(() => { + if (!isSyncing) { + removeAlert(ALERT_ID.BEACON_SYNC) + return + } + + storeAlert({ + id: ALERT_ID.BEACON_SYNC, + severity: StatusColor.WARNING, + subText: t('fair'), + message: t('alertMessages.beaconNotSync'), + }) + }, [t, isSyncing, storeAlert, removeAlert]) + + useEffect(() => { + if (isReady) { + removeAlert(ALERT_ID.VALIDATOR_SYNC) + return + } + + storeAlert({ + id: ALERT_ID.VALIDATOR_SYNC, + severity: StatusColor.WARNING, + subText: t('fair'), + message: t('alertMessages.ethClientNotSync'), + }) + }, [t, isReady, storeAlert, removeAlert]) + + useEffect(() => { + if (connected <= 50) { + if (connected <= 20) { + updateAlert({ + message: t('alert.peerCountLow', { type: t('alert.type.nodeValidator') }), + subText: t('poor'), + severity: StatusColor.ERROR, + id: ALERT_ID.PEER_COUNT, + }) + return + } + updateAlert({ + message: t('alert.peerCountMedium', { type: t('alert.type.nodeValidator') }), + subText: t('fair'), + severity: StatusColor.WARNING, + id: ALERT_ID.PEER_COUNT, + }) + } + }, [t, connected, updateAlert]) + + useEffect(() => { + if (natOpen) { + removeAlert(ALERT_ID.NAT) + return + } + + storeAlert({ + id: ALERT_ID.NAT, + message: t('alert.natClosedStatus', { type: t('alert.type.network') }), + subText: t('poor'), + severity: StatusColor.ERROR, + }) + }, [t, natOpen, storeAlert, removeAlert]) + + useEffect(() => { + if (warningCount > 5) { + storeAlert({ + id: ALERT_ID.WARNING_LOG, + message: t('alertMessages.excessiveWarningLogs'), + severity: StatusColor.WARNING, + subText: t('fair'), + }) + + return + } + + removeAlert(ALERT_ID.WARNING_LOG) + }, [warningCount, storeAlert, removeAlert]) + + return ( + +
+
+ + + +
+
+ + + +
+
+
+ ) +} + +export default Main diff --git a/app/dashboard/Wrapper.tsx b/app/dashboard/Wrapper.tsx new file mode 100644 index 00000000..27a2e247 --- /dev/null +++ b/app/dashboard/Wrapper.tsx @@ -0,0 +1,16 @@ +'use client' + +import React, { FC } from 'react' +import Providers from '../Providers' +import Main, { MainProps } from './Main' +import '../../src/i18n' + +const Wrapper: FC = (props) => { + return ( + +
+ + ) +} + +export default Wrapper diff --git a/app/dashboard/logs/Content.tsx b/app/dashboard/logs/Content.tsx new file mode 100644 index 00000000..fba6b102 --- /dev/null +++ b/app/dashboard/logs/Content.tsx @@ -0,0 +1,13 @@ +import { FC } from 'react' +import SSELogProvider from '../../../src/components/SSELogProvider/SSELogProvider' +import Main, { MainProps } from './Main' + +const Content: FC = (props) => { + return ( + +
+ + ) +} + +export default Content diff --git a/app/dashboard/logs/Main.tsx b/app/dashboard/logs/Main.tsx new file mode 100644 index 00000000..b0a4640e --- /dev/null +++ b/app/dashboard/logs/Main.tsx @@ -0,0 +1,88 @@ +import { FC, useEffect, useMemo, useState } from 'react'; +import DashboardWrapper from '../../../src/components/DashboardWrapper/DashboardWrapper' +import LogControls from '../../../src/components/LogControls/LogControls' +import LogDisplay from '../../../src/components/LogDisplay/LogDisplay' +import { OptionType } from '../../../src/components/SelectDropDown/SelectDropDown' +import useNetworkMonitor from '../../../src/hooks/useNetworkMonitor' +import useSWRPolling from '../../../src/hooks/useSWRPolling' +import { LogMetric, LogType } from '../../../src/types'; +import { BeaconNodeSpecResults, SyncData } from '../../../src/types/beacon' +import { Diagnostics } from '../../../src/types/diagnostic' + +export interface MainProps { + initNodeHealth: Diagnostics + beaconSpec: BeaconNodeSpecResults + initSyncData: SyncData + initLogMetrics: LogMetric +} + +const Main: FC = ({ initSyncData, beaconSpec, initNodeHealth, initLogMetrics }) => { + const { SECONDS_PER_SLOT } = beaconSpec + const { isValidatorError, isBeaconError } = useNetworkMonitor() + const networkError = isValidatorError || isBeaconError + const slotInterval = SECONDS_PER_SLOT * 1000 + + const [logType, selectType] = useState(LogType.VALIDATOR) + const [isLoading, setLoading] = useState(true) + + useEffect(() => { + setTimeout(() => { + setLoading(false) + }, 500) + }, []) + + const { data: syncData } = useSWRPolling('/api/node-sync', { + refreshInterval: slotInterval, + fallbackData: initSyncData, + networkError, + }) + const { data: nodeHealth } = useSWRPolling('/api/node-health', { + refreshInterval: 6000, + fallbackData: initNodeHealth, + networkError, + }) + + const { data: logMetrics } = useSWRPolling('/api/priority-logs', { + refreshInterval: slotInterval / 2, + fallbackData: initLogMetrics, + networkError, + }) + + const filteredLogs = useMemo(() => { + return { + warningLogs: logMetrics.warningLogs.filter(({type}) => type === logType), + errorLogs: logMetrics.errorLogs.filter(({type}) => type === logType), + criticalLogs: logMetrics.criticalLogs.filter(({type}) => type === logType) + } + }, [logMetrics, logType]) + + const toggleLogType = (selection: OptionType) => { + if (selection === logType) return + + setLoading(true) + + setTimeout(() => { + setLoading(false) + setTimeout(() => { + selectType(selection as LogType) + }, 500) + }, 500) + } + + return ( + +
+ + +
+
+ ) +} + +export default Main diff --git a/app/dashboard/logs/Wrapper.tsx b/app/dashboard/logs/Wrapper.tsx new file mode 100644 index 00000000..bcd12d0d --- /dev/null +++ b/app/dashboard/logs/Wrapper.tsx @@ -0,0 +1,17 @@ +'use client' + +import React, { FC } from 'react' +import Providers from '../../Providers' +import Content from './Content' +import { MainProps } from './Main' +import '../../../src/i18n' + +const Wrapper: FC = (props) => { + return ( + + + + ) +} + +export default Wrapper diff --git a/app/dashboard/logs/page.tsx b/app/dashboard/logs/page.tsx new file mode 100644 index 00000000..2359fa50 --- /dev/null +++ b/app/dashboard/logs/page.tsx @@ -0,0 +1,21 @@ +import '../../../src/global.css' +import { redirect } from 'next/navigation'; +import getSessionCookie from '../../../utilities/getSessionCookie'; +import { fetchBeaconSpec, fetchNodeHealth, fetchSyncData } from '../../api/beacon' +import { fetchLogMetrics } from '../../api/logs'; +import Wrapper from './Wrapper' + +export default async function Page() { + try { + const token = getSessionCookie() + + const logMetrics = await fetchLogMetrics(token) + const beaconSpec = await fetchBeaconSpec(token) + const syncData = await fetchSyncData(token) + const nodeHealth = await fetchNodeHealth(token) + + return + } catch (e) { + redirect('/error') + } +} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx new file mode 100644 index 00000000..cb131f8e --- /dev/null +++ b/app/dashboard/page.tsx @@ -0,0 +1,52 @@ +import '../../src/global.css' +import { redirect } from 'next/navigation'; +import getSessionCookie from '../../utilities/getSessionCookie'; +import { + fetchBeaconSpec, + fetchInclusionRate, + fetchNodeHealth, + fetchPeerData, fetchProposerDuties, + fetchSyncData +} from '../api/beacon'; +import { fetchBeaconNodeVersion, fetchGenesisData, fetchValidatorVersion } from '../api/config' +import { fetchLogMetrics } from '../api/logs'; +import { fetchValCaches, fetchValStates } from '../api/validator' +import Wrapper from './Wrapper' + +export default async function Page() { + try { + const token = getSessionCookie() + + const beaconSpec = await fetchBeaconSpec(token) + const genesisBlock = await fetchGenesisData(token) + const peerData = await fetchPeerData(token) + const syncData = await fetchSyncData(token) + const nodeHealth = await fetchNodeHealth(token) + const states = await fetchValStates(token) + const caches = await fetchValCaches(token) + const inclusion = await fetchInclusionRate(token) + const bnVersion = await fetchBeaconNodeVersion(token) + const lighthouseVersion = await fetchValidatorVersion(token) + const proposerDuties = await fetchProposerDuties(token) + const logMetrics = await fetchLogMetrics(token) + + return ( + + ) + } catch (e) { + redirect('/error') + } +} diff --git a/app/dashboard/settings/Main.tsx b/app/dashboard/settings/Main.tsx new file mode 100644 index 00000000..9cb05b1c --- /dev/null +++ b/app/dashboard/settings/Main.tsx @@ -0,0 +1,183 @@ +'use client'; + +import React, { FC, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import LighthouseSvg from '../../../src/assets/images/lighthouse-black.svg'; +import AppDescription from '../../../src/components/AppDescription/AppDescription'; +import AppVersion from '../../../src/components/AppVersion/AppVersion'; +import DashboardWrapper from '../../../src/components/DashboardWrapper/DashboardWrapper'; +import Input from '../../../src/components/Input/Input'; +import SocialIcon from '../../../src/components/SocialIcon/SocialIcon'; +import Toggle from '../../../src/components/Toggle/Toggle'; +import Typography from '../../../src/components/Typography/Typography'; +import UiModeIcon from '../../../src/components/UiModeIcon/UiModeIcon'; +import { DiscordUrl, LighthouseBookUrl, SigPGithubUrl, SigPIoUrl, SigPTwitter } from '../../../src/constants/constants'; +import { UiMode } from '../../../src/constants/enums'; +import useLocalStorage from '../../../src/hooks/useLocalStorage'; +import useNetworkMonitor from '../../../src/hooks/useNetworkMonitor'; +import useSWRPolling from '../../../src/hooks/useSWRPolling'; +import useUiMode from '../../../src/hooks/useUiMode'; +import { OptionalString } from '../../../src/types'; +import { BeaconNodeSpecResults, SyncData } from '../../../src/types/beacon'; +import { Diagnostics } from '../../../src/types/diagnostic'; +import { UsernameStorage } from '../../../src/types/storage'; +import addClassString from '../../../utilities/addClassString'; + +export interface MainProps { + initNodeHealth: Diagnostics + initSyncData: SyncData + beaconSpec: BeaconNodeSpecResults + bnVersion: string + lighthouseVersion: string +} + +const Main: FC = (props) => { + const { t } = useTranslation() + const { initNodeHealth, initSyncData, beaconSpec, lighthouseVersion, bnVersion } = props + + const { SECONDS_PER_SLOT } = beaconSpec + const { isValidatorError, isBeaconError } = useNetworkMonitor() + const { mode, toggleUiMode } = useUiMode() + const [userNameError, setError] = useState() + const [username, storeUserName] = useLocalStorage('username', undefined) + + const handleUserNameChange = (e: any) => { + const value = e.target.value + setError(undefined) + + if (!value) { + setError(t('error.userName.required')) + } + + storeUserName(value) + } + + const networkError = isValidatorError || isBeaconError + const slotInterval = SECONDS_PER_SLOT * 1000 + const { data: nodeHealth } = useSWRPolling('/api/node-health', { + refreshInterval: 6000, + fallbackData: initNodeHealth, + networkError, + }) + const { data: syncData } = useSWRPolling('/api/node-sync', { + refreshInterval: slotInterval, + fallbackData: initSyncData, + networkError, + }) + + const svgClasses = addClassString('hidden md:block absolute top-14 right-10', [ + mode === UiMode.DARK ? 'opacity-20' : 'opacity-40', + ]) + + return ( + +
+ +
+
+ + {t('sidebar.settings')} + +
+
+
+
+
+ + {t('settings.currentVersion')} + +
+
+ + {t('sidebar.theme')} + + + toggleUiMode(value ? UiMode.DARK : UiMode.LIGHT)} /> +
+
+ +
+
+ +
+ + + + + +
+
+
+
+ + {t('settings.general')} + +
+ +
+
+
+
+
+ ) +} + +export default Main diff --git a/app/dashboard/settings/Wrapper.tsx b/app/dashboard/settings/Wrapper.tsx new file mode 100644 index 00000000..8384e94b --- /dev/null +++ b/app/dashboard/settings/Wrapper.tsx @@ -0,0 +1,16 @@ +'use client' + +import React, { FC } from 'react' +import Providers from '../../Providers' +import Main, { MainProps } from './Main' +import '../../../src/i18n' + +const Wrapper: FC = (props) => { + return ( + +
+ + ) +} + +export default Wrapper diff --git a/app/dashboard/settings/page.tsx b/app/dashboard/settings/page.tsx new file mode 100644 index 00000000..9c761d9a --- /dev/null +++ b/app/dashboard/settings/page.tsx @@ -0,0 +1,30 @@ +import '../../../src/global.css' +import { redirect } from 'next/navigation'; +import getSessionCookie from '../../../utilities/getSessionCookie'; +import { fetchBeaconSpec, fetchNodeHealth, fetchSyncData } from '../../api/beacon' +import { fetchBeaconNodeVersion, fetchValidatorVersion } from '../../api/config' +import Wrapper from './Wrapper' + +export default async function Page() { + try { + const token = getSessionCookie() + + const beaconSpec = await fetchBeaconSpec(token) + const syncData = await fetchSyncData(token) + const nodeHealth = await fetchNodeHealth(token) + const bnVersion = await fetchBeaconNodeVersion(token) + const lighthouseVersion = await fetchValidatorVersion(token) + + return ( + + ) + } catch (e) { + redirect('/error') + } +} diff --git a/app/dashboard/validators/Main.tsx b/app/dashboard/validators/Main.tsx new file mode 100644 index 00000000..a90e06cc --- /dev/null +++ b/app/dashboard/validators/Main.tsx @@ -0,0 +1,245 @@ +'use client' + +import { useMotionValueEvent, useScroll } from 'framer-motion'; +import { useRouter, useSearchParams } from 'next/navigation'; +import React, { FC, useEffect, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next' +import { useRecoilState, useSetRecoilState } from 'recoil'; +import BlsExecutionModal from '../../../src/components/BlsExecutionModal/BlsExecutionModal'; +import Button, { ButtonFace } from '../../../src/components/Button/Button' +import DashboardWrapper from '../../../src/components/DashboardWrapper/DashboardWrapper' +import DisabledTooltip from '../../../src/components/DisabledTooltip/DisabledTooltip' +import EditValidatorModal from '../../../src/components/EditValidatorModal/EditValidatorModal'; +import Typography from '../../../src/components/Typography/Typography' +import ValidatorModal from '../../../src/components/ValidatorModal/ValidatorModal' +import ValidatorSearchInput from '../../../src/components/ValidatorSearchInput/ValidatorSearchInput' +import ValidatorSummary from '../../../src/components/ValidatorSummary/ValidatorSummary' +import ValidatorTable from '../../../src/components/ValidatorTable/ValidatorTable' +import { CoinbaseExchangeRateUrl } from '../../../src/constants/constants' +import useNetworkMonitor from '../../../src/hooks/useNetworkMonitor' +import useSWRPolling from '../../../src/hooks/useSWRPolling' +import { activeValidatorId, exchangeRates, isEditValidator, isValidatorDetail } from '../../../src/recoil/atoms'; +import { + BeaconNodeSpecResults, + SyncData, ValidatorMetricResult +} from '../../../src/types/beacon'; +import { Diagnostics } from '../../../src/types/diagnostic' +import { ValidatorCache, ValidatorCountResult, ValidatorInfo } from '../../../src/types/validator' + +export interface MainProps { + initNodeHealth: Diagnostics + initValStates: ValidatorInfo[] + initValidatorCountData: ValidatorCountResult + initSyncData: SyncData + initValCaches: ValidatorCache + initValMetrics: ValidatorMetricResult + beaconSpec: BeaconNodeSpecResults +} + +const Main: FC = (props) => { + const { t } = useTranslation() + const { + initNodeHealth, + initSyncData, + beaconSpec, + initValidatorCountData, + initValStates, + initValCaches, + initValMetrics, + } = props + + const [scrollPercentage, setPercentage] = useState(0) + + const container = useRef(null) + const { scrollY } = useScroll({ + container + }) + + useMotionValueEvent(scrollY, "change", (latest) => { + if(container?.current) { + const totalHeight = container.current.scrollHeight - container.current.clientHeight; + setPercentage(Math.round((latest / totalHeight) * 100)) + } + }) + + const router = useRouter() + const { SECONDS_PER_SLOT, SLOTS_PER_EPOCH } = beaconSpec + const setExchangeRate = useSetRecoilState(exchangeRates) + const [search, setSearch] = useState('') + const [activeValId, setValidatorId] = useRecoilState(activeValidatorId) + const [isEditVal, setIsEditValidator] = useRecoilState(isEditValidator) + const setValDetail = useSetRecoilState(isValidatorDetail) + const [isValDetail] = useRecoilState(isValidatorDetail) + const [isRendered, setRender] = useState(false) + + const { isValidatorError, isBeaconError } = useNetworkMonitor() + + const networkError = isValidatorError || isBeaconError + + const slotInterval = SECONDS_PER_SLOT * 1000 + const epochInterval = slotInterval * Number(SLOTS_PER_EPOCH) + const searchParams = useSearchParams() + const validatorId = searchParams.get('id') + const modalView = searchParams.get('view') + const { data: exchangeData } = useSWRPolling(CoinbaseExchangeRateUrl, { + refreshInterval: 60 * 1000, + networkError, + }) + + const { data: valNetworkData } = useSWRPolling('/api/validator-network', { + refreshInterval: 60 * 1000, + fallbackData: initValidatorCountData, + networkError, + }) + const { data: validatorCache } = useSWRPolling('/api/validator-cache', { + refreshInterval: slotInterval / 2, + fallbackData: initValCaches, + networkError, + }) + const { data: validatorStates } = useSWRPolling(`/api/validator-states`, { + refreshInterval: slotInterval, + fallbackData: initValStates, + networkError, + }) + const { data: nodeHealth } = useSWRPolling('/api/node-health', { + refreshInterval: 6000, + fallbackData: initNodeHealth, + networkError, + }) + const { data: syncData } = useSWRPolling('/api/node-sync', { + refreshInterval: slotInterval, + fallbackData: initSyncData, + networkError, + }) + const { data: validatorMetrics } = useSWRPolling('/api/validator-metrics', { refreshInterval: epochInterval / 2, fallbackData: initValMetrics, networkError }) + + const filteredValidators = useMemo(() => { + return validatorStates.filter((validator) => { + const query = search.toLowerCase() + + return ( + validator.name.toLowerCase().includes(query) || + (query.length > 3 && validator.pubKey.toLowerCase().includes(query)) || + validator.index.toString().includes(query) + ) + }) + }, [search, validatorStates]) + + const rates = exchangeData?.data.rates + + const activeValidator = useMemo(() => { + if (activeValId === undefined) return + + return validatorStates.find(({ index }) => Number(activeValId) === index) + }, [activeValId, validatorStates]) + + useEffect(() => { + if(isRendered) return + + if(validatorId) { + setValidatorId(Number(validatorId)) + } + + if(modalView === 'detail') { + setValDetail(true) + } + + if(modalView === 'edit') { + setIsEditValidator(true) + } + + setRender(true) + }, [validatorId, isRendered, modalView]) + + useEffect(() => { + if (rates) { + setExchangeRate({ + rates, + currencies: Object.keys(rates), + }) + } + }, [rates, setExchangeRate]) + + const closeEditValModal = () => { + setIsEditValidator(false); + setValidatorId(undefined) + router.push('/dashboard/validators') + } + + return ( + <> + +
+
+
+ + {t('validatorManagement.title')} + + +
+
+ + {t('validatorManagement.overview')} + +
+ +
+ + + + + + +
+
+
+
+ +
+
+ + {isValDetail && activeValidator && ( + + )} + { + isEditVal && activeValidator && ( + + ) + } + + ) +} + +export default Main diff --git a/app/dashboard/validators/Wrapper.tsx b/app/dashboard/validators/Wrapper.tsx new file mode 100644 index 00000000..8384e94b --- /dev/null +++ b/app/dashboard/validators/Wrapper.tsx @@ -0,0 +1,16 @@ +'use client' + +import React, { FC } from 'react' +import Providers from '../../Providers' +import Main, { MainProps } from './Main' +import '../../../src/i18n' + +const Wrapper: FC = (props) => { + return ( + +
+ + ) +} + +export default Wrapper diff --git a/app/dashboard/validators/page.tsx b/app/dashboard/validators/page.tsx new file mode 100644 index 00000000..10e93169 --- /dev/null +++ b/app/dashboard/validators/page.tsx @@ -0,0 +1,39 @@ +import '../../../src/global.css' +import { redirect } from 'next/navigation'; +import getSessionCookie from '../../../utilities/getSessionCookie'; +import { + fetchBeaconSpec, + fetchNodeHealth, + fetchSyncData, + fetchValidatorCountData, +} from '../../api/beacon' +import { fetchValCaches, fetchValMetrics, fetchValStates } from '../../api/validator'; +import Wrapper from './Wrapper' + +export default async function Page() { + try { + const token = getSessionCookie() + + const bnHealth = await fetchNodeHealth(token) + const beaconSpec = await fetchBeaconSpec(token) + const validatorCount = await fetchValidatorCountData(token) + const syncData = await fetchSyncData(token) + const states = await fetchValStates(token) + const caches = await fetchValCaches(token) + const metrics = await fetchValMetrics(token) + + return ( + + ) + } catch (e) { + redirect('/error') + } +} diff --git a/app/error/Main.tsx b/app/error/Main.tsx new file mode 100644 index 00000000..790be6be --- /dev/null +++ b/app/error/Main.tsx @@ -0,0 +1,22 @@ +'use client' + +import { useTranslation } from 'react-i18next'; +import Button, { ButtonFace } from '../../src/components/Button/Button'; +import Typography from '../../src/components/Typography/Typography'; + +const Main = () => { + const {t} = useTranslation() + return ( +
+
+ {t('errorPage.title')} + {t('errorPage.subText')} +
+
+ +
+
+ ) +} + +export default Main \ No newline at end of file diff --git a/app/error/page.tsx b/app/error/page.tsx new file mode 100644 index 00000000..5823c891 --- /dev/null +++ b/app/error/page.tsx @@ -0,0 +1,18 @@ +import '../../src/global.css'; +import Lighthouse from '../../src/assets/images/lightHouse.svg' +import TopographyCanvas from '../../src/components/Topography/Topography'; +import Main from './Main'; + +export default async function Page() { + return ( +
+
+ +
+ +
+
+
+
+ ) +} diff --git a/app/icon.png b/app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1deeea8a6cee8bc487e6d5ae23fc091409dd0137 GIT binary patch literal 157922 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�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 M!ufbKyNBoeABdKC)Bpeg literal 0 HcmV?d00001 diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 00000000..cfc35db7 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,120 @@ +import type { Metadata } from 'next' +import localFont from 'next/font/local' + +export const metas = { + title: 'Siren', + description: 'User interface built for Lighthouse that connects to a Lighthouse Beacon Node and a Lighthouse Validator Client to monitor performance and display key validator metrics.', + image: '/siren.png', +} + +export const metadata: Metadata = { + // metadataBase: new URL('http://localhost'), + ...metas, + twitter: { + title: metas.title, + description: metas.description, + creator: 'sigmaPrime', + images: [metas.image], + }, + openGraph: { + title: metas.title, + description: metas.description, + siteName: metas.title, + locale: 'en_US', + type: 'website', + images: [ + { + url: metas.image, + width: 800, + height: 600, + }, + { + url: metas.image, + width: 1800, + height: 1600, + alt: metas.title, + }, + ], + } +} as any + +const openSauce = localFont({ + src: [ + { + path: '../public/Fonts/OpenSauce/OpenSauceOne-Light.ttf', + weight: '300', + style: 'normal', + }, + { + path: '../public/Fonts/OpenSauce/OpenSauceOne-Regular.ttf', + weight: '400', + style: 'normal', + }, + { + path: '../public/Fonts/OpenSauce/OpenSauceOne-Bold.ttf', + weight: '700', + style: 'normal', + }, + ], + variable: '--openSauce', +}) + +const roboto = localFont({ + src: [ + { + path: '../public/Fonts/Roboto/Roboto-Regular.ttf', + weight: '300', + style: 'normal', + }, + { + path: '../public/Fonts/Roboto/Roboto-Medium.ttf', + weight: '600', + style: 'normal', + }, + ], + variable: '--roboto', +}) + +const archivo = localFont({ + src: [ + { + path: '../public/Fonts/Archivo/Archivo-Regular.ttf', + weight: '400', + style: 'normal', + }, + { + path: '../public/Fonts/Archivo/Archivo-Bold.ttf', + weight: '700', + style: 'normal', + }, + ], + variable: '--archivo', +}) + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + +