├── models └── data.json ├── .eslintrc.json ├── jsconfig.json ├── instrumentation.js ├── app ├── country │ └── [code] │ │ ├── country.module.css │ │ └── page.jsx ├── layout.jsx ├── page.jsx ├── nextar-dashboard │ └── page.jsx ├── globals.css └── nextar │ └── page.jsx ├── components ├── page.module.css ├── CountryCard.module.css ├── Navigation.jsx ├── ActivityRow.jsx ├── CountryContainer.jsx ├── CountryCard.jsx └── LineChart.jsx ├── server ├── controller.js └── server.js ├── next.config.js ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── instrumentation.node.js ├── package.json └── README.md /models/data.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@*": ["./*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /instrumentation.js: -------------------------------------------------------------------------------- 1 | export async function register() { 2 | if (process.env.NEXT_RUNTIME === 'nodejs') { 3 | await import('./instrumentation.node.js') 4 | } 5 | } -------------------------------------------------------------------------------- /app/country/[code]/country.module.css: -------------------------------------------------------------------------------- 1 | .countryContainer { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | } 6 | 7 | .countryDetails { 8 | 9 | } -------------------------------------------------------------------------------- /components/page.module.css: -------------------------------------------------------------------------------- 1 | .cardContainer { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 4 | margin: 3rem; 5 | padding: 3rem; 6 | gap: 16px; 7 | } -------------------------------------------------------------------------------- /components/CountryCard.module.css: -------------------------------------------------------------------------------- 1 | .cardContainer { 2 | padding: 20px; 3 | border: 2px solid gray; 4 | border-radius: 10px; 5 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 6 | background-color: #97cba9; 7 | text-align: center; 8 | } -------------------------------------------------------------------------------- /server/controller.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const dashboardController = {}; 3 | 4 | dashboardController.getTraceData = (req, res, next) => { 5 | try { 6 | res.sendFile(path.join(__dirname, './models/data.json')) 7 | 8 | } 9 | catch(err) { 10 | return next(err) 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /components/Navigation.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | 4 | const Navigation = () => { 5 | return ( 6 | 14 | ) 15 | } 16 | 17 | export default Navigation; -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | // next.config.js 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | images: { 6 | domains: ['flagsapi.com'], // Add 'flagsapi.com' to the list of allowed domains 7 | }, 8 | env: { 9 | timezoneKey: '02f249b24b414f5388a66cbd0152c747' 10 | }, 11 | experimental: { 12 | instrumentationHook: true 13 | } 14 | }; 15 | 16 | module.exports = nextConfig; 17 | -------------------------------------------------------------------------------- /components/ActivityRow.jsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React from 'react' 3 | 4 | const ActivityRow = (props) => { 5 | const {requestName, origin, httpMethod, httpStatus } = props.result 6 | console.log('this is propsresult', props.result) 7 | return ( 8 | 9 | {requestName} 10 | {httpMethod} 11 | {httpStatus} 12 | {origin} 13 | 14 | ) 15 | } 16 | 17 | export default ActivityRow -------------------------------------------------------------------------------- /app/layout.jsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | import Navigation from '@components/Navigation.jsx' 3 | 4 | export const metadata = { 5 | title: 'GLOBAL CITIZEN', 6 | description: 'Demo application for Nextar', 7 | } 8 | 9 | const RootLayout = ({ children }) => { 10 | return ( 11 | 12 | 13 |
14 | 15 | {children} 16 |
17 | 18 | 19 | ) 20 | } 21 | 22 | export default RootLayout; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /instrumentation.node.js: -------------------------------------------------------------------------------- 1 | import { NodeSDK } from '@opentelemetry/sdk-node' 2 | import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' 3 | import { Resource } from '@opentelemetry/resources' 4 | import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions' 5 | import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node' 6 | 7 | const exporter = new OTLPTraceExporter({ 8 | 'url': 'http://localhost:8000/postTraceData' 9 | }) 10 | 11 | const sdk = new NodeSDK({ 12 | resource: new Resource({ 13 | [SemanticResourceAttributes.SERVICE_NAME]: 'next-app', 14 | }), 15 | spanProcessor: new SimpleSpanProcessor(exporter), 16 | }) 17 | sdk.start() -------------------------------------------------------------------------------- /components/CountryContainer.jsx: -------------------------------------------------------------------------------- 1 | 2 | import CountryCard from '@components/CountryCard' 3 | import styles from './page.module.css' 4 | 5 | 6 | const getCountries = async() => { 7 | const response = await fetch('https://restcountries.com/v3.1/all'); 8 | const countries = await response.json(); 9 | return countries; 10 | } 11 | 12 | 13 | const CountryContainer = async() => { 14 | 15 | const countries = await getCountries(); 16 | 17 | return ( 18 |
19 | {countries.map((country, index) => ( 20 | 21 | ))} 22 |
23 | ) 24 | } 25 | 26 | export default CountryContainer -------------------------------------------------------------------------------- /components/CountryCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Image from 'next/image' 3 | import styles from './CountryCard.module.css' 4 | import Link from 'next/link' 5 | 6 | const CountryCard = (props) => { 7 | const { name, capital, region, subregion, languages, latlng, maps, flags, cca2} = props.details 8 | const coordinates = latlng.join(', ') 9 | 10 | 11 | return ( 12 |
13 | 14 |

{name.common}

15 | N/A 16 | 17 |
18 | ) 19 | } 20 | 21 | export default CountryCard; -------------------------------------------------------------------------------- /app/page.jsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | import CountryContainer from '@components/CountryContainer' 4 | // import styles from './page.module.css' 5 | import {onCLS, onFID, onLCP} from 'web-vitals'; 6 | import {performance, PerformanceObserver} from 'perf_hooks'; 7 | 8 | 9 | 10 | 11 | const HomePage = () => { 12 | 13 | console.log('before po') 14 | setTimeout(()=>{ 15 | 16 | const po = new PerformanceObserver((entryList) => { 17 | entryList.getEntries() 18 | 19 | 20 | }).observe({ type: "largest-contentful-paint", buffered: false }) 21 | console.log(po) 22 | 23 | // po.observe({ type: "largest-contentful-paint", buffered: true }); 24 | 25 | },5000) 26 | 27 | return ( 28 |
29 | 30 |
31 | ) 32 | } 33 | 34 | export default HomePage -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextar_demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "nextar": "nodemon --require ./server/server.js & next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@opentelemetry/exporter-trace-otlp-http": "^0.41.2", 13 | "@opentelemetry/resources": "^1.15.2", 14 | "@opentelemetry/sdk-node": "^0.41.2", 15 | "@opentelemetry/sdk-trace-node": "^1.15.2", 16 | "@opentelemetry/semantic-conventions": "^1.15.2", 17 | "cors": "^2.8.5", 18 | "chart.js": "^4.4.0", 19 | "dotenv": "^16.3.1", 20 | "eslint": "8.47.0", 21 | "eslint-config-next": "13.4.19", 22 | "express": "^4.18.2", 23 | "next": "13.4.19", 24 | "nodemon": "^3.0.1", 25 | "react": "18.2.0", 26 | "react-dom": "18.2.0", 27 | "web-vitals": "^3.4.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const cors = require('cors'); 6 | const PORT = 8000; 7 | // const dashboardController = require('./controller') 8 | 9 | app.use(cors()); 10 | app.use(express.json()); 11 | 12 | // post request from tracing 13 | app.post('/postTraceData', (req, res, next) => { 14 | console.log('POSTING!', req.body) 15 | 16 | fs.readFile(path.join(__dirname, '../models/data.json'), (err, data) => { 17 | const json = JSON.parse(data) 18 | json.push(req.body) 19 | fs.writeFile(path.join(__dirname, '../models/data.json'), JSON.stringify(json), (err) => { 20 | if(err) { 21 | return next(err) 22 | } else res.status(200).send('Successful!') 23 | }) 24 | }) 25 | // fs.appendFile(path.join(__dirname,'../models/data.json'),JSON.stringify(req.body), (err) => { 26 | // if (err) { 27 | // return next(err) 28 | // } else res.status(200).send('Successful') 29 | // }) 30 | }) 31 | 32 | // get request from dashboard 33 | app.get('/traceData', (req, res, next) => { 34 | console.log('GETTING') 35 | res.sendFile(path.join(__dirname, '../models/data.json'), (err) => { 36 | if(err) { 37 | console.log(err) 38 | return next(err) 39 | } 40 | }) 41 | }) 42 | 43 | // global error handler 44 | app.use((err, req, res, next) => { 45 | const defaultErr = { 46 | log: 'Express error handler caught unknown middleware error', 47 | status: 400, 48 | message: { err: 'An error occurred' }, 49 | }; 50 | const errorObj = Object.assign({}, defaultErr, err); 51 | console.log(errorObj.log); 52 | return res.status(errorObj.status).json(errorObj.message); 53 | }); 54 | 55 | 56 | app.listen(PORT, function (err) { 57 | if (err) console.log(err); 58 | console.log("Server listening on PORT", PORT); 59 | }); -------------------------------------------------------------------------------- /app/nextar-dashboard/page.jsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { useState, useEffect } from 'react' 3 | import ActivityRow from '@components/ActivityRow.jsx' 4 | 5 | 6 | const Dashboard = () => { 7 | const [data, setData] = useState([]); 8 | 9 | useEffect(() => { 10 | const interval = setInterval(async() => { 11 | try { 12 | console.log('fetching data') 13 | const results = []; 14 | const response = await fetch('http://localhost:8000/traceData'); 15 | const traces = await response.json(); 16 | 17 | for (let trace of traces) { 18 | const result = {}; 19 | result.requestName = trace.resourceSpans[0].scopeSpans[0].spans[0].attributes[3].value.stringValue 20 | result.origin = trace.resourceSpans[0].scopeSpans[0].spans[0].kind 21 | result.startTime = trace.resourceSpans[0].scopeSpans[0].spans[0].startTimeUnixNano 22 | result.endTime = trace.resourceSpans[0].scopeSpans[0].spans[0].endTimeUnixNano 23 | result.httpMethod = trace.resourceSpans[0].scopeSpans[0].spans[0].attributes[2].value.stringValue 24 | result.httpStatus = trace.resourceSpans[0].scopeSpans[0].spans[0].attributes[4].value.intValue 25 | results.push(result) 26 | } 27 | setData(() => [...results]) 28 | } 29 | catch(error) { 30 | console.log(error) 31 | } 32 | }, 10000) 33 | console.log('data', data) 34 | return () => clearInterval(interval) 35 | }, [data]) 36 | 37 | 38 | return ( 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {data.map((datem, index) => ( 50 | 51 | ))} 52 | 53 |
ResourceMethodStatusKindDuration
54 |
55 | ) 56 | } 57 | 58 | export default Dashboard; -------------------------------------------------------------------------------- /app/country/[code]/page.jsx: -------------------------------------------------------------------------------- 1 | import styles from './country.module.css' 2 | 3 | const getCountryDetails = async(code) => { 4 | const response = await fetch(`https://restcountries.com/v3.1/alpha/${code}`) 5 | const data = await response.json(); 6 | return data; 7 | } 8 | 9 | const getTime = async(coord) => { 10 | const coordinates = coord.join(',') 11 | try { 12 | const response = await fetch(`https://timezone.abstractapi.com/v1/current_time?api_key=02f249b24b414f5388a66cbd0152c747&location=${coordinates}`, { cache: 'no-store'}) 13 | const data = await response.json(); 14 | 15 | const date = new Date(data.datetime) 16 | const hours = date.getHours(); 17 | const mins = date.getMinutes(); 18 | const meridiem = hours >= 12 ? 'pm' : 'am'; 19 | const hour = hours % 12 || 12; 20 | const time = `${hour}:${mins < 10 ? '0': ''}${mins}${meridiem}`; 21 | return time; 22 | } 23 | catch (error) { 24 | console.log(error) 25 | } 26 | } 27 | 28 | const Country = async ({ params }) => { 29 | const arr = await getCountryDetails(params.code); 30 | const details = arr[0] 31 | 32 | let capitals; 33 | if (Array.isArray(details.capital)) { 34 | capitals = details.capital.join(', ') 35 | } else capitals = details.capital 36 | 37 | let language; 38 | if(details.languages) { 39 | let langArr = Object.values(details.languages) 40 | language = langArr.join(', ') 41 | } else language = 'N/A' 42 | 43 | let latlong; 44 | if(details.capitalInfo.latlng) latlong = details.capitalInfo.latlng; 45 | else latlong = details.latlng 46 | 47 | return ( 48 |
49 |

{details.name.common}

50 |
51 |
52 | Capital 53 |

{capitals}

54 |
55 | Region 56 |

{details.region}

57 | Subregion 58 |

{details.subregion}

59 |
60 | Languages 61 |

{language}

62 |
63 |
64 | Current Time 65 |

{getTime(latlong)}

66 |
67 |
68 |
69 | ) 70 | } 71 | 72 | export default Country -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | /* :root { 2 | --max-width: 1100px; 3 | --border-radius: 12px; 4 | --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 5 | 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', 6 | 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; 7 | 8 | --foreground-rgb: 0, 0, 0; 9 | --background-start-rgb: 214, 219, 220; 10 | --background-end-rgb: 255, 255, 255; 11 | 12 | --primary-glow: conic-gradient( 13 | from 180deg at 50% 50%, 14 | #16abff33 0deg, 15 | #0885ff33 55deg, 16 | #54d6ff33 120deg, 17 | #0071ff33 160deg, 18 | transparent 360deg 19 | ); 20 | --secondary-glow: radial-gradient( 21 | rgba(255, 255, 255, 1), 22 | rgba(255, 255, 255, 0) 23 | ); 24 | 25 | --tile-start-rgb: 239, 245, 249; 26 | --tile-end-rgb: 228, 232, 233; 27 | --tile-border: conic-gradient( 28 | #00000080, 29 | #00000040, 30 | #00000030, 31 | #00000020, 32 | #00000010, 33 | #00000010, 34 | #00000080 35 | ); 36 | 37 | --callout-rgb: 238, 240, 241; 38 | --callout-border-rgb: 172, 175, 176; 39 | --card-rgb: 180, 185, 188; 40 | --card-border-rgb: 131, 134, 135; 41 | } 42 | 43 | @media (prefers-color-scheme: dark) { 44 | :root { 45 | --foreground-rgb: 255, 255, 255; 46 | --background-start-rgb: 0, 0, 0; 47 | --background-end-rgb: 0, 0, 0; 48 | 49 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); 50 | --secondary-glow: linear-gradient( 51 | to bottom right, 52 | rgba(1, 65, 255, 0), 53 | rgba(1, 65, 255, 0), 54 | rgba(1, 65, 255, 0.3) 55 | ); 56 | 57 | --tile-start-rgb: 2, 13, 46; 58 | --tile-end-rgb: 2, 5, 19; 59 | --tile-border: conic-gradient( 60 | #ffffff80, 61 | #ffffff40, 62 | #ffffff30, 63 | #ffffff20, 64 | #ffffff10, 65 | #ffffff10, 66 | #ffffff80 67 | ); 68 | 69 | --callout-rgb: 20, 20, 20; 70 | --callout-border-rgb: 108, 108, 108; 71 | --card-rgb: 100, 100, 100; 72 | --card-border-rgb: 200, 200, 200; 73 | } 74 | } 75 | 76 | * { 77 | box-sizing: border-box; 78 | padding: 0; 79 | margin: 0; 80 | } 81 | 82 | html, 83 | body { 84 | max-width: 100vw; 85 | overflow-x: hidden; 86 | } 87 | 88 | 89 | 90 | a { 91 | color: inherit; 92 | text-decoration: none; 93 | } 94 | 95 | @media (prefers-color-scheme: dark) { 96 | html { 97 | color-scheme: dark; 98 | } 99 | } */ -------------------------------------------------------------------------------- /app/nextar/page.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import Link from 'next/link' 4 | // import {performance, PerformanceObserver} from 'perf_hooks'; 5 | import Pic from '../../public/next.svg' 6 | import Image from 'next/image' 7 | // import { useState, useEffect, useRef } from "react"; 8 | import LineChart from '@components/LineChart' 9 | 10 | export default function Nextar() { 11 | 12 | // FCP CREATOR 13 | const fcp = Math.round(JSON.parse(localStorage.getItem('web-vitals-extension-metrics')).fcp.value) 14 | console.log('test',fcp) 15 | 16 | const fcpArray = JSON.parse(localStorage.getItem('FCParray')); 17 | 18 | let newFCPArray; 19 | if(fcpArray){ 20 | newFCPArray = [...fcpArray, fcp]; 21 | } else { 22 | newFCPArray = [fcp]; 23 | } 24 | 25 | localStorage.setItem('FCParray', JSON.stringify(newFCPArray.sort((a,b)=> a-b))); 26 | 27 | 28 | const FCPindex = Math.ceil(fcpArray.length*0.75); 29 | const FCP75 = newFCPArray.sort((a,b)=> a-b)[FCPindex] 30 | 31 | // LCP CREATOR 32 | const lcp = Math.round(JSON.parse(localStorage.getItem('web-vitals-extension-metrics')).lcp.value) 33 | console.log('test',lcp) 34 | 35 | const lcpArray = JSON.parse(localStorage.getItem('LCParray')); 36 | 37 | let newLCPArray; 38 | if(lcpArray){ 39 | newLCPArray = [...lcpArray, lcp]; 40 | } else { 41 | newLCPArray = [lcp]; 42 | } 43 | 44 | localStorage.setItem('LCParray', JSON.stringify(newLCPArray.sort((a,b)=> a-b))); 45 | 46 | const LCPindex = Math.ceil(lcpArray.length*0.75); 47 | const LCP75 = newLCPArray.sort((a,b)=> a-b)[LCPindex] 48 | 49 | // CLS CREATOR 50 | const cls = JSON.parse(localStorage.getItem('web-vitals-extension-metrics')).cls.value 51 | console.log('test',cls) 52 | 53 | const clsArray = JSON.parse(localStorage.getItem('CLSarray')); 54 | 55 | let newCLSArray; 56 | if(clsArray){ 57 | newCLSArray = [...clsArray, cls]; 58 | } else { 59 | newCLSArray = [cls]; 60 | } 61 | 62 | localStorage.setItem('CLSarray', JSON.stringify(newCLSArray.sort((a,b)=> a-b))); 63 | 64 | const CLSindex = Math.ceil(clsArray.length*0.75); 65 | const CLS75 = (newCLSArray.sort((a,b)=> a-b)[CLSindex]).toFixed(3) 66 | 67 | // FID CREATOR 68 | const fid = JSON.parse(localStorage.getItem('web-vitals-extension-metrics')).fid.value 69 | console.log('test',fid) 70 | 71 | const fidArray = JSON.parse(localStorage.getItem('FIDarray')); 72 | 73 | let newFIDArray; 74 | if(fidArray){ 75 | newFIDArray = [...fidArray, fid]; 76 | } else { 77 | newFIDArray = [fid]; 78 | } 79 | 80 | localStorage.setItem('FIDarray', JSON.stringify(newFIDArray.sort((a,b)=> a-b))); 81 | 82 | 83 | const FIDindex = Math.ceil(fidArray.length*0.75); 84 | const FID75 = (newFIDArray.sort((a,b)=> a-b)[FIDindex]).toFixed(2) 85 | 86 | console.log('test in nextar dash'); 87 | 88 | // const [fid, setFID] = useState(0); 89 | // const [LCP, setLCP] = useState(0); 90 | // const [lcpArray, setLcpArray] = useState([]); 91 | // const [CLS, setCLS] = useState(0); 92 | // let clsArray = []; 93 | 94 | // useEffect(() => { 95 | // //LCP 96 | // new PerformanceObserver((entryList) => { 97 | // const entry = +(entryList.getEntries()[0].renderTime/1000).toFixed(3); 98 | // setLCP(entry); 99 | 100 | // const myArray = JSON.parse(localStorage.getItem('LCP array')); 101 | 102 | // let newLCPArray; 103 | // if(myArray){ 104 | // newLCPArray = [...myArray, entry]; 105 | // } else { 106 | // newLCPArray = [entry]; 107 | // } 108 | 109 | // setLcpArray(newLCPArray); 110 | 111 | // localStorage.setItem('LCP array', JSON.stringify(newLCPArray)); 112 | // }).observe({ type: "largest-contentful-paint", buffered: true }); 113 | 114 | // // FID 115 | // new PerformanceObserver((entryList) => { 116 | // setFID( 117 | // entryList.getEntries()[0].processingStart - 118 | // entryList.getEntries()[0].startTime 119 | // ); 120 | // localStorage.setItem('fid', Math.round(fid * 10) / 10); 121 | // }).observe({ type: 'first-input', buffered: true }); 122 | 123 | 124 | // // CLS 125 | // new PerformanceObserver((entryList) => { 126 | // const entry = +entryList.getEntries()[0].value.toFixed(4); 127 | // setCLS(entry); 128 | 129 | // const clsArray = JSON.parse(localStorage.getItem('CLS array')); 130 | 131 | // let newCLSArray; 132 | // if(clsArray){ 133 | // newCLSArray = [...clsArray, entry]; 134 | // } else { 135 | // newCLSArray = [entry]; 136 | // } 137 | 138 | // localStorage.setItem('CLS array', JSON.stringify(newCLSArray)); 139 | // }).observe({ type: 'layout-shift', buffered: true }); 140 | // }, []); 141 | 142 | return ( 143 |
144 |

