diff --git a/.github/workflows/check-format-build.yml b/.github/workflows/backend-checks.yml similarity index 93% rename from .github/workflows/check-format-build.yml rename to .github/workflows/backend-checks.yml index 8a7cf9b95..4f020f092 100644 --- a/.github/workflows/check-format-build.yml +++ b/.github/workflows/backend-checks.yml @@ -1,11 +1,15 @@ -name: Backend (Rust) Check formatting and build +name: Check backend (Rust) on: push: branches: main + paths: + - backend-rust/** pull_request: types: [opened, synchronize, reopened, ready_for_review] branches: [ main ] + paths: + - backend-rust/** env: RUST_FMT: "nightly-2023-04-01" diff --git a/.github/workflows/ci.yml b/.github/workflows/backend-net-checks.yml similarity index 88% rename from .github/workflows/ci.yml rename to .github/workflows/backend-net-checks.yml index 4f34d5798..04074fdef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/backend-net-checks.yml @@ -1,8 +1,11 @@ -name: Build +name: Check backend (.NET) on: pull_request: + types: [opened, synchronize, reopened, ready_for_review] branches: [ main ] + paths: + - backend/** env: RUST_VERSION: "1.65" diff --git a/.github/workflows/frontend-checks.yml b/.github/workflows/frontend-checks.yml new file mode 100644 index 000000000..d699674f2 --- /dev/null +++ b/.github/workflows/frontend-checks.yml @@ -0,0 +1,41 @@ +name: Check frontend + +on: + push: + branches: main + paths: + - frontend/** + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ main ] + paths: + - frontend/** + +env: + NODE_VERSION: "18.18.2" + +jobs: + checks: + name: Formatting and types + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'yarn' + cache-dependency-path: frontend/yarn.lock + - name: Install deps + working-directory: frontend + run: yarn install --frozen-lockfile --immutable + - name: Check formatting + working-directory: frontend + run: yarn run formatcheck + - name: Check types + working-directory: frontend + run: yarn run typecheck + # - name: Linting + # working-directory: frontend + # run: yarn run lintcheck diff --git a/.github/workflows/frontend-release.yml b/.github/workflows/frontend-release.yml new file mode 100644 index 000000000..e3da57f71 --- /dev/null +++ b/.github/workflows/frontend-release.yml @@ -0,0 +1,48 @@ +name: Publish frontend docker image on DockerHub + +on: + push: + tags: + - frontend/* + +jobs: + publish-docker-image: + runs-on: ubuntu-latest + environment: release + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + flavor: latest=false + images: concordium/ccdscan-frontend + tags: type=match,pattern=frontend/(.*),group=1 + - name: Ensure tag matches version in package.json + run: | + EXPECTED=$(jq -r .version frontend/package.json) + EXTRACTED="${{ steps.meta.outputs.version }}" + if [ "$EXPECTED" = "$EXTRACTED" ]; then + printf "Extracted version matches the version in package.json ($EXTRACTED).\n" + exit 0 + else + printf "ERROR: Extracted version does not match the version in package.json. \nExtracted: '$EXTRACTED'\nExpected: '$EXPECTED'\n" + exit 1 + fi + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: concordium + password: ${{ secrets.DOCKER_TOKEN }} + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: ./frontend + file: ./frontend/Dockerfile + push: true + platforms: linux/amd64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/backend-rust/.sqlx/query-163cc788cf5c4fd22a1f8e0289ad322fa7d92a7dd503a03a517622f2942c4d60.json b/backend-rust/.sqlx/query-163cc788cf5c4fd22a1f8e0289ad322fa7d92a7dd503a03a517622f2942c4d60.json new file mode 100644 index 000000000..ebd4ef0f0 --- /dev/null +++ b/backend-rust/.sqlx/query-163cc788cf5c4fd22a1f8e0289ad322fa7d92a7dd503a03a517622f2942c4d60.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COALESCE(MAX(index), 0) FROM accounts", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "coalesce", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "163cc788cf5c4fd22a1f8e0289ad322fa7d92a7dd503a03a517622f2942c4d60" +} diff --git a/backend-rust/.sqlx/query-792aeb42f25e1f70758407b469945187b00c166757ad7f9324abf3f58ac1e821.json b/backend-rust/.sqlx/query-792aeb42f25e1f70758407b469945187b00c166757ad7f9324abf3f58ac1e821.json new file mode 100644 index 000000000..c67948eb2 --- /dev/null +++ b/backend-rust/.sqlx/query-792aeb42f25e1f70758407b469945187b00c166757ad7f9324abf3f58ac1e821.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "-- Counts accounts in buckets by counting the cumulative total number of\n-- accounts (i.e. the account index) at or before (i.e. <=) the start of the\n-- bucket and the same number just before (i.e. <) the next bucket. The\n-- difference between the two numbers should give the total number of accounts\n-- created within the bucket.\nSELECT\n -- The bucket time is the starting time of the bucket.\n bucket_time,\n -- Number of accounts at or before the bucket.\n COALESCE(before_bucket.index, 0) as start_index,\n -- Number of accounts at the end of the bucket.\n COALESCE(after_bucket.index, 0) as end_index\nFROM\n -- We generate a time series of all the buckets where accounts will be counted.\n -- $1 is the full period, $2 is the bucket interval.\n -- For the rest of the comments, let's go with the example of a full period of 7 days with 6 hour buckets.\n generate_series(\n -- The first bucket starts 7 days ago.\n now() - $1::interval,\n -- The final bucket starts 6 hours ago, since the bucket time is the start of the bucket.\n now() - $2::interval,\n -- Each bucket is seperated by 6 hours.\n $2::interval\n ) AS bucket_time\nLEFT JOIN LATERAL (\n -- Selects the index at or before the start of the bucket.\n SELECT index\n FROM accounts\n LEFT JOIN blocks ON created_block = height\n WHERE slot_time <= bucket_time\n ORDER BY slot_time DESC\n LIMIT 1\n) before_bucket ON true\nLEFT JOIN LATERAL (\n -- Selects the index at the end of the bucket.\n SELECT index\n FROM accounts\n LEFT JOIN blocks ON created_block = height\n WHERE slot_time < bucket_time + $2::interval\n ORDER BY slot_time DESC\n LIMIT 1\n) after_bucket ON true\n", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "bucket_time", + "type_info": "Timestamptz" + }, + { + "ordinal": 1, + "name": "start_index", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "end_index", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Interval", + "Interval" + ] + }, + "nullable": [ + null, + null, + null + ] + }, + "hash": "792aeb42f25e1f70758407b469945187b00c166757ad7f9324abf3f58ac1e821" +} diff --git a/backend-rust/.sqlx/query-d6bccf668d544b1649962a5f0fb358bada7fbdbb4f78de19cc2ab8d0be5a83d5.json b/backend-rust/.sqlx/query-d6bccf668d544b1649962a5f0fb358bada7fbdbb4f78de19cc2ab8d0be5a83d5.json new file mode 100644 index 000000000..0e93971ea --- /dev/null +++ b/backend-rust/.sqlx/query-d6bccf668d544b1649962a5f0fb358bada7fbdbb4f78de19cc2ab8d0be5a83d5.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COALESCE(MAX(index), 0)\n FROM accounts\n LEFT JOIN blocks ON created_block = height\n WHERE slot_time < (now() - $1::interval)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "coalesce", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Interval" + ] + }, + "nullable": [ + null + ] + }, + "hash": "d6bccf668d544b1649962a5f0fb358bada7fbdbb4f78de19cc2ab8d0be5a83d5" +} diff --git a/backend-rust/migrations/0001_initialize.up.sql b/backend-rust/migrations/0001_initialize.up.sql index 72bc0ebff..4c7b750f7 100644 --- a/backend-rust/migrations/0001_initialize.up.sql +++ b/backend-rust/migrations/0001_initialize.up.sql @@ -208,6 +208,9 @@ CREATE TABLE accounts( -- credential_registration_id ); +-- Important for performance when joining accounts with its associated creation block. +CREATE INDEX accounts_created_block_idx ON accounts (created_block); + -- Add foreign key constraint now that the account table is created. ALTER TABLE transactions ADD CONSTRAINT fk_transaction_sender diff --git a/backend-rust/src/graphql_api.rs b/backend-rust/src/graphql_api.rs index 7dc46c3c8..2e6eaa41e 100644 --- a/backend-rust/src/graphql_api.rs +++ b/backend-rust/src/graphql_api.rs @@ -5,6 +5,7 @@ #![allow(unused_variables)] +mod account_metrics; mod transaction_metrics; // TODO remove this macro, when done with first iteration @@ -15,6 +16,7 @@ macro_rules! todo_api { }; } +use account_metrics::AccountMetricsQuery; use anyhow::Context as _; use async_graphql::{ http::GraphiQLSource, @@ -63,7 +65,7 @@ pub struct ApiServiceConfig { } #[derive(MergedObject, Default)] -pub struct Query(BaseQuery, TransactionMetricsQuery); +pub struct Query(BaseQuery, AccountMetricsQuery, TransactionMetricsQuery); pub struct Service { pub schema: Schema, @@ -754,7 +756,6 @@ LIMIT 30", // WHERE slot_time > (LOCALTIMESTAMP - $1::interval) }) } - // accountsMetrics(period: MetricsPeriod!): AccountsMetrics // bakerMetrics(period: MetricsPeriod!): BakerMetrics! // rewardMetrics(period: MetricsPeriod!): RewardMetrics! // rewardMetricsForAccount(accountId: ID! period: MetricsPeriod!): diff --git a/backend-rust/src/graphql_api/account_metrics.rs b/backend-rust/src/graphql_api/account_metrics.rs new file mode 100644 index 000000000..96b0dfbf5 --- /dev/null +++ b/backend-rust/src/graphql_api/account_metrics.rs @@ -0,0 +1,113 @@ +use std::sync::Arc; + +use async_graphql::{Context, Object, SimpleObject}; +use sqlx::postgres::types::PgInterval; + +use super::{get_pool, ApiError, ApiResult, DateTime, MetricsPeriod, TimeSpan}; + +#[derive(SimpleObject)] +struct AccountMetrics { + /// Total number of accounts created (all time). + last_cumulative_accounts_created: i64, + + /// Total number of accounts created in requested period. + accounts_created: i64, + + buckets: AccountMetricsBuckets, +} + +#[derive(SimpleObject)] +struct AccountMetricsBuckets { + /// The width (time interval) of each bucket. + bucket_width: TimeSpan, + + /// Start of the bucket time period. Intended x-axis value. + #[graphql(name = "x_Time")] + x_time: Vec, + + /// Total number of accounts created (all time) at the end of the bucket + /// period. Intended y-axis value. + #[graphql(name = "y_LastCumulativeAccountsCreated")] + y_last_cumulative_accounts_created: Vec, + + /// Number of accounts created within bucket time period. Intended y-axis + /// value. + #[graphql(name = "y_AccountsCreated")] + y_accounts_created: Vec, +} + +#[derive(Default)] +pub(crate) struct AccountMetricsQuery; + +#[Object] +impl AccountMetricsQuery { + async fn account_metrics( + &self, + ctx: &Context<'_>, + period: MetricsPeriod, + ) -> ApiResult { + let pool = get_pool(ctx)?; + + let last_cumulative_accounts_created = + sqlx::query_scalar!("SELECT COALESCE(MAX(index), 0) FROM accounts") + .fetch_one(pool) + .await? + .expect("coalesced"); + + // The full period interval, e.g. 7 days. + let period_interval: PgInterval = period + .as_duration() + .try_into() + .map_err(|e| ApiError::DurationOutOfRange(Arc::new(e)))?; + + let cumulative_accounts_created_before_period = sqlx::query_scalar!( + "SELECT COALESCE(MAX(index), 0) + FROM accounts + LEFT JOIN blocks ON created_block = height + WHERE slot_time < (now() - $1::interval)", + period_interval, + ) + .fetch_one(pool) + .await? + .expect("coalesced"); + + let accounts_created = + last_cumulative_accounts_created - cumulative_accounts_created_before_period; + + let bucket_width = period.bucket_width(); + + // The bucket interval, e.g. 6 hours. + let bucket_interval: PgInterval = + bucket_width.try_into().map_err(|err| ApiError::DurationOutOfRange(Arc::new(err)))?; + + let rows = sqlx::query_file!( + "src/graphql_api/account_metrics.sql", + period_interval, + bucket_interval, + ) + .fetch_all(pool) + .await?; + + let x_time = rows + .iter() + .map(|r| r.bucket_time.expect("generated by generate_series so never null")) + .collect(); + let y_last_cumulative_accounts_created = + rows.iter().map(|r| r.end_index.expect("coalesced")).collect(); + let y_accounts_created = rows + .iter() + .map(|r| r.end_index.expect("coalesced") - r.start_index.expect("coalesced")) + .collect(); + + Ok(AccountMetrics { + last_cumulative_accounts_created, + accounts_created, + buckets: AccountMetricsBuckets { + bucket_width: TimeSpan(bucket_width), + x_time, + y_last_cumulative_accounts_created, + y_accounts_created, + }, + }) + } +} diff --git a/backend-rust/src/graphql_api/account_metrics.sql b/backend-rust/src/graphql_api/account_metrics.sql new file mode 100644 index 000000000..5fc18436f --- /dev/null +++ b/backend-rust/src/graphql_api/account_metrics.sql @@ -0,0 +1,42 @@ +-- Counts accounts in buckets by counting the cumulative total number of +-- accounts (i.e. the account index) at or before (i.e. <=) the start of the +-- bucket and the same number just before (i.e. <) the next bucket. The +-- difference between the two numbers should give the total number of accounts +-- created within the bucket. +SELECT + -- The bucket time is the starting time of the bucket. + bucket_time, + -- Number of accounts at or before the bucket. + COALESCE(before_bucket.index, 0) as start_index, + -- Number of accounts at the end of the bucket. + COALESCE(after_bucket.index, 0) as end_index +FROM + -- We generate a time series of all the buckets where accounts will be counted. + -- $1 is the full period, $2 is the bucket interval. + -- For the rest of the comments, let's go with the example of a full period of 7 days with 6 hour buckets. + generate_series( + -- The first bucket starts 7 days ago. + now() - $1::interval, + -- The final bucket starts 6 hours ago, since the bucket time is the start of the bucket. + now() - $2::interval, + -- Each bucket is seperated by 6 hours. + $2::interval + ) AS bucket_time +LEFT JOIN LATERAL ( + -- Selects the index at or before the start of the bucket. + SELECT index + FROM accounts + LEFT JOIN blocks ON created_block = height + WHERE slot_time <= bucket_time + ORDER BY slot_time DESC + LIMIT 1 +) before_bucket ON true +LEFT JOIN LATERAL ( + -- Selects the index at the end of the bucket. + SELECT index + FROM accounts + LEFT JOIN blocks ON created_block = height + WHERE slot_time < bucket_time + $2::interval + ORDER BY slot_time DESC + LIMIT 1 +) after_bucket ON true diff --git a/frontend/.env.mainnet b/frontend/.env.mainnet new file mode 100644 index 000000000..0ca7f1e94 --- /dev/null +++ b/frontend/.env.mainnet @@ -0,0 +1,10 @@ +# Runtime configuration see comments in `nuxt.config.ts` for documentation. + +NUXT_PUBLIC_API_URL=https://api-ccdscan.mainnet.concordium.software/graphql +NUXT_PUBLIC_WS_URL=wss://api-ccdscan.mainnet.concordium.software/graphql +NUXT_PUBLIC_EXPLORER_NAME=Mainnet +NUXT_PUBLIC_EXPLORER_EXTERNAL=Testnet@https://testnet.ccdscan.io + +# Dev features, always disable in production. +NUXT_PUBLIC_ENABLE_URQL_DEVTOOLS=true +NUXT_PUBLIC_ENABLE_BREAKPOINT_HINT=true diff --git a/frontend/.env.stagenet b/frontend/.env.stagenet new file mode 100644 index 000000000..413f0a487 --- /dev/null +++ b/frontend/.env.stagenet @@ -0,0 +1,10 @@ +# Runtime configuration see comments in `nuxt.config.ts` for documentation. + +NUXT_PUBLIC_API_URL=https://api-ccdscan.stagenet.concordium.com/graphql +NUXT_PUBLIC_WS_URL=wss://api-ccdscan.stagenet.concordium.com/graphql +NUXT_PUBLIC_EXPLORER_NAME=Stagenet +NUXT_PUBLIC_EXPLORER_EXTERNAL=Mainnet@https://ccdscan.io;Testnet@https://testnet.ccdscan.io + +# Dev features, always disable in production. +NUXT_PUBLIC_ENABLE_URQL_DEVTOOLS=true +NUXT_PUBLIC_ENABLE_BREAKPOINT_HINT=true diff --git a/frontend/.env.testnet b/frontend/.env.testnet new file mode 100644 index 000000000..1ea3a0125 --- /dev/null +++ b/frontend/.env.testnet @@ -0,0 +1,10 @@ +# Runtime configuration see comments in `nuxt.config.ts` for documentation. + +NUXT_PUBLIC_API_URL=https://api-ccdscan.testnet.concordium.com/graphql +NUXT_PUBLIC_WS_URL=wss://api-ccdscan.testnet.concordium.com/graphql +NUXT_PUBLIC_EXPLORER_NAME=Testnet +NUXT_PUBLIC_EXPLORER_EXTERNAL=Mainnet@https://ccdscan.io + +# Dev features, always disable in production. +NUXT_PUBLIC_ENABLE_URQL_DEVTOOLS=true +NUXT_PUBLIC_ENABLE_BREAKPOINT_HINT=true diff --git a/frontend/.eslintrc b/frontend/.eslintrc index 147c05fea..d24bc883f 100644 --- a/frontend/.eslintrc +++ b/frontend/.eslintrc @@ -24,7 +24,7 @@ "plugin:vue/vue3-recommended", "prettier" ], - "ignorePatterns": ["src/types/generated.ts"], + "ignorePatterns": ["src/types/generated.ts", "dist", ".output"], "rules": { "vue/multi-word-component-names": "off", "testing-library/await-async-utils": "off", diff --git a/frontend/.prettierignore b/frontend/.prettierignore index c83f90a2b..74324eccf 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -1 +1 @@ -src/generated +src/types/generated.ts diff --git a/frontend/CHANGELOG.md b/frontend/CHANGELOG.md new file mode 100644 index 000000000..0c2d3e217 --- /dev/null +++ b/frontend/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [Unreleased] + +## [1.6.0] - 2024-11-05 + +### Added + +- Temporary maintenance banner. + +### Changed + +- Migrate project to Nuxt `3.13`. +- Update NodeJS runtime version to `18.12.1`. +- Frontend image is now independent on the network being used, and can be configured at runtime. + +### Removed + +- Display of pending stake changes for validators and delegators, as this information is no longer relevant starting from Concordium Protocol Version 7. + +## [1.5.41] - 2024-03-25 + +From before a CHANGELOG was tracked. diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 084b70f3e..0813c344d 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,26 +1,15 @@ -ARG NODE_VERSION=17.0.0 +ARG NODE_VERSION=18.18.2 FROM node:${NODE_VERSION} as build - -ARG ENVIRONMENT - -ENV ENVIRONMENT=$ENVIRONMENT - WORKDIR /app - COPY package.json yarn.lock ./ RUN yarn - COPY . . RUN yarn build FROM node:${NODE_VERSION}-slim - ENV PORT=3000 ENV HOST=0.0.0.0 - EXPOSE 3000 - COPY --from=build /app/.output . - CMD ["node", "server/index.mjs"] diff --git a/frontend/Jenkinsfile b/frontend/Jenkinsfile deleted file mode 100644 index bfe7d4cec..000000000 --- a/frontend/Jenkinsfile +++ /dev/null @@ -1,37 +0,0 @@ -// Params in JobDSL file -// 'https://github.com/Concordium/concordium-infra-jenkins-jobs/blob/master/ccdscan_frontend.groovy': -// - VERSION -// - TARGET_NET -pipeline { - agent any - environment { - image_repo = 'concordium/ccdscan-frontend' - image_name = "${image_repo}:${VERSION}" - } - stages { - stage('dockerhub-login') { - environment { - CRED = credentials('jenkins-dockerhub') - } - steps { - sh 'echo $CRED_PSW | docker login --username $CRED_USR --password-stdin' - } - } - stage('build') { - steps { - sh '''\ - docker build \ - --build-arg ENVIRONMENT=${TARGET_NET} \ - -f "frontend/Dockerfile" \ - -t "${image_name}" \ - ./frontend - '''.stripIndent() - } - } - stage('push') { - steps { - sh 'docker push "${image_name}"' - } - } - } -} diff --git a/frontend/README.md b/frontend/README.md index 228b104b6..9bb133912 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -4,11 +4,11 @@ The frontend of CCDScan is a server-side rendered single page app, which consume The frontend is built on some fundamental technologies: -- **[Vue](https://vuejs.org/)** +- **[Vue](https://vuejs.org/)** JavaScript framework for building user interfaces for the web. It is reactive, declarative and very approachable to build and extend. -- **[Nuxt 3](https://v3.nuxtjs.org/)** +- **[Nuxt 3](https://v3.nuxtjs.org/)** Application framework built on top of Vue. Out of the box it gives us some things that Vue itself lacks, such as routing, and it comes with a build system supporting code splitting and ohter optimisations. -- **[TypeScript](https://www.typescriptlang.org/)** +- **[TypeScript](https://www.typescriptlang.org/)** A typed programming language, which compiles to JavaScript. This acts as an accelerator during development, and prevents most type errors at write-time and compile-time. [More on this in a later section](#typescript). ## Setup @@ -23,25 +23,38 @@ yarn ### Run Development server +To run the development server a configuration can be provided using `.env` file, without it will assume the backend API is running locally. + ```sh yarn dev ``` -Go to [http://localhost:3000](http://localhost:3000) +Go to [http://localhost:3000](http://localhost:3000). + +To develop against our backend APIs already in production, specify the appropriate file with environment variables. Below is an example of using testnet backend API: + +```sh +yarn dev --dotenv .env.testnet +``` + ### Build and serve locally You can build and run the production image locally. -To build the image run +To build the image run: + ```sh -docker build -t IMAGE_NAME:VERSION --build-arg ENVIRONMENT= . +docker build -t IMAGE_NAME:VERSION . ``` -where `IMAGE_NAME` and `VERSION` are some container name and version of your choice and `CHAIN_ENVIRONMENT` is which environment to run agains (`stagenet`, `testnet` or `mainnet`). -To run the image run +where `IMAGE_NAME` and `VERSION` are some container name and version. + + +The image can be run against Testnet by providing the `.env.testnet` configuration. + ```sh -docker run -p 3000:3000 IMAGE_NAME:VERSION +docker run --public 3000:3000 --env-file .env.testnet IMAGE_NAME:VERSION ``` The application is now available at `http://localhost:3000/`. diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts index 875d055ea..147ede18d 100644 --- a/frontend/nuxt.config.ts +++ b/frontend/nuxt.config.ts @@ -1,41 +1,43 @@ -import { defineNuxtConfig } from 'nuxt3' - -type Environment = 'dev' | 'stagenet' | 'testnet' | 'mainnet' -type Config = { - apiUrl: string - wsUrl: string -} - -const ENVIRONMENT = (process.env.ENVIRONMENT as Environment) || 'dev' - -const VARS: Record = { - dev: { - apiUrl: 'http://localhost:5090/graphql', - wsUrl: 'ws://localhost:5090/graphql', - }, - stagenet: { - apiUrl: 'https://api-ccdscan.stagenet.concordium.com/graphql', - wsUrl: 'wss://api-ccdscan.stagenet.concordium.com/graphql', - }, - testnet: { - apiUrl: 'https://api-ccdscan.testnet.concordium.com/graphql', - wsUrl: 'wss://api-ccdscan.testnet.concordium.com/graphql', - }, - mainnet: { - apiUrl: 'https://api-ccdscan.mainnet.concordium.software/graphql', - wsUrl: 'wss://api-ccdscan.mainnet.concordium.software/graphql', - }, -} - -const getConfig = (env: Environment): Config => { - return { - apiUrl: process.env.BACKEND_API_URL || VARS[env].apiUrl || '', - wsUrl: process.env.BACKEND_WS_URL || VARS[env].wsUrl || '', - } -} +import { defineNuxtConfig } from 'nuxt/config' export default defineNuxtConfig({ + // Configuration available to the application at runtime, note the `public` object will be + // expose directly in the client-side code and therefore should not contain secrets. + // Below values are default and can be overwritten by environment variables at runtime. + runtimeConfig: { + public: { + version: process.env.npm_package_version, + // URL to use when sending GraphQL queries to the CCDscan API. + // (env NUXT_PUBLIC_API_URL) + apiUrl: 'http://localhost:5090/graphql', + // URL to use when using websockets in GraphQL CCDscan API. + // (env NUXT_PUBLIC_WS_URL) + wsUrl: 'ws://localhost:5090/graphql', + // Settings for how to display the explorer. + explorer: { + // The name to display for the explorer. + // (env NUXT_PUBLIC_EXPLORER_NAME) + name: 'Local', + // The list of external explorers to link in the explorer selector. + // Should be provided as `@` separated by `;`. + // Ex.: 'Mainnet@https://ccdscan.io;Testnet@https://testnet.ccdscan.io' + // (env NUXT_PUBLIC_EXPLORER_EXTERNAL). + external: + 'Mainnet@https://ccdscan.io;Testnet@https://testnet.ccdscan.io;Stagenet@https://stagenet.ccdscan.io', + }, + // When enabled a hint for the current breakpoint (related to screen size) + // is displayed in the bottom left corner. Enable only for development. + // (env NUXT_PUBLIC_ENABLE_BREAKPOINT_HINT) + enableBreakpointHint: false, + // Enabled the urql-devtools for debugging GraphQL (require browser extension). + // Enable only for development. + // (env NUXT_PUBLIC_ENABLE_URQL_DEVTOOLS) + enableUrqlDevtools: false, + }, + }, + // Directory for finding the source files. srcDir: 'src/', + // Directories to search for components. components: [ '~/components', '~/components/atoms', @@ -45,19 +47,22 @@ export default defineNuxtConfig({ '~/components/Drawer', '~/components/BlockDetails', ], - publicRuntimeConfig: { - ...getConfig(ENVIRONMENT), - version: process.env.npm_package_version, - environment: ENVIRONMENT, - includeDevTools: - ENVIRONMENT === 'dev' || - ENVIRONMENT === 'stagenet' || - ENVIRONMENT === 'testnet', - }, - css: ['@/assets/css/styles.css'], - build: { - postcss: { - postcssOptions: require('./postcss.config.cjs'), + // Global CSS files + css: ['~/assets/css/styles.css'], + // Enable postCSS + postcss: { + plugins: { + tailwindcss: {}, + autoprefixer: {}, }, }, + // Lock default values for Nuxt to what they were at this date. + compatibilityDate: '2024-11-01', + // TypeScript configurations. + typescript: { + // Enable strict checks. + strict: true, + // Enable type-checking at build time. + typeCheck: true, + }, }) diff --git a/frontend/package.json b/frontend/package.json index 6c0ba6eb8..f611a6783 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "ccscan-frontend", "description": "CCDScan frontend", - "version": "1.5.41", + "version": "1.6.0", "engine": "16", "type": "module", "private": true, @@ -13,8 +13,11 @@ "generate": "nuxt generate", "test": "cross-env TZ=Universal jest", "test:watch": "cross-env TZ=Universal jest --watch", - "typecheck": "vue-tsc --noEmit", + "typecheck": "nuxi typecheck", "lint": "eslint --fix --max-warnings 0 --ext .js,.ts,.vue .", + "lintcheck": "eslint --max-warnings 0 src", + "format": "prettier src --write", + "formatcheck": "prettier src --check", "gql-codegen": "graphql-codegen --config graphql-codegen.yml" }, "dependencies": { @@ -26,7 +29,6 @@ "date-fns": "^2.27.0", "duration-fns": "^3.0.0", "graphql": "^16.1.0", - "nuxt3": "3.0.0-27356801.e9128f3", "subscriptions-transport-ws": "^0.11.0", "tailwindcss": "^2.2.19", "vue": "^3.2.36", @@ -60,11 +62,15 @@ "jest": "^28", "jest-environment-jsdom": "^28.1.0", "lint-staged": "^12.4.3", + "nuxt": "^3.13.2", "postcss": "^8.4.14", "prettier": "^2.6.2", "ts-jest": "^28", "ts-node": "^10.8.0", - "typescript": "^4.7.2", - "vue-tsc": "^0.35.2" + "typescript": "^5.6.3", + "vue-tsc": "^2.1.10" + }, + "volta": { + "node": "18.18.2" } } diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs deleted file mode 100644 index 1b69d43b0..000000000 --- a/frontend/postcss.config.cjs +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/frontend/src/app.vue b/frontend/src/app.vue new file mode 100644 index 000000000..ef03d1ea3 --- /dev/null +++ b/frontend/src/app.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/src/assets/css/styles.css b/frontend/src/assets/css/styles.css index e68b0fd8e..a7651327c 100644 --- a/frontend/src/assets/css/styles.css +++ b/frontend/src/assets/css/styles.css @@ -94,11 +94,11 @@ body { justify-content: center; align-items: center; border: 1px solid #787594; - box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.2); + box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.2); } .month-picker-input { - @apply bg-theme-background-primary-elevated-nontrans + @apply bg-theme-background-primary-elevated-nontrans; } .month-picker-input-container { diff --git a/frontend/src/components/Accounts/AccountDetailsAccountStatement.vue b/frontend/src/components/Accounts/AccountDetailsAccountStatement.vue index 74047bd1b..228ac6e33 100644 --- a/frontend/src/components/Accounts/AccountDetailsAccountStatement.vue +++ b/frontend/src/components/Accounts/AccountDetailsAccountStatement.vue @@ -133,7 +133,7 @@
@@ -149,7 +149,7 @@ :max-date="currentMonth" :default-month="currentMonth.getMonth() + 1" @change=" - update => { + (update: any) => { chosenMonth = buildMonthInput(update.year, update.monthIndex) } " @@ -159,6 +159,7 @@ diff --git a/frontend/src/components/BakerDetails/BakerDetailsPoolAPY.vue b/frontend/src/components/BakerDetails/BakerDetailsPoolAPY.vue index 3bf2aaa36..d6936531f 100644 --- a/frontend/src/components/BakerDetails/BakerDetailsPoolAPY.vue +++ b/frontend/src/components/BakerDetails/BakerDetailsPoolAPY.vue @@ -30,7 +30,7 @@ @@ -39,7 +39,7 @@ @@ -51,7 +51,7 @@ v-if="data && Number.isFinite(data.delegatorsApy)" class="numerical" > - {{formatPercentage(data.delegatorsApy!)}}% + {{ formatPercentage(data.delegatorsApy!) }}% - diff --git a/frontend/src/components/BakerDetails/BakerDetailsPoolContent.vue b/frontend/src/components/BakerDetails/BakerDetailsPoolContent.vue index 5f12c13c9..473bf68e7 100644 --- a/frontend/src/components/BakerDetails/BakerDetailsPoolContent.vue +++ b/frontend/src/components/BakerDetails/BakerDetailsPoolContent.vue @@ -2,13 +2,6 @@
- -
@@ -227,7 +220,6 @@ import { computed } from 'vue' import CommissionRates from '../Staking/CommissionRates.vue' import BakerDetailsHeader from './BakerDetailsHeader.vue' -import BakerDetailsPendingChange from './BakerDetailsPendingChange.vue' import BakerDetailsTransactions from './BakerDetailsTransactions.vue' import BakerDetailsDelegators from './BakerDetailsDelegators.vue' import BakerDetailsPoolAPY from './BakerDetailsPoolAPY.vue' diff --git a/frontend/src/components/Contracts/ContractDetailsContent.vue b/frontend/src/components/Contracts/ContractDetailsContent.vue index 0b163e100..97c87b97b 100644 --- a/frontend/src/components/Contracts/ContractDetailsContent.vue +++ b/frontend/src/components/Contracts/ContractDetailsContent.vue @@ -108,14 +108,14 @@ import ContractDetailsEvents from './ContractDetailsEvents.vue' import ContractDetailsTokens from './ContractDetailsTokens.vue' import DrawerContent from '~/components/Drawer/DrawerContent.vue' import DetailsCard from '~/components/DetailsCard.vue' -import { Contract } from '~~/src/types/generated' +import type { Contract } from '~~/src/types/generated' import { convertTimestampToRelative, formatTimestamp, } from '~~/src/utils/format' import ContractDetailsRejectEvents from '~/components/Contracts/ContractDetailsRejectEvents.vue' -import { PaginationOffsetInfo } from '~~/src/composables/usePaginationOffset' -import { PageDropdownInfo } from '~~/src/composables/usePageDropdown' +import type { PaginationOffsetInfo } from '~~/src/composables/usePaginationOffset' +import type { PageDropdownInfo } from '~~/src/composables/usePageDropdown' import { Breakpoint } from '~~/src/composables/useBreakpoint' const { NOW } = useDateNow() diff --git a/frontend/src/components/Contracts/ContractDetailsEvents.vue b/frontend/src/components/Contracts/ContractDetailsEvents.vue index b47e13627..531e18782 100644 --- a/frontend/src/components/Contracts/ContractDetailsEvents.vue +++ b/frontend/src/components/Contracts/ContractDetailsEvents.vue @@ -8,7 +8,10 @@ - + @@ -50,14 +53,14 @@
From Module
- +
To Module
- +
@@ -104,7 +107,7 @@ import ContractTransfer from './Events/ContractTransfer.vue' import ModuleLink from '~/components/molecules/ModuleLink.vue' import ContractUpdated from '~/components/Contracts/Events/ContractUpdated.vue' import Tooltip from '~~/src/components/atoms/Tooltip.vue' -import { ContractEvent } from '~~/src/types/generated' +import type { ContractEvent } from '~~/src/types/generated' import TransactionLink from '~~/src/components/molecules/TransactionLink.vue' import { convertTimestampToRelative } from '~~/src/utils/format' diff --git a/frontend/src/components/Contracts/ContractDetailsRejectEvents.vue b/frontend/src/components/Contracts/ContractDetailsRejectEvents.vue index ca807e81c..24eee2304 100644 --- a/frontend/src/components/Contracts/ContractDetailsRejectEvents.vue +++ b/frontend/src/components/Contracts/ContractDetailsRejectEvents.vue @@ -10,7 +10,7 @@ @@ -92,13 +92,13 @@ import DateTimeWithLineBreak from '../Details/DateTimeWithLineBreak.vue' import MessageHEX from '../Details/MessageHEX.vue' import Message from '../Details/Message.vue' import DetailsView from '../Details/DetailsView.vue' -import InfoTooltip from '../atoms/InfoTooltip.vue' import { getEntrypoint } from './Events/contractEvents' -import { ContractRejectEvent } from '~~/src/types/generated' -import TransactionLink from '~~/src/components/molecules/TransactionLink.vue' -import Tooltip from '~~/src/components/atoms/Tooltip.vue' -import { formatTimestamp } from '~~/src/utils/format' -import { RECEIVE_NAME } from '~~/src/utils/infoTooltips' +import InfoTooltip from '~/components/atoms/InfoTooltip.vue' +import type { ContractRejectEvent } from '~/types/generated' +import TransactionLink from '~/components/molecules/TransactionLink.vue' +import Tooltip from '~/components/atoms/Tooltip.vue' +import { formatTimestamp } from '~/utils/format' +import { RECEIVE_NAME } from '~/utils/infoTooltips' type Props = { contractRejectEvents: ContractRejectEvent[] diff --git a/frontend/src/components/Contracts/ContractDetailsTokens.vue b/frontend/src/components/Contracts/ContractDetailsTokens.vue index ff739aa7d..e8642ecde 100644 --- a/frontend/src/components/Contracts/ContractDetailsTokens.vue +++ b/frontend/src/components/Contracts/ContractDetailsTokens.vue @@ -7,7 +7,10 @@ - + diff --git a/frontend/src/components/Details/Logs.vue b/frontend/src/components/Details/Logs.vue index a10df9ec9..4c5855a72 100644 --- a/frontend/src/components/Details/Logs.vue +++ b/frontend/src/components/Details/Logs.vue @@ -25,10 +25,10 @@
diff --git a/frontend/src/components/Module/ModuleDetailsContractLinkEvents.vue b/frontend/src/components/Module/ModuleDetailsContractLinkEvents.vue index 62495fbb1..9a1c24883 100644 --- a/frontend/src/components/Module/ModuleDetailsContractLinkEvents.vue +++ b/frontend/src/components/Module/ModuleDetailsContractLinkEvents.vue @@ -4,13 +4,19 @@ Transaction Contract Address Age - Action - + Action + - + @@ -40,7 +46,7 @@ import DateTimeWithLineBreak from '../Details/DateTimeWithLineBreak.vue' import ContractLink from '../molecules/ContractLink.vue' import InfoTooltip from '../atoms/InfoTooltip.vue' import Tooltip from '~~/src/components/atoms/Tooltip.vue' -import { ModuleReferenceContractLinkEvent } from '~~/src/types/generated' +import type { ModuleReferenceContractLinkEvent } from '~~/src/types/generated' import TransactionLink from '~~/src/components/molecules/TransactionLink.vue' import { convertTimestampToRelative } from '~~/src/utils/format' diff --git a/frontend/src/components/Module/ModuleDetailsLinkedContracts.vue b/frontend/src/components/Module/ModuleDetailsLinkedContracts.vue index 443734b3d..1559abcd3 100644 --- a/frontend/src/components/Module/ModuleDetailsLinkedContracts.vue +++ b/frontend/src/components/Module/ModuleDetailsLinkedContracts.vue @@ -6,7 +6,10 @@ - + import DateTimeWithLineBreak from '../Details/DateTimeWithLineBreak.vue' import ContractLink from '../molecules/ContractLink.vue' -import Tooltip from '~~/src/components/atoms/Tooltip.vue' -import { LinkedContract } from '~~/src/types/generated' -import { convertTimestampToRelative } from '~~/src/utils/format' +import Tooltip from '~/components/atoms/Tooltip.vue' +import type { LinkedContract } from '~/types/generated' +import { convertTimestampToRelative } from '~/utils/format' const { NOW } = useDateNow() diff --git a/frontend/src/components/Module/ModuleDetailsRejectEvents.vue b/frontend/src/components/Module/ModuleDetailsRejectEvents.vue index a3bceaf5d..7a2ab5954 100644 --- a/frontend/src/components/Module/ModuleDetailsRejectEvents.vue +++ b/frontend/src/components/Module/ModuleDetailsRejectEvents.vue @@ -10,7 +10,7 @@ @@ -26,22 +26,40 @@ {{ moduleRejectEvent.rejectedEvent.__typename }} - + -
-
Init name: - +
+
+ Init name: +
{{ moduleRejectEvent.rejectedEvent.initName }}
-
-
Entrypoint: - +
+
+ Entrypoint: +
{{ moduleRejectEvent.rejectedEvent.receiveName }}
-
+
@@ -52,7 +70,7 @@ diff --git a/frontend/src/components/PageDropdown.vue b/frontend/src/components/PageDropdown.vue index 0406e78e9..e1886d6aa 100644 --- a/frontend/src/components/PageDropdown.vue +++ b/frontend/src/components/PageDropdown.vue @@ -1,26 +1,30 @@