Nextar Dashboard

145 |

WEB VITALS

146 | 147 | 148 | 149 |

SERVER NETWORK ACTIVITY

150 | 151 | {/* head */} 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |
EndpointType of RequestResponse StatusResponse SizeStart TimeDuration__
172 |
173 | ) 174 | } 175 | 176 | 177 | //
178 | //

FCP

179 | //

{FCP75}

180 | //
181 | 182 | //
183 | //

LCP

184 | // {/*

{LCP} s

*/} 185 | //
186 | 187 | //
188 | //

CLS

189 | // {/*

{CLS}

*/} 190 | //
191 | 192 | //
193 | //

FID

194 | // {/*

{fid.toFixed(3)} ms

*/} 195 | //
196 | 197 | //
198 | // 199 | // 200 | // 201 | //
-------------------------------------------------------------------------------- /components/LineChart.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { 3 | Chart, 4 | ChartConfiguration, 5 | LineController, 6 | LineElement, 7 | PointElement, 8 | LinearScale, 9 | Title, 10 | CategoryScale, 11 | Filler, 12 | Legend, 13 | } from 'chart.js'; 14 | 15 | export default function LineChart(props) { 16 | useEffect(() => { 17 | Chart.register( 18 | CategoryScale, 19 | LineController, 20 | LineElement, 21 | PointElement, 22 | LinearScale, 23 | Title, 24 | Filler, 25 | Legend 26 | ); 27 | const ctx = document.getElementById('myChart').getContext('2d'); 28 | const myChart = new Chart(ctx, { 29 | type: 'line', 30 | data: { 31 | labels: JSON.parse(localStorage.LCParray).map((el, i) => i + 1), 32 | datasets: [ 33 | { 34 | data: JSON.parse(localStorage.LCParray), 35 | label: 'Actual', 36 | borderColor: 'rgb(62,149,205)', 37 | backgroundColor: 'rgb(62,149,205,0.1)', 38 | // fill: true, 39 | }, 40 | { 41 | data: JSON.parse(localStorage.LCParray).map((el) => 2500), 42 | label: 'Good', 43 | borderColor: 'rgb(60,186,159)', 44 | backgroundColor: 'rgb(60,186,159,0.1)', 45 | fill: true, 46 | }, 47 | { 48 | data: JSON.parse(localStorage.LCParray).map((el) => 4000), 49 | label: 'Needs Improvement', 50 | borderColor: 'rgb(255,165,0)', 51 | backgroundColor: 'rgb(255,165,0,0.1)', 52 | fill: true, 53 | }, 54 | { 55 | data: JSON.parse(localStorage.LCParray).map((el) => 56 | Math.max(6000, ...JSON.parse(localStorage.LCParray)) 57 | ), 58 | label: 'Poor', 59 | borderColor: 'rgb(196,88,80)', 60 | backgroundColor: 'rgb(196,88,80,0.1)', 61 | fill: true, 62 | }, 63 | ], 64 | }, 65 | options: { 66 | scales: { 67 | y: { 68 | suggestedMin: 0, 69 | suggestedMax: 400, 70 | }, 71 | }, 72 | }, 73 | }); 74 | 75 | const ctx1 = document.getElementById('CLSChart').getContext('2d'); 76 | const CLSChart = new Chart(ctx1, { 77 | type: 'line', 78 | data: { 79 | labels: JSON.parse(localStorage.CLSarray).map((el, i) => i + 1), 80 | datasets: [ 81 | { 82 | data: JSON.parse(localStorage.CLSarray), 83 | label: 'Actual', 84 | borderColor: 'rgb(62,149,205)', 85 | backgroundColor: 'rgb(62,149,205,0.1)', 86 | // fill: true, 87 | }, 88 | { 89 | data: JSON.parse(localStorage.CLSarray).map((el) => 1.2), 90 | label: 'Good', 91 | borderColor: 'rgb(60,186,159)', 92 | backgroundColor: 'rgb(60,186,159,0.1)', 93 | fill: true, 94 | }, 95 | { 96 | data: JSON.parse(localStorage.CLSarray).map((el) => 1.666), 97 | label: 'Needs Improvement', 98 | borderColor: 'rgb(255,165,0)', 99 | backgroundColor: 'rgb(255,165,0,0.1)', 100 | fill: true, 101 | }, 102 | { 103 | data: JSON.parse(localStorage.CLSarray).map((el) => 104 | Math.max(3.00, ...JSON.parse(localStorage.CLSarray)) 105 | ), 106 | label: 'Poor', 107 | borderColor: 'rgb(196,88,80)', 108 | backgroundColor: 'rgb(196,88,80,0.1)', 109 | fill: true, 110 | }, 111 | ], 112 | }, 113 | options: { 114 | scales: { 115 | y: { 116 | suggestedMin: 0, 117 | suggestedMax: 0.5, 118 | }, 119 | }, 120 | }, 121 | }); 122 | 123 | const ctx2 = document.getElementById('FIDChart').getContext('2d'); 124 | const FIDChart = new Chart(ctx2, { 125 | type: 'line', 126 | data: { 127 | labels: JSON.parse(localStorage.FIDarray).map((el, i) => i + 1), 128 | datasets: [ 129 | { 130 | data: JSON.parse(localStorage.FIDarray), 131 | label: 'Actual', 132 | borderColor: 'rgb(62,149,205)', 133 | backgroundColor: 'rgb(62,149,205,0.1)', 134 | // fill: true, 135 | }, 136 | { 137 | data: JSON.parse(localStorage.FIDarray).map((el) => 100), 138 | label: 'Good', 139 | borderColor: 'rgb(60,186,159)', 140 | backgroundColor: 'rgb(60,186,159,0.1)', 141 | fill: true, 142 | }, 143 | { 144 | data: JSON.parse(localStorage.FIDarray).map((el) => 300), 145 | label: 'Needs Improvement', 146 | borderColor: 'rgb(255,165,0)', 147 | backgroundColor: 'rgb(255,165,0,0.1)', 148 | fill: true, 149 | }, 150 | { 151 | data: JSON.parse(localStorage.FIDarray).map((el) => 152 | Math.max(600, ...JSON.parse(localStorage.FIDarray)) 153 | ), 154 | label: 'Poor', 155 | borderColor: 'rgb(196,88,80)', 156 | backgroundColor: 'rgb(196,88,80,0.1)', 157 | fill: true, 158 | }, 159 | ], 160 | }, 161 | options: { 162 | scales: { 163 | y: { 164 | suggestedMin: 0, 165 | suggestedMax: 0.5, 166 | }, 167 | }, 168 | }, 169 | }); 170 | 171 | 172 | const ctx3 = document.getElementById('FCPChart').getContext('2d'); 173 | const FCPChart = new Chart(ctx3, { 174 | type: 'line', 175 | data: { 176 | labels: JSON.parse(localStorage.FCParray).map((el, i) => i + 1), 177 | datasets: [ 178 | { 179 | data: JSON.parse(localStorage.FCParray), 180 | label: 'Actual', 181 | borderColor: 'rgb(62,149,205)', 182 | backgroundColor: 'rgb(62,149,205,0.1)', 183 | // fill: true, 184 | }, 185 | { 186 | data: JSON.parse(localStorage.FCParray).map((el) => 1800), 187 | label: 'Good', 188 | borderColor: 'rgb(60,186,159)', 189 | backgroundColor: 'rgb(60,186,159,0.1)', 190 | fill: true, 191 | }, 192 | { 193 | data: JSON.parse(localStorage.FCParray).map((el) => 3000), 194 | label: 'Needs Improvement', 195 | borderColor: 'rgb(255,165,0)', 196 | backgroundColor: 'rgb(255,165,0,0.1)', 197 | fill: true, 198 | }, 199 | { 200 | data: JSON.parse(localStorage.FCParray).map((el) => 201 | Math.max(5000, ...JSON.parse(localStorage.FCParray)) 202 | ), 203 | label: 'Poor', 204 | borderColor: 'rgb(196,88,80)', 205 | backgroundColor: 'rgb(196,88,80,0.1)', 206 | fill: true, 207 | }, 208 | ], 209 | }, 210 | options: { 211 | scales: { 212 | y: { 213 | suggestedMin: 0, 214 | suggestedMax: 10, 215 | }, 216 | }, 217 | }, 218 | }); 219 | }, []); 220 | 221 | return ( 222 | <> 223 | {/* Filled line chart */} 224 |
225 |
226 |

Largest Contentful Paint (LCP): {props.FCP75}ms (75th percentile)

227 | 228 |

Cumulative Layout Shift (CLS): {props.CLS75} (75th percentile)

229 | 230 |

First Input Delay (FID): {props.FID75}ms (75th percentile)

231 | 232 |

First Contentful Paint (FCP): {props.FCP75}ms (75th percentile)

233 | 234 |
235 |
236 | 237 | ); 238 | } 239 | --------------------------------------------------------------------------------