├── .env ├── .eslintrc.json ├── .gitignore ├── README.md ├── __test__ └── pages │ └── home.test.js ├── assets ├── PanLogo.png └── demoPic.png ├── lib ├── dbConnect.js ├── parseCookies.ts └── passport-github-auth.js ├── models └── loginModel.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── api │ ├── all.ts │ ├── auth │ │ ├── callback │ │ │ └── github.ts │ │ └── nextauth.ts │ ├── createUser.ts │ ├── finduser │ │ └── index.ts │ ├── githublogin.ts │ ├── lighthouse.ts │ ├── lighthousedemo.ts │ ├── login.ts │ ├── newsletter │ │ └── [newsletter].js │ ├── updateuser │ │ └── index.ts │ └── user │ │ └── [user].ts ├── components │ ├── AboutLH.tsx │ ├── ControlPanel.tsx │ ├── ControlPanelDemo.tsx │ ├── EndpointsList.tsx │ ├── FeatureSection.tsx │ ├── Footer.tsx │ ├── Hero.tsx │ ├── InfoSection.tsx │ ├── Intro.tsx │ ├── LoadingSpinner.tsx │ ├── MainLineChartRE.tsx │ ├── Nav.tsx │ ├── Newsletter.tsx │ ├── Sidenav.tsx │ ├── UsingApp.tsx │ ├── UsingMetrics.tsx │ ├── employeeID.tsx │ ├── lhGauge.tsx │ ├── wrightDetails.tsx │ └── wrightDetailsDemo.tsx ├── dashboard.tsx ├── demo.tsx ├── docs.tsx ├── index.tsx ├── login.tsx ├── manager.tsx ├── repoendpoints.tsx └── signup.tsx ├── panoptic-nextjs-docs └── README.md ├── public ├── centerDash.png ├── icons8-watchtower-96.ico ├── icons8-watchtower-96.png ├── repoPic.png └── rightSideDash.png ├── styles ├── Dashboard.module.scss ├── Demo.module.scss ├── Docs.module.scss ├── Endpoints.module.scss ├── Home.module.scss ├── Login.module.scss ├── Manager.module.scss ├── Nav.module.scss ├── Newsletter.module.scss ├── Repoendpoints.module.scss ├── Sidenav.module.scss ├── Variables.scss ├── chartie.scss ├── globals.css └── lineChart.css ├── tsconfig.json └── types.ts /.env: -------------------------------------------------------------------------------- 1 | GITHUB_CLIENT="26a39c74c6cc84f5a4ab" 2 | GITHUB_SECRET="34bf7cb06ce444f2f964633472c538eddef13e26" 3 | MONGO_URI="mongodb+srv://admin:admin@cluster0.tuf6p.mongodb.net/Panoptic?retryWrites=true&w=majority" 4 | MAILCHIMP_KEY="b1d923e73fdbb043cae54c13581370a5-us5" -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Panoptic 2 | 3 | An open-source tool for tacking website performance.  4 | 5 | Panoptic is used to track key metrics for your website during development and deployment including **Performance, Accessibility, Best Practices and SEO** 6 | 7 | By tracking these key metrics over a long period of time, Engineers are able to spot a regression in performance to identify why and when a regression has occurred 8 | 9 | Panoptic is powered by Google's Lighthouse SDK, and keeps track of data from all past reports to create a long term image of the health of your application 10 | 11 | ## Try Panoptic 12 | 13 | To get started, open [www.panoptic.app](http://www.panoptic.app) 14 | 15 | Navigate to our sign-up/login page 16 | 17 | Create an account manually, or sign in with your existing github account 18 | 19 | If you sign in with your github account then you will see a list of your existing repositories, otherwise all endpoints to be tested will be added to "other" 20 | 21 | Select that endpoint and click "run test"  22 | 23 | After waiting for the test to complete, you may need to reload the page (Adding this to the graph on LH audit completion is a feature we are looking to add in the near future)  24 | 25 | Select the associated endpoint again and you will see your test has been added to the line graph 26 | 27 | Run this test any time there is a change to your main branch to keep a long term picture of the health of your application 28 | 29 | In the future we plan to add GitHub action integration to allow these tests to run automatically through your repository's CI/CD pipeline 30 | 31 | ## Contribute 32 | 33 | Panoptic is an open-source application, if you would like to contribute to Panoptic, fork this repository, add any features you would like to contribute, then submit a pull request to the official Panoptic dev branch and a team member will review your request prior to approval 34 | 35 | ### Current Contributors 36 | 37 | - [Elliot Adinolfi](https://github.com/ElliotAdinolfi) 38 | - [Marc Doran](https://github.com/CodedMarc) 39 | - [Karl Richards](https://github.com/Acario175) 40 | - [Davis Zung](https://github.com/daviszung) 41 | -------------------------------------------------------------------------------- /__test__/pages/home.test.js: -------------------------------------------------------------------------------- 1 | 2 | const puppeteer = require('puppeteer'); 3 | 4 | // const timeout = process.env.SLOWMO ? 30000 : 10000; 5 | 6 | describe('index.tsx', () => { 7 | let browser; 8 | let page; 9 | 10 | beforeEach(async () => { 11 | browser = await puppeteer.launch({ headless: false, slowMo: 50 }); 12 | page = await browser.newPage(); 13 | await page.goto('http://localhost:3000/'); 14 | }); 15 | 16 | afterEach(async () => { 17 | await page.close(); 18 | await browser.close(); 19 | }); 20 | 21 | it('should be titled "Panoptic"', async () => { 22 | await expect(page.title()).resolves.toMatch('Panoptic'); 23 | }); 24 | 25 | it('Functional Login Nav Btn', async () => { 26 | await page.waitForSelector('#navLoginBtn'); 27 | await page.click('#navLoginBtn'); 28 | await page.waitForSelector('#loginBtn'); 29 | const html = await page.$eval('#loginBtn', (e) => e.innerHTML); 30 | expect(html).toBe('Login'); 31 | }); 32 | 33 | it('Does Login Work?', async () => { 34 | await page.waitForSelector('#navLoginBtn'); 35 | await page.click('#navLoginBtn'); 36 | await page.waitForSelector('form'); 37 | 38 | await page.type('#username', 'admin2'); 39 | await page.type('#password', 'admin2'); 40 | await page.click('#loginBtn'); 41 | await page.waitForNavigation(); 42 | 43 | await expect(page.url()).toMatch('http://localhost:3000/dashboard'); 44 | }, 3000); 45 | 46 | it('Does Docs Btn work?', async () => { 47 | await page.waitForSelector('#docsBtn'); 48 | 49 | await page.click('#docsBtn'); 50 | await page.waitForNavigation(); 51 | await expect(page.url()).toMatch('http://localhost:3000/docs'); 52 | }, 1000); 53 | 54 | it('Does Demo Btn work?', async () => { 55 | await page.waitForSelector('#demoBtn'); 56 | 57 | await page.click('#demoBtn'); 58 | await page.waitForNavigation(); 59 | await expect(page.url()).toMatch('http://localhost:3000/demo'); 60 | }, 1000); 61 | 62 | it('Does Demo Btn work?', async () => { 63 | await page.waitForSelector('#demoBtn'); 64 | 65 | await page.click('#demoBtn'); 66 | await page.waitForNavigation(); 67 | await expect(page.url()).toMatch('http://localhost:3000/demo'); 68 | await page.waitForSelector('#urlData'); 69 | 70 | await page.type('#urlData', 'http://netflix.com'); 71 | await page.click('#runDemoBtn'); 72 | await page.waitForSelector('#controlGauges'); 73 | const html = await page.$eval('#performanceBtn', (e) => 74 | e.getAttribute('id') 75 | ); 76 | console.log(html); 77 | await expect(html).toBe('performanceBtn'); 78 | 79 | await page.click('#accessibilityBtn'); 80 | 81 | await page.waitForSelector('#metricTitle'); 82 | 83 | const metricTitle = await page.$eval('#metricTitle', (e) => e.innerText); 84 | // console.log(metricTitle); 85 | await expect(metricTitle).toBe('Accessibility Metrics'); 86 | // await page.waitForTimeout(5000); 87 | }, 30000); 88 | }); 89 | -------------------------------------------------------------------------------- /assets/PanLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Panoptic/478f453a44790dcd7a1555380edae989fee83c23/assets/PanLogo.png -------------------------------------------------------------------------------- /assets/demoPic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Panoptic/478f453a44790dcd7a1555380edae989fee83c23/assets/demoPic.png -------------------------------------------------------------------------------- /lib/dbConnect.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import { DBOptions } from '../types'; 3 | 4 | let cached = global.mongoose; 5 | 6 | if (!cached) { 7 | cached = global.mongoose = { connect: null, promise: null } 8 | } 9 | 10 | const dbConnect = async () => { 11 | if (cached.connect) { 12 | return cached.connect; 13 | }; 14 | if (!cached.promise) { 15 | const options = { 16 | useNewUrlParser: true, 17 | useUnifiedTopology: true 18 | }; 19 | cached.promise = mongoose.connect(process.env.MONGO_URI, options).then((mongoose) => { 20 | return mongoose; 21 | }); 22 | }; 23 | cached.connect = await cached.promise; 24 | return cached.connect; 25 | }; 26 | 27 | export default dbConnect; -------------------------------------------------------------------------------- /lib/parseCookies.ts: -------------------------------------------------------------------------------- 1 | import cookie from "cookie" 2 | import { Request, Response } from 'express'; 3 | 4 | export function parseCookies(req:Request) { 5 | return cookie.parse(req ? req.headers.cookie || "" : document.cookie) 6 | }; -------------------------------------------------------------------------------- /lib/passport-github-auth.js: -------------------------------------------------------------------------------- 1 | import { Strategy } from 'passport-github2' 2 | import passport from 'passport' 3 | 4 | 5 | 6 | passport.use(new Strategy( 7 | { 8 | clientID: process.env.GITHUB_CLIENT, 9 | clientSecret: process.env.GITHUB_SECRET, 10 | callbackURL: 'https://panoptic.app/api/auth/callback/github' 11 | }, 12 | 13 | function (accessToken, refreshToken, profile, cb) { 14 | profile.token = accessToken; 15 | profile.refresh = refreshToken; 16 | return cb(null, profile); 17 | })); 18 | 19 | passport.serializeUser((user, cb) => { 20 | cb(null, user); 21 | }); 22 | passport.deserializeUser((id, cb) => { 23 | cb(null, id); 24 | }); 25 | 26 | 27 | export default passport; 28 | -------------------------------------------------------------------------------- /models/loginModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from 'mongoose'; 2 | import { MongoUser } from '../types'; 3 | 4 | const userSchema = new Schema( 5 | { 6 | username: { type: String, unique: true, required: true }, 7 | password: { type: String, required: true }, 8 | endpoints: { type: Object, default: {} }, 9 | github: { type: Schema.Types.Mixed, default: false }, 10 | }, 11 | { minimize: false } 12 | ); 13 | 14 | // This or statement fixes mongoose error on NextJS-> 15 | //"Mongoose Cannot Overwrite Model Once Compiled Error" 16 | module.exports = mongoose.models.users || mongoose.model('users', userSchema); 17 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: false, 4 | typescript: { 5 | ignoreBuildErrors: true, 6 | }, 7 | eslint: { ignoreDuringBuilds: true }, 8 | }; 9 | 10 | module.exports = nextConfig; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "panoptic-nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "test": "jest --watch" 11 | }, 12 | "dependencies": { 13 | "@chakra-ui/icons": "^2.0.2", 14 | "@chakra-ui/react": "^2.2.1", 15 | "@emotion/react": "^11.9.3", 16 | "@emotion/styled": "^11.9.3", 17 | "@fortawesome/fontawesome-svg-core": "^6.1.1", 18 | "@fortawesome/free-solid-svg-icons": "^6.1.1", 19 | "@fortawesome/react-fontawesome": "^0.1.18", 20 | "@mailchimp/mailchimp_marketing": "^3.0.75", 21 | "@types/chart.js": "^2.9.37", 22 | "@types/mongoose": "^5.11.97", 23 | "@types/next-auth": "^3.15.0", 24 | "@types/passport": "^1.0.9", 25 | "@types/passport-github": "^1.1.7", 26 | "apexcharts": "^3.35.3", 27 | "axios": "^0.27.2", 28 | "bcrypt": "^5.0.1", 29 | "chart.js": "^3.8.0", 30 | "chrome-aws-lambda": "^10.1.0", 31 | "chrome-launcher": "^0.15.1", 32 | "cookies": "^0.8.0", 33 | "dotenv": "^16.0.1", 34 | "express-session": "^1.17.3", 35 | "faker": "^5.5.3", 36 | "framer-motion": "^6.3.13", 37 | "js-cookie": "^3.0.1", 38 | "jsonwebtoken": "^8.5.1", 39 | "mongodb": "^4.7.0", 40 | "mongoose": "^6.3.8", 41 | "mongoose-findorcreate": "^3.0.0", 42 | "next": "12.1.6", 43 | "next-auth": "^4.5.0", 44 | "next-connect": "^0.12.2", 45 | "next-session": "^4.0.4", 46 | "octokit": "^1.8.0", 47 | "passport": "^0.6.0", 48 | "passport-github": "^1.1.0", 49 | "passport-github2": "^0.1.12", 50 | "react": "18.1.0", 51 | "react-apexcharts": "^1.4.0", 52 | "react-chartjs-2": "^4.2.0", 53 | "react-cookie": "^4.1.1", 54 | "react-dom": "18.1.0", 55 | "react-icons": "^4.4.0", 56 | "react-spinners": "^0.12.0", 57 | "sass": "^1.52.3", 58 | "ts-jest": "^28.0.5", 59 | "uuid": "^8.3.2" 60 | }, 61 | "devDependencies": { 62 | "@testing-library/jest-dom": "^5.16.4", 63 | "@testing-library/react": "^13.3.0", 64 | "@types/express": "^4.17.13", 65 | "@types/node": "17.0.42", 66 | "@types/passport-github2": "^1.2.5", 67 | "@types/puppeteer": "^5.4.6", 68 | "@types/react": "18.0.12", 69 | "@types/react-dom": "18.0.5", 70 | "@types/uuid": "^8.3.4", 71 | "concurrently": "^7.2.1", 72 | "cors": "^2.8.5", 73 | "eslint": "8.17.0", 74 | "eslint-config-next": "12.1.6", 75 | "express": "^4.18.1", 76 | "jest": "^28.1.2", 77 | "lighthouse": "^9.6.3", 78 | "nodemon": "^2.0.16", 79 | "puppeteer": "^14.4.1", 80 | "typescript": "4.7.3" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import '../styles/chartie.scss'; 3 | import type { AppProps } from 'next/app'; 4 | import Head from 'next/head'; 5 | import React from 'react'; 6 | import { SessionProvider } from 'next-auth/react'; 7 | import '../styles/lineChart.css'; 8 | import { ChakraProvider } from '@chakra-ui/react'; 9 | 10 | function MyApp({ Component, pageProps }: AppProps):JSX.Element { 11 | return ( 12 | // 13 | 14 | 15 | Panoptic 16 | 17 | 18 | 19 | 20 | // 21 | ); 22 | }; 23 | 24 | export default MyApp; 25 | -------------------------------------------------------------------------------- /pages/api/all.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import dbConnect from '../../lib/dbConnect'; 3 | 4 | const User = require('../../models/loginModel'); 5 | import express, { Request, Response, NextFunction, ErrorRequestHandler } from 'express'; 6 | 7 | const getAllData = async (req: Request, res: Response):Promise => { 8 | if (req.method === 'GET') { 9 | await dbConnect(); 10 | const allData = await User.find({}); 11 | res.status(200).send(allData); 12 | }; 13 | }; 14 | 15 | export default getAllData; -------------------------------------------------------------------------------- /pages/api/auth/callback/github.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next'; 2 | import nextConnect from 'next-connect'; 3 | import dbConnect from '../../../../lib/dbConnect'; 4 | import passport from '../../../../lib/passport-github-auth'; 5 | const session = require('express-session'); 6 | const mongoose = require('mongoose'); 7 | const Cookies = require('cookies'); 8 | const bcrypt = require('bcrypt'); 9 | const User = require('../../../../models/loginModel'); 10 | 11 | export default nextConnect() 12 | .use( 13 | session({ 14 | secret: process.env.GITHUB_SECRET, 15 | }) 16 | ) 17 | .get( 18 | passport.authenticate('github', { failureRedirect: '/' }), 19 | async (req: NextApiRequest & { user: any }, res: NextApiResponse) => { 20 | const cookies = new Cookies(req, res); 21 | await dbConnect(); 22 | // store username / pass from req.body 23 | 24 | console.log('LOGIN called with data ', req.user); 25 | await dbConnect(); 26 | console.log('~~~Connected to mongoDB~~~'); 27 | // store username / pass from req.body 28 | const { username, nodeId, token, photos, refresh } = req.user; 29 | console.log(req.user); 30 | // Check if user exists and then compare pass if so 31 | const foundUser = await User.findOne({ username: username }); 32 | if (foundUser) { 33 | console.log('Login username found'); 34 | cookies.set('userId', foundUser._id); 35 | return res.status(201).redirect('/dashboard'); 36 | // User exist 37 | // Checking password with hash password on server 38 | } else { 39 | // create user as they don't exist 40 | console.log('Create user called in github.ts'); 41 | console.log('req.user', username, nodeId, token); 42 | // Assign username / pass to varibles so we can hash pass 43 | // const { username, password } = req.body; 44 | // Hashing function with bcrypt 45 | bcrypt.hash(nodeId, 10, async function (err: any, hash: any) { 46 | if (err) { 47 | // Error when hashing password 48 | console.log('Error hashing password', nodeId); 49 | return res.status(401).send('Error hashing password'); 50 | } else { 51 | // Store hash in your password DB. 52 | await dbConnect(); 53 | const newUser = await new User({ 54 | username: username, 55 | password: hash, 56 | github: { 57 | profilePic: photos[0].value, 58 | token: token, 59 | refresh: refresh, 60 | repos: {}, 61 | }, 62 | }); 63 | await newUser.save(); 64 | // Set Login Cookie 65 | cookies.set('userId', newUser._id); 66 | return res.status(201).redirect('/dashboard'); 67 | } 68 | }); 69 | } 70 | } 71 | ); 72 | -------------------------------------------------------------------------------- /pages/api/auth/nextauth.ts: -------------------------------------------------------------------------------- 1 | import GithubProvider from 'next-auth/providers/github'; 2 | import NextAuth from 'next-auth'; 3 | 4 | export default NextAuth({ 5 | providers: [ 6 | GithubProvider({ 7 | clientId: process.env.GITHUB_CLIENT, 8 | clientSecret: process.env.GITHUB_SECRET, 9 | authorization: "https://github.com/login/oauth/authorize?scope=read:user+user:email+repo+read:repo_hook+workflow", 10 | }) 11 | ], 12 | }) 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /pages/api/createUser.ts: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | import dbConnect from '../../lib/dbConnect'; 3 | 4 | const User = require('../../models/loginModel'); 5 | import express, { Request, Response, NextFunction, ErrorRequestHandler } from 'express'; 6 | import { CreateUser } from '../../types'; 7 | 8 | const createUser = async (req: Request, res: Response):Promise => { 9 | // Check method type ie post/get etc 10 | if (req.method === 'POST') { 11 | await dbConnect(); 12 | const newUser = await new User(req.body); 13 | newUser.save((err: ErrorRequestHandler, user: CreateUser) => { 14 | if (err) { 15 | return res.json(err); 16 | } 17 | res.send(user.username) 18 | }); 19 | }; 20 | }; 21 | 22 | export default createUser; -------------------------------------------------------------------------------- /pages/api/finduser/index.ts: -------------------------------------------------------------------------------- 1 | const User = require('../../../models/loginModel'); 2 | import dbConnect from '../../../lib/dbConnect'; 3 | import { Request, Response, ErrorRequestHandler } from 'express'; 4 | 5 | const getUserData = async (req:Request, res:Response):Promise => { 6 | const { username } = req.body; 7 | try { 8 | await dbConnect(); 9 | await User.findOne({_id: username}, (err:ErrorRequestHandler, user:any) => { 10 | if (err) return console.log('err getting username', err); 11 | if (user) return res.send(user); 12 | }); 13 | } 14 | catch (error) { 15 | if (error) console.log('damn...'); 16 | }; 17 | }; 18 | // const getUserData = async (username) => { 19 | // await dbConnect(); 20 | 21 | // // Find user username": "sampledata" 22 | // // Data we need is endpoints."https://sampledata.coateam" 23 | // const foundGetUser = await User.findOne({ _id: username }); 24 | // if (foundGetUser) { 25 | // console.log(username + ' getUser found'); 26 | // return foundGetUser 27 | // } else { 28 | // return console.log(username + ' getUser not found'); 29 | // }; 30 | // }; 31 | // const userHandler = async (req, res) => { 32 | // const userData = await getUserData(req.query.user);//req.query.user 33 | // res.json(userData) 34 | // }; 35 | 36 | export default getUserData; -------------------------------------------------------------------------------- /pages/api/githublogin.ts: -------------------------------------------------------------------------------- 1 | import nextConnect from "next-connect"; 2 | import passport from "../../lib/passport-github-auth"; 3 | 4 | 5 | export default nextConnect() 6 | .use(passport.initialize()) 7 | .get( 8 | passport.authenticate('github', { scope: ['read:user', 'read:repo', 'workflow', 'repo:read'] }) 9 | ); -------------------------------------------------------------------------------- /pages/api/lighthouse.ts: -------------------------------------------------------------------------------- 1 | const lighthouse = require('lighthouse'); 2 | const chromeLauncher = require('chrome-launcher'); 3 | const User = require('../../models/loginModel.ts'); 4 | const Cookies = require('cookies'); 5 | import dbConnect from '../../lib/dbConnect'; 6 | import { LHData, LHOptions, MongoUser } from '../../types'; 7 | import { Request, Response } from 'express'; 8 | 9 | 10 | function isValidUrl(string) { 11 | const matchpattern = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/gm; 12 | if (matchpattern.test(string) && string.includes('www')) return true; 13 | return false; 14 | } 15 | 16 | export default async function lighthouseRequest(req: Request, res: Response):Promise { 17 | await dbConnect(); 18 | const cookies = new Cookies(req, res); 19 | let url:string = req.body.url; 20 | const reponame:string | null = req.body.reponame || null; 21 | const lastCommit:string | null = req.body.commit || null; 22 | const platform:string = req.body.platform ? req.body.platform : 'desktop'; 23 | if(url[url.length - 1] !== '/') url = url.concat('/'); 24 | 25 | if(url.slice(0, 8) !== 'https://' && url.slice(0,7) !== 'http://') { 26 | for(let i = 0; i < url.length; i++) { 27 | if(url[i] === 'w' && url[i+3] === '.') { 28 | url = 'https://'.concat(url.slice(i, url.length - 1)); 29 | break; 30 | }; 31 | }; 32 | }; 33 | try { 34 | if (!isValidUrl(url)){ 35 | throw new Error('Please enter a valid url, format should be https://www.[website].com') 36 | } 37 | } 38 | catch (err){ 39 | console.log("Error: Invalid url", err); 40 | }; 41 | const googleUrl = 'https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?strategy=MOBILE&url=' + url + '&key=AIzaSyCWNar-IbOaQT1WX_zfAjUxG01x7xErbSc&category=ACCESSIBILITY&category=BEST_PRACTICES&category=PERFORMANCE&category=SEO'; 42 | const getGoogleReport = async () => { 43 | const response = await fetch(googleUrl, { 44 | headers: { 45 | Referer: 'https://web.dev/measure/?url=' + url 46 | }, 47 | }) 48 | .then(response => response.json()) 49 | return response; 50 | } 51 | 52 | let runnerResult = await getGoogleReport(); 53 | 54 | const scores: LHData = { 55 | performance: Math.ceil(runnerResult.lighthouseResult.categories.performance.score * 100), 56 | accessibility: Math.ceil( 57 | runnerResult.lighthouseResult.categories.accessibility.score * 100 58 | ), 59 | bestPractices: Math.ceil( 60 | runnerResult.lighthouseResult.categories['best-practices'].score * 100 61 | ), 62 | seo: Math.ceil(runnerResult.lighthouseResult.categories.seo.score * 100), 63 | performanceMetrics: { 64 | 'first-contentful-paint': { 65 | title: runnerResult.lighthouseResult.audits['first-contentful-paint'].title, 66 | description: 67 | runnerResult.lighthouseResult.audits['first-contentful-paint'].description, 68 | score: runnerResult.lighthouseResult.audits['first-contentful-paint'].score, 69 | displayValue: 70 | runnerResult.lighthouseResult.audits['first-contentful-paint'].displayValue, 71 | }, 72 | 'speed-index': { 73 | title: runnerResult.lighthouseResult.audits['speed-index'].title, 74 | description: runnerResult.lighthouseResult.audits['speed-index'].description, 75 | score: runnerResult.lighthouseResult.audits['speed-index'].score, 76 | displayValue: runnerResult.lighthouseResult.audits['speed-index'].displayValue, 77 | }, 78 | 'largest-contentful-paint': { 79 | title: 80 | runnerResult.lighthouseResult.audits['largest-contentful-paint-element'].title, 81 | description: 82 | runnerResult.lighthouseResult.audits['largest-contentful-paint'].description, 83 | score: runnerResult.lighthouseResult.audits['largest-contentful-paint'].score, 84 | displayValue: 85 | runnerResult.lighthouseResult.audits['largest-contentful-paint'].displayValue, 86 | }, 87 | 'time-to-interactive': { 88 | title: runnerResult.lighthouseResult.audits['interactive'].title, 89 | description: runnerResult.lighthouseResult.audits['interactive'].description, 90 | score: runnerResult.lighthouseResult.audits['interactive'].score, 91 | displayValue: runnerResult.lighthouseResult.audits['interactive'].displayValue, 92 | }, 93 | 'total-blocking-time': { 94 | title: runnerResult.lighthouseResult.audits['total-blocking-time'].title, 95 | description: runnerResult.lighthouseResult.audits['total-blocking-time'].description, 96 | score: runnerResult.lighthouseResult.audits['total-blocking-time'].score, 97 | displayValue: 98 | runnerResult.lighthouseResult.audits['total-blocking-time'].displayValue, 99 | }, 100 | 'cumulative-layout-shift': { 101 | title: runnerResult.lighthouseResult.audits['cumulative-layout-shift'].title, 102 | description: 103 | runnerResult.lighthouseResult.audits['cumulative-layout-shift'].description, 104 | score: runnerResult.lighthouseResult.audits['cumulative-layout-shift'].score, 105 | displayValue: 106 | runnerResult.lighthouseResult.audits['cumulative-layout-shift'].displayValue, 107 | }, 108 | 'modern-image-formats': { 109 | title: runnerResult.lighthouseResult.audits['modern-image-formats'].title, 110 | description: 111 | runnerResult.lighthouseResult.audits['modern-image-formats'].description, 112 | score: runnerResult.lighthouseResult.audits['modern-image-formats'].score, 113 | displayValue: 114 | Math.max( 115 | runnerResult.lighthouseResult.audits['modern-image-formats'].numericValue / 1000 116 | ) + ' S', 117 | }, 118 | 'unused-javascript': { 119 | title: runnerResult.lighthouseResult.audits['unused-javascript'].title, 120 | description: runnerResult.lighthouseResult.audits['unused-javascript'].description, 121 | score: runnerResult.lighthouseResult.audits['unused-javascript'].score, 122 | displayValue: 123 | Math.max( 124 | runnerResult.lighthouseResult.audits['unused-javascript'].numericValue / 1000 125 | ) + ' S', 126 | }, 127 | 'minified-javascript': { 128 | title: runnerResult.lighthouseResult.audits['unminified-javascript'].title, 129 | description: 130 | runnerResult.lighthouseResult.audits['unminified-javascript'].description, 131 | score: runnerResult.lighthouseResult.audits['unminified-javascript'].score, 132 | displayValue: 133 | runnerResult.lighthouseResult.audits['unminified-javascript'].scoreDisplayMode, 134 | }, 135 | 'minified-css': { 136 | title: runnerResult.lighthouseResult.audits['unminified-css'].title, 137 | description: runnerResult.lighthouseResult.audits['unminified-css'].description, 138 | score: runnerResult.lighthouseResult.audits['unminified-css'].score, 139 | displayValue: 140 | runnerResult.lighthouseResult.audits['unminified-css'].scoreDisplayMode, 141 | }, 142 | 'preload-lcp-image': { 143 | title: runnerResult.lighthouseResult.audits['preload-lcp-image'].title, 144 | description: runnerResult.lighthouseResult.audits['preload-lcp-image'].description, 145 | score: runnerResult.lighthouseResult.audits['preload-lcp-image'].score, 146 | displayValue: 147 | runnerResult.lighthouseResult.audits['preload-lcp-image'].scoreDisplayMode, 148 | }, 149 | 'uses-long-cache-ttl': { 150 | title: runnerResult.lighthouseResult.audits['uses-long-cache-ttl'].title, 151 | description: runnerResult.lighthouseResult.audits['uses-long-cache-ttl'].description, 152 | score: runnerResult.lighthouseResult.audits['uses-long-cache-ttl'].score, 153 | displayValue: 154 | runnerResult.lighthouseResult.audits['uses-long-cache-ttl'].displayValue, 155 | }, 156 | }, 157 | accessibilityMetrics: { 158 | 'button-name': { 159 | title: runnerResult.lighthouseResult.audits['button-name'].title, 160 | description: runnerResult.lighthouseResult.audits['button-name'].description, 161 | score: runnerResult.lighthouseResult.audits['button-name'].score, 162 | displayMode: runnerResult.lighthouseResult.audits['button-name'].scoreDisplayMode, 163 | }, 164 | bypass: { 165 | title: runnerResult.lighthouseResult.audits['bypass'].title, 166 | description: runnerResult.lighthouseResult.audits['bypass'].description, 167 | score: runnerResult.lighthouseResult.audits['bypass'].score, 168 | displayMode: runnerResult.lighthouseResult.audits['bypass'].scoreDisplayMode, 169 | }, 170 | 'link-name': { 171 | title: runnerResult.lighthouseResult.audits['link-name'].title, 172 | description: runnerResult.lighthouseResult.audits['link-name'].description, 173 | score: runnerResult.lighthouseResult.audits['link-name'].score, 174 | displayMode: runnerResult.lighthouseResult.audits['link-name'].scoreDisplayMode, 175 | }, 176 | }, 177 | bestPracticesMetrics: { 178 | doctype: { 179 | title: runnerResult.lighthouseResult.audits['doctype'].title, 180 | description: runnerResult.lighthouseResult.audits['doctype'].description, 181 | score: runnerResult.lighthouseResult.audits['doctype'].score, 182 | displayMode: runnerResult.lighthouseResult.audits['doctype'].scoreDisplayMode, 183 | }, 184 | 'is-on-https': { 185 | title: runnerResult.lighthouseResult.audits['is-on-https'].title, 186 | description: runnerResult.lighthouseResult.audits['is-on-https'].description, 187 | score: runnerResult.lighthouseResult.audits['is-on-https'].score, 188 | displayMode: runnerResult.lighthouseResult.audits['is-on-https'].scoreDisplayMode, 189 | }, 190 | deprecations: { 191 | title: runnerResult.lighthouseResult.audits['deprecations'].title, 192 | description: runnerResult.lighthouseResult.audits['deprecations'].description, 193 | score: runnerResult.lighthouseResult.audits['deprecations'].score, 194 | displayMode: runnerResult.lighthouseResult.audits['deprecations'].scoreDisplayMode, 195 | }, 196 | 'geolocation-on-start': { 197 | title: runnerResult.lighthouseResult.audits['geolocation-on-start'].title, 198 | description: 199 | runnerResult.lighthouseResult.audits['geolocation-on-start'].description, 200 | score: runnerResult.lighthouseResult.audits['geolocation-on-start'].score, 201 | displayMode: 202 | runnerResult.lighthouseResult.audits['geolocation-on-start'].scoreDisplayMode, 203 | }, 204 | 'notification-on-start': { 205 | title: runnerResult.lighthouseResult.audits['notification-on-start'].title, 206 | description: 207 | runnerResult.lighthouseResult.audits['notification-on-start'].description, 208 | score: runnerResult.lighthouseResult.audits['notification-on-start'].score, 209 | displayMode: 210 | runnerResult.lighthouseResult.audits['notification-on-start'].scoreDisplayMode, 211 | }, 212 | 'image-size-responsive': { 213 | title: runnerResult.lighthouseResult.audits['image-size-responsive'].title, 214 | description: 215 | runnerResult.lighthouseResult.audits['image-size-responsive'].description, 216 | score: runnerResult.lighthouseResult.audits['image-size-responsive'].score, 217 | displayMode: 218 | runnerResult.lighthouseResult.audits['image-size-responsive'].scoreDisplayMode, 219 | }, 220 | 'image-aspect-ratio': { 221 | title: runnerResult.lighthouseResult.audits['image-aspect-ratio'].title, 222 | description: runnerResult.lighthouseResult.audits['image-aspect-ratio'].description, 223 | score: runnerResult.lighthouseResult.audits['image-aspect-ratio'].score, 224 | displayMode: 225 | runnerResult.lighthouseResult.audits['image-aspect-ratio'].scoreDisplayMode, 226 | }, 227 | 'password-inputs-can-be-pasted-into': { 228 | title: 229 | runnerResult.lighthouseResult.audits['password-inputs-can-be-pasted-into'].title, 230 | description: 231 | runnerResult.lighthouseResult.audits['password-inputs-can-be-pasted-into'] 232 | .description, 233 | score: 234 | runnerResult.lighthouseResult.audits['password-inputs-can-be-pasted-into'].score, 235 | displayMode: 236 | runnerResult.lighthouseResult.audits['password-inputs-can-be-pasted-into'] 237 | .scoreDisplayMode, 238 | }, 239 | 'errors-in-console': { 240 | title: runnerResult.lighthouseResult.audits['errors-in-console'].title, 241 | description: runnerResult.lighthouseResult.audits['errors-in-console'].description, 242 | score: runnerResult.lighthouseResult.audits['errors-in-console'].score, 243 | displayMode: 244 | runnerResult.lighthouseResult.audits['errors-in-console'].scoreDisplayMode, 245 | }, 246 | }, 247 | seoMetrics: { 248 | viewport: { 249 | title: runnerResult.lighthouseResult.audits['viewport'].title, 250 | description: runnerResult.lighthouseResult.audits['viewport'].description, 251 | score: runnerResult.lighthouseResult.audits['viewport'].score, 252 | displayMode: runnerResult.lighthouseResult.audits['viewport'].scoreDisplayMode, 253 | }, 254 | 'document-title': { 255 | title: runnerResult.lighthouseResult.audits['document-title'].title, 256 | description: runnerResult.lighthouseResult.audits['document-title'].description, 257 | score: runnerResult.lighthouseResult.audits['document-title'].score, 258 | displayMode: runnerResult.lighthouseResult.audits['document-title'].scoreDisplayMode, 259 | }, 260 | 'link-name': { 261 | title: runnerResult.lighthouseResult.audits['link-name'].title, 262 | description: runnerResult.lighthouseResult.audits['link-name'].description, 263 | score: runnerResult.lighthouseResult.audits['link-name'].score, 264 | displayMode: runnerResult.lighthouseResult.audits['link-name'].scoreDisplayMode, 265 | }, 266 | 'http-status-code': { 267 | title: runnerResult.lighthouseResult.audits['http-status-code'].title, 268 | description: runnerResult.lighthouseResult.audits['http-status-code'].description, 269 | score: runnerResult.lighthouseResult.audits['http-status-code'].score, 270 | displayMode: 271 | runnerResult.lighthouseResult.audits['http-status-code'].scoreDisplayMode, 272 | }, 273 | 'meta-description': { 274 | title: runnerResult.lighthouseResult.audits['meta-description'].title, 275 | description: runnerResult.lighthouseResult.audits['meta-description'].description, 276 | score: runnerResult.lighthouseResult.audits['meta-description'].score, 277 | displayMode: 278 | runnerResult.lighthouseResult.audits['meta-description'].scoreDisplayMode, 279 | }, 280 | 'image-alt': { 281 | title: runnerResult.lighthouseResult.audits['image-alt'].title, 282 | description: runnerResult.lighthouseResult.audits['image-alt'].description, 283 | score: runnerResult.lighthouseResult.audits['image-alt'].score, 284 | displayMode: runnerResult.lighthouseResult.audits['image-alt'].scoreDisplayMode, 285 | }, 286 | }, 287 | }; 288 | 289 | const id:string = cookies.get('userId'); 290 | type currentUser = MongoUser; 291 | let currentUser; 292 | if (id) { 293 | 294 | currentUser = await User.findOne({ _id: id }); 295 | const currentdate:Date = new Date(); 296 | const datetime:string = 297 | currentdate.getDate() + 298 | '/' + 299 | (currentdate.getMonth() + 1) + 300 | '/' + 301 | currentdate.getFullYear() + 302 | '@' + 303 | currentdate.getHours() + 304 | ':' + 305 | currentdate.getMinutes() + 306 | ':' + 307 | currentdate.getSeconds(); 308 | 309 | if(!currentUser.endpoints[url]) { 310 | currentUser.endpoints[url] = {}; 311 | }; 312 | if(!currentUser.endpoints[url][platform]) { 313 | currentUser.endpoints[url][platform] = {}; 314 | }; 315 | currentUser.endpoints[url][platform][datetime] = { 316 | metrics: scores, 317 | }; 318 | if(reponame) { 319 | console.log('davis test: ', reponame) 320 | if(!currentUser.github.repos[reponame]) { 321 | currentUser.github.repos[reponame] = { 322 | repoEndpoints: [], 323 | lastCommit: lastCommit, 324 | }; 325 | }; 326 | if(!currentUser.github.repos[reponame].repoEndpoints.includes(url)) { 327 | currentUser.github.repos[reponame].repoEndpoints.push(url); 328 | }; 329 | currentUser.github.repos[reponame].lastCommit = lastCommit; 330 | }; 331 | }; 332 | 333 | await currentUser.markModified('endpoints'); 334 | await currentUser.save(); 335 | 336 | res.status(200).json(scores); 337 | } 338 | -------------------------------------------------------------------------------- /pages/api/lighthousedemo.ts: -------------------------------------------------------------------------------- 1 | const lighthouse = require('lighthouse'); 2 | const chromeLauncher = require('chrome-launcher'); 3 | import { LHData, LHOptions } from '../../types'; 4 | import express, { Request, Response } from 'express'; 5 | 6 | function isValidUrl(string) { 7 | const matchpattern = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/gm; 8 | if (matchpattern.test(string) && string.includes('www')) return true; 9 | return false; 10 | } 11 | 12 | export default async function lighthouseRequest(req: Request, res: Response):Promise { 13 | 14 | let url = req.body; 15 | 16 | if(url[url.length - 1] !== '/') url = url.concat('/'); 17 | 18 | if(url.slice(0, 8) !== 'https://' && url.slice(0,7) !== 'http://') { 19 | for(let i = 0; i < url.length; i++) { 20 | if(url[i] === 'w' && url[i+3] === '.') { 21 | url = 'https://'.concat(url.slice(i, url.length - 1)); 22 | break; 23 | }; 24 | }; 25 | }; 26 | 27 | try { 28 | if (!isValidUrl(url)){ 29 | throw new Error('Please enter a valid url, format should be https://www.[website].com') 30 | } 31 | } 32 | catch (err){ 33 | console.log("Error: Invalid url", err); 34 | }; 35 | const googleUrl = 'https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?strategy=MOBILE&url=' + url + '&key=AIzaSyCWNar-IbOaQT1WX_zfAjUxG01x7xErbSc&category=ACCESSIBILITY&category=BEST_PRACTICES&category=PERFORMANCE&category=SEO'; 36 | const getGoogleReport = async () => { 37 | const response = await fetch(googleUrl, { 38 | headers: { 39 | Referer: 'https://web.dev/measure/?url=' + url 40 | }, 41 | }) 42 | .then(response => response.json()) 43 | return response; 44 | }; 45 | 46 | let runnerResult = await getGoogleReport(); 47 | // grab all the info to return to the front-end 48 | const scores:LHData = { 49 | performance: Math.ceil(runnerResult.lighthouseResult.categories.performance.score * 100), 50 | accessibility: Math.ceil( 51 | runnerResult.lighthouseResult.categories.accessibility.score * 100 52 | ), 53 | bestPractices: Math.ceil( 54 | runnerResult.lighthouseResult.categories['best-practices'].score * 100 55 | ), 56 | seo: Math.ceil(runnerResult.lighthouseResult.categories.seo.score * 100), 57 | performanceMetrics: { 58 | 'first-contentful-paint': { 59 | title: runnerResult.lighthouseResult.audits['first-contentful-paint'].title, 60 | description: 61 | runnerResult.lighthouseResult.audits['first-contentful-paint'].description, 62 | score: runnerResult.lighthouseResult.audits['first-contentful-paint'].score, 63 | displayValue: 64 | runnerResult.lighthouseResult.audits['first-contentful-paint'].displayValue, 65 | }, 66 | 'speed-index': { 67 | title: runnerResult.lighthouseResult.audits['speed-index'].title, 68 | description: runnerResult.lighthouseResult.audits['speed-index'].description, 69 | score: runnerResult.lighthouseResult.audits['speed-index'].score, 70 | displayValue: runnerResult.lighthouseResult.audits['speed-index'].displayValue, 71 | }, 72 | 'largest-contentful-paint': { 73 | title: 74 | runnerResult.lighthouseResult.audits['largest-contentful-paint-element'].title, 75 | description: 76 | runnerResult.lighthouseResult.audits['largest-contentful-paint'].description, 77 | score: runnerResult.lighthouseResult.audits['largest-contentful-paint'].score, 78 | displayValue: 79 | runnerResult.lighthouseResult.audits['largest-contentful-paint'].displayValue, 80 | }, 81 | 'time-to-interactive': { 82 | title: runnerResult.lighthouseResult.audits['interactive'].title, 83 | description: runnerResult.lighthouseResult.audits['interactive'].description, 84 | score: runnerResult.lighthouseResult.audits['interactive'].score, 85 | displayValue: runnerResult.lighthouseResult.audits['interactive'].displayValue, 86 | }, 87 | 'total-blocking-time': { 88 | title: runnerResult.lighthouseResult.audits['total-blocking-time'].title, 89 | description: runnerResult.lighthouseResult.audits['total-blocking-time'].description, 90 | score: runnerResult.lighthouseResult.audits['total-blocking-time'].score, 91 | displayValue: 92 | runnerResult.lighthouseResult.audits['total-blocking-time'].displayValue, 93 | }, 94 | 'cumulative-layout-shift': { 95 | title: runnerResult.lighthouseResult.audits['cumulative-layout-shift'].title, 96 | description: 97 | runnerResult.lighthouseResult.audits['cumulative-layout-shift'].description, 98 | score: runnerResult.lighthouseResult.audits['cumulative-layout-shift'].score, 99 | displayValue: 100 | runnerResult.lighthouseResult.audits['cumulative-layout-shift'].displayValue, 101 | }, 102 | 'modern-image-formats': { 103 | title: runnerResult.lighthouseResult.audits['modern-image-formats'].title, 104 | description: 105 | runnerResult.lighthouseResult.audits['modern-image-formats'].description, 106 | score: runnerResult.lighthouseResult.audits['modern-image-formats'].score, 107 | displayValue: 108 | Math.max( 109 | runnerResult.lighthouseResult.audits['modern-image-formats'].numericValue / 1000 110 | ) + ' S', 111 | }, 112 | 'unused-javascript': { 113 | title: runnerResult.lighthouseResult.audits['unused-javascript'].title, 114 | description: runnerResult.lighthouseResult.audits['unused-javascript'].description, 115 | score: runnerResult.lighthouseResult.audits['unused-javascript'].score, 116 | displayValue: 117 | Math.max( 118 | runnerResult.lighthouseResult.audits['unused-javascript'].numericValue / 1000 119 | ) + ' S', 120 | }, 121 | 'minified-javascript': { 122 | title: runnerResult.lighthouseResult.audits['unminified-javascript'].title, 123 | description: 124 | runnerResult.lighthouseResult.audits['unminified-javascript'].description, 125 | score: runnerResult.lighthouseResult.audits['unminified-javascript'].score, 126 | displayValue: 127 | runnerResult.lighthouseResult.audits['unminified-javascript'].scoreDisplayMode, 128 | }, 129 | 'minified-css': { 130 | title: runnerResult.lighthouseResult.audits['unminified-css'].title, 131 | description: runnerResult.lighthouseResult.audits['unminified-css'].description, 132 | score: runnerResult.lighthouseResult.audits['unminified-css'].score, 133 | displayValue: 134 | runnerResult.lighthouseResult.audits['unminified-css'].scoreDisplayMode, 135 | }, 136 | 'preload-lcp-image': { 137 | title: runnerResult.lighthouseResult.audits['preload-lcp-image'].title, 138 | description: runnerResult.lighthouseResult.audits['preload-lcp-image'].description, 139 | score: runnerResult.lighthouseResult.audits['preload-lcp-image'].score, 140 | displayValue: 141 | runnerResult.lighthouseResult.audits['preload-lcp-image'].scoreDisplayMode, 142 | }, 143 | 'uses-long-cache-ttl': { 144 | title: runnerResult.lighthouseResult.audits['uses-long-cache-ttl'].title, 145 | description: runnerResult.lighthouseResult.audits['uses-long-cache-ttl'].description, 146 | score: runnerResult.lighthouseResult.audits['uses-long-cache-ttl'].score, 147 | displayValue: 148 | runnerResult.lighthouseResult.audits['uses-long-cache-ttl'].displayValue, 149 | }, 150 | }, 151 | accessibilityMetrics: { 152 | 'button-name': { 153 | title: runnerResult.lighthouseResult.audits['button-name'].title, 154 | description: runnerResult.lighthouseResult.audits['button-name'].description, 155 | score: runnerResult.lighthouseResult.audits['button-name'].score, 156 | displayMode: runnerResult.lighthouseResult.audits['button-name'].scoreDisplayMode, 157 | }, 158 | bypass: { 159 | title: runnerResult.lighthouseResult.audits['bypass'].title, 160 | description: runnerResult.lighthouseResult.audits['bypass'].description, 161 | score: runnerResult.lighthouseResult.audits['bypass'].score, 162 | displayMode: runnerResult.lighthouseResult.audits['bypass'].scoreDisplayMode, 163 | }, 164 | 'link-name': { 165 | title: runnerResult.lighthouseResult.audits['link-name'].title, 166 | description: runnerResult.lighthouseResult.audits['link-name'].description, 167 | score: runnerResult.lighthouseResult.audits['link-name'].score, 168 | displayMode: runnerResult.lighthouseResult.audits['link-name'].scoreDisplayMode, 169 | }, 170 | }, 171 | bestPracticesMetrics: { 172 | doctype: { 173 | title: runnerResult.lighthouseResult.audits['doctype'].title, 174 | description: runnerResult.lighthouseResult.audits['doctype'].description, 175 | score: runnerResult.lighthouseResult.audits['doctype'].score, 176 | displayMode: runnerResult.lighthouseResult.audits['doctype'].scoreDisplayMode, 177 | }, 178 | 'is-on-https': { 179 | title: runnerResult.lighthouseResult.audits['is-on-https'].title, 180 | description: runnerResult.lighthouseResult.audits['is-on-https'].description, 181 | score: runnerResult.lighthouseResult.audits['is-on-https'].score, 182 | displayMode: runnerResult.lighthouseResult.audits['is-on-https'].scoreDisplayMode, 183 | }, 184 | deprecations: { 185 | title: runnerResult.lighthouseResult.audits['deprecations'].title, 186 | description: runnerResult.lighthouseResult.audits['deprecations'].description, 187 | score: runnerResult.lighthouseResult.audits['deprecations'].score, 188 | displayMode: runnerResult.lighthouseResult.audits['deprecations'].scoreDisplayMode, 189 | }, 190 | 'geolocation-on-start': { 191 | title: runnerResult.lighthouseResult.audits['geolocation-on-start'].title, 192 | description: 193 | runnerResult.lighthouseResult.audits['geolocation-on-start'].description, 194 | score: runnerResult.lighthouseResult.audits['geolocation-on-start'].score, 195 | displayMode: 196 | runnerResult.lighthouseResult.audits['geolocation-on-start'].scoreDisplayMode, 197 | }, 198 | 'notification-on-start': { 199 | title: runnerResult.lighthouseResult.audits['notification-on-start'].title, 200 | description: 201 | runnerResult.lighthouseResult.audits['notification-on-start'].description, 202 | score: runnerResult.lighthouseResult.audits['notification-on-start'].score, 203 | displayMode: 204 | runnerResult.lighthouseResult.audits['notification-on-start'].scoreDisplayMode, 205 | }, 206 | 'image-size-responsive': { 207 | title: runnerResult.lighthouseResult.audits['image-size-responsive'].title, 208 | description: 209 | runnerResult.lighthouseResult.audits['image-size-responsive'].description, 210 | score: runnerResult.lighthouseResult.audits['image-size-responsive'].score, 211 | displayMode: 212 | runnerResult.lighthouseResult.audits['image-size-responsive'].scoreDisplayMode, 213 | }, 214 | 'image-aspect-ratio': { 215 | title: runnerResult.lighthouseResult.audits['image-aspect-ratio'].title, 216 | description: runnerResult.lighthouseResult.audits['image-aspect-ratio'].description, 217 | score: runnerResult.lighthouseResult.audits['image-aspect-ratio'].score, 218 | displayMode: 219 | runnerResult.lighthouseResult.audits['image-aspect-ratio'].scoreDisplayMode, 220 | }, 221 | 'password-inputs-can-be-pasted-into': { 222 | title: 223 | runnerResult.lighthouseResult.audits['password-inputs-can-be-pasted-into'].title, 224 | description: 225 | runnerResult.lighthouseResult.audits['password-inputs-can-be-pasted-into'] 226 | .description, 227 | score: 228 | runnerResult.lighthouseResult.audits['password-inputs-can-be-pasted-into'].score, 229 | displayMode: 230 | runnerResult.lighthouseResult.audits['password-inputs-can-be-pasted-into'] 231 | .scoreDisplayMode, 232 | }, 233 | 'errors-in-console': { 234 | title: runnerResult.lighthouseResult.audits['errors-in-console'].title, 235 | description: runnerResult.lighthouseResult.audits['errors-in-console'].description, 236 | score: runnerResult.lighthouseResult.audits['errors-in-console'].score, 237 | displayMode: 238 | runnerResult.lighthouseResult.audits['errors-in-console'].scoreDisplayMode, 239 | }, 240 | }, 241 | seoMetrics: { 242 | viewport: { 243 | title: runnerResult.lighthouseResult.audits['viewport'].title, 244 | description: runnerResult.lighthouseResult.audits['viewport'].description, 245 | score: runnerResult.lighthouseResult.audits['viewport'].score, 246 | displayMode: runnerResult.lighthouseResult.audits['viewport'].scoreDisplayMode, 247 | }, 248 | 'document-title': { 249 | title: runnerResult.lighthouseResult.audits['document-title'].title, 250 | description: runnerResult.lighthouseResult.audits['document-title'].description, 251 | score: runnerResult.lighthouseResult.audits['document-title'].score, 252 | displayMode: runnerResult.lighthouseResult.audits['document-title'].scoreDisplayMode, 253 | }, 254 | 'link-name': { 255 | title: runnerResult.lighthouseResult.audits['link-name'].title, 256 | description: runnerResult.lighthouseResult.audits['link-name'].description, 257 | score: runnerResult.lighthouseResult.audits['link-name'].score, 258 | displayMode: runnerResult.lighthouseResult.audits['link-name'].scoreDisplayMode, 259 | }, 260 | 'http-status-code': { 261 | title: runnerResult.lighthouseResult.audits['http-status-code'].title, 262 | description: runnerResult.lighthouseResult.audits['http-status-code'].description, 263 | score: runnerResult.lighthouseResult.audits['http-status-code'].score, 264 | displayMode: 265 | runnerResult.lighthouseResult.audits['http-status-code'].scoreDisplayMode, 266 | }, 267 | 'meta-description': { 268 | title: runnerResult.lighthouseResult.audits['meta-description'].title, 269 | description: runnerResult.lighthouseResult.audits['meta-description'].description, 270 | score: runnerResult.lighthouseResult.audits['meta-description'].score, 271 | displayMode: 272 | runnerResult.lighthouseResult.audits['meta-description'].scoreDisplayMode, 273 | }, 274 | 'image-alt': { 275 | title: runnerResult.lighthouseResult.audits['image-alt'].title, 276 | description: runnerResult.lighthouseResult.audits['image-alt'].description, 277 | score: runnerResult.lighthouseResult.audits['image-alt'].score, 278 | displayMode: runnerResult.lighthouseResult.audits['image-alt'].scoreDisplayMode, 279 | }, 280 | }, 281 | }; 282 | res.status(200).json(scores); 283 | } 284 | -------------------------------------------------------------------------------- /pages/api/login.ts: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Cookies = require('cookies'); 3 | const bcrypt = require('bcrypt'); 4 | import dbConnect from '../../lib/dbConnect'; 5 | import express, { 6 | Request, 7 | Response, 8 | NextFunction, 9 | ErrorRequestHandler, 10 | } from 'express'; 11 | const User = require('../../models/loginModel'); 12 | import { MongoUser } from '../../types'; 13 | // Api/login 14 | 15 | const handler = async (req: Request, res: Response) => { 16 | await dbConnect(); 17 | const cookies = new Cookies(req, res); 18 | // Check method type ie post/get etc 19 | if (req.method === 'POST') { 20 | const { username, password } = req.body; 21 | // Check if user exists and then compare pass if so 22 | const foundUser:MongoUser = await User.findOne({ username: username }); 23 | if (foundUser) { 24 | console.log('Login username found'); 25 | // User exist 26 | // Checking password with hash password on server 27 | bcrypt.compare( 28 | password, 29 | foundUser.password, 30 | function (err: any, result: any) { 31 | if (err) { 32 | console.log('Error comparing hashed password on login'); 33 | return res 34 | .status(401) 35 | .send('Error comparing hashed password on login'); 36 | } else { 37 | if (result) { 38 | // Login pass correct 39 | // Set Login Cookie 40 | cookies.set('userId', foundUser._id); 41 | // return res.status(200).send('Logged in and new cookie set'); 42 | return res.status(201).redirect('/dashboard'); 43 | } else { 44 | // Login pass wrong 45 | return res.status(401).send('Password wrong'); 46 | } 47 | } 48 | } 49 | ); // End hash password compare 50 | } else { 51 | // create user as they don't exist 52 | console.log('Create user called in login.ts'); 53 | // Assign username / pass to varibles so we can hash pass 54 | type username = string; 55 | type password = string; 56 | const { username, password } = req.body; 57 | // Hashing function with bcrypt 58 | bcrypt.hash(password, 10, async function (err: any, hash: any) { 59 | if (err) { 60 | // Error when hashing password 61 | console.log('Error hashing password'); 62 | return res.status(401).send('Error hashing password'); 63 | } else { 64 | // Store hash in your password DB. 65 | 66 | const newUser = await new User({ 67 | username: username, 68 | password: hash, 69 | }); 70 | await newUser.save(); 71 | // Set Login Cookie 72 | cookies.set('username', username); 73 | return res.status(201).send(`Created user ` + newUser.username); 74 | } 75 | }); 76 | } 77 | } 78 | }; 79 | 80 | export default handler; 81 | -------------------------------------------------------------------------------- /pages/api/newsletter/[newsletter].js: -------------------------------------------------------------------------------- 1 | const mailchimp = require("@mailchimp/mailchimp_marketing"); 2 | 3 | const sendToMailChimp = (req, res) => { 4 | const { EMAIL } = req.body; 5 | mailchimp.setConfig({ 6 | apiKey: process.env.MAILCHIMP_KEY, 7 | server: "us5", 8 | }); 9 | 10 | const listId = "396c103380"; 11 | 12 | 13 | const run = async () => { 14 | const response = await mailchimp.lists.addListMember(listId, { 15 | email_address: EMAIL, 16 | status: "subscribed", 17 | }); 18 | console.log(response); 19 | return res.json(response); 20 | } 21 | 22 | run(); 23 | 24 | } 25 | 26 | export default sendToMailChimp; 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /pages/api/updateuser/index.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | const User = require('../../../models/loginModel'); 3 | import dbConnect from '../../../lib/dbConnect'; 4 | import { Request, Response } from 'express'; 5 | 6 | const updateUserData = async ( 7 | username: string, 8 | updated: string, 9 | reponame: string 10 | ): Promise => { 11 | await dbConnect(); 12 | 13 | // const filter = { _id: username} 14 | // const updateThis = {$set: {'github.repos': [updated]}} 15 | 16 | // try { 17 | // return await User.findOneAndUpdate(filter, updateThis); 18 | // } 19 | // catch (err) { 20 | // return console.log(err); 21 | // } 22 | // const foundUser = await User.findOne({ _id: username }); 23 | // console.log(username); 24 | let currentUser = await User.findOne({ _id: username }); 25 | // console.log(currentUser); 26 | // currentUser.github.repos.push(updated); //push in object 27 | if (currentUser.github.repos[reponame]) { 28 | currentUser.github.repos[reponame].repoPoints.push(updated); 29 | } else { 30 | currentUser.github.repos[reponame] = { repoPoints: [updated] }; 31 | } 32 | 33 | // console.log(currentUser); 34 | 35 | await currentUser.markModified('github'); 36 | await currentUser.save(); 37 | }; 38 | const handler = async (req: Request | any, res: Response) => { 39 | console.log('in update handler'); 40 | console.log(req.body); 41 | const userData = await updateUserData( 42 | req.body.id, 43 | req.body.url, 44 | req.body.reponame 45 | ); 46 | res.send(userData); 47 | }; 48 | 49 | export default handler; 50 | -------------------------------------------------------------------------------- /pages/api/user/[user].ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | const User = require('../../../models/loginModel'); 3 | import dbConnect from '../../../lib/dbConnect'; 4 | import { Request, Response } from 'express'; 5 | 6 | 7 | const getEndPointData = async (username:string):Promise => { 8 | await dbConnect(); 9 | 10 | const foundUser = await User.findOne({ _id: username }); 11 | if (foundUser) { 12 | console.log(username + ' user found'); 13 | console.log(foundUser.endpoints) 14 | return (JSON.stringify(foundUser.endpoints)) 15 | } else { 16 | console.log(username + ' user not found'); 17 | return; 18 | }; 19 | }; 20 | const handler = async (req:Request | any, res:Response) => { 21 | console.log('in api/user handler') 22 | const userData = await getEndPointData(req.query.user);//req.query.user 23 | res.send((userData)) 24 | }; 25 | 26 | export default handler; -------------------------------------------------------------------------------- /pages/components/AboutLH.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../../styles/Docs.module.scss'; 2 | import React, { FC } from 'react'; 3 | import { Heading, VStack } from '@chakra-ui/react'; 4 | 5 | const AboutLH:FC = (props): JSX.Element => { 6 | return ( 7 |
8 | 9 | 10 | What is Google Lighthouse 11 |
12 |

13 | Google Lighthouse is an open-source tool for running technical website audits. It creates a browser environment, and visits a selected endpoint just as any user would, and along the way collects data on key metrics including Performance, Accessibility, Best Practices, and SEO. When Lighthouse finishes running an audit, it can deliver all of the metrics associated on a results page. 14 |

15 |
16 | How does Panoptic use Lighthouse? 17 |
18 |

19 | After a user runs a report with Lighthouse, and the page is exited, that data is lost permanently. If the user wants to store it, they have to do so manually. That is where Panoptic comes in. We take the stress away from having to manually store Lighthouse audits, with the benefit of interactive graphics to give a more in depth look at past reports. 20 |
21 |
22 | The ability to track when and why their performance may have degraded, or skyrocketed assists in the development process. Knowing when these events happen allows Engineers to continue moving their application in the right direction. 23 |

24 |
25 |
26 |
27 | ); 28 | }; 29 | 30 | export default AboutLH; 31 | -------------------------------------------------------------------------------- /pages/components/ControlPanel.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../../styles/Dashboard.module.scss'; 2 | import { Box, HStack, Button } from '@chakra-ui/react'; 3 | import LH_Gauge from './lhGauge'; 4 | import React, { FC } from 'react'; 5 | 6 | const controlPanel: FC = (props: any): JSX.Element => { 7 | const tempArr: JSX.Element[] = []; 8 | // const { lhdata: any } = props; 9 | 10 | if (props.lhdata) 11 | for (const key in props.lhdata) { 12 | if (typeof props.lhdata[key] == 'number') { 13 | // console.log('KEY', key); 14 | tempArr.push( 15 | 37 | ); 38 | }; 39 | }; 40 | return ( 41 | 42 | 43 | {tempArr} 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default controlPanel; 50 | -------------------------------------------------------------------------------- /pages/components/ControlPanelDemo.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../../styles/Demo.module.scss'; 2 | import { Box, HStack, Button } from '@chakra-ui/react'; 3 | import LH_Gauge from './lhGauge'; 4 | import React, { FC } from 'react'; 5 | 6 | const controlPanelDemo: FC = (props: any): JSX.Element => { 7 | const tempArr: JSX.Element[] = []; 8 | if (props.lhdata) 9 | for (const key in props.lhdata) { 10 | if (typeof props.lhdata[key] === 'number') { 11 | tempArr.push( 12 | 31 | ); 32 | } 33 | } 34 | return ( 35 | 36 |
37 | {tempArr} 38 |
39 |
40 | ); 41 | }; 42 | 43 | export default controlPanelDemo; 44 | -------------------------------------------------------------------------------- /pages/components/EndpointsList.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import style from '../../styles/Endpoints.module.scss'; 3 | import React, { FC } from 'react'; 4 | import { v4 as uuidv4 } from 'uuid'; 5 | import { 6 | Box, 7 | Flex, 8 | Button, 9 | Accordion, 10 | AccordionItem, 11 | AccordionPanel, 12 | AccordionButton, 13 | Spacer, 14 | VStack, 15 | Text, 16 | } from '@chakra-ui/react'; 17 | import { SmallAddIcon, SmallCloseIcon, EditIcon } from '@chakra-ui/icons'; 18 | 19 | const EndpointsList: FC = (props: any) => { 20 | const [endpoints, setEndpoints] = useState([]); 21 | 22 | // filter user obj for only endpoints 23 | const filteredObj: any = (): any => { 24 | const test: any = Object.keys(props.endPts).map((key) => key); 25 | // console.log({ test }); 26 | return test; 27 | }; 28 | 29 | // click handler for selecting endpoint 30 | const endpointSelector = (e: any): void => { 31 | props.setSelected(e.target.textContent); 32 | props.func(e, e.target.textContent); 33 | }; 34 | // const endpointsArr: any = filteredObj(); 35 | const arr: JSX.Element[] = []; 36 | for (const key in props.reponames) { 37 | // console.log('props.reponames', props.reponames) 38 | const tempArr: JSX.Element[] = props.reponames[key].map((el: String) => ( 39 | 42 | )); 43 | arr.push( 44 | 45 | 46 | 47 | 48 | {key} 49 | 50 | 51 | 52 | console.log('Add clicked')} /> 53 | 54 | console.log('Add clicked')} /> 55 | 56 | console.log('Del clicked')} /> 57 | 58 | 59 | 60 | {tempArr} 61 | 62 | ); 63 | } 64 | return ( 65 |
66 | 67 |

My Repositories:

68 | {arr} 69 |
70 |
71 | ); 72 | }; 73 | 74 | export default EndpointsList; 75 | -------------------------------------------------------------------------------- /pages/components/FeatureSection.tsx: -------------------------------------------------------------------------------- 1 | import style from '../../styles/Home.module.scss' 2 | import { 3 | Tabs, 4 | TabList, 5 | TabPanels, 6 | Tab, 7 | TabPanel, 8 | Flex, 9 | Spacer, 10 | Text, 11 | Box, 12 | Heading, 13 | StylesProvider, 14 | } from '@chakra-ui/react'; 15 | 16 | const FeatSect = () => { 17 | return ( 18 | 19 | Features 20 | 21 | 22 | Visual Reporting 23 | Compare Metrics Overtime 24 | Github Integration 25 | 26 | 27 | 28 | 29 |

View graphs and gauges that clearly display the key web metrics that are most relevant to the success of your website

30 | Image from Gyazo 31 |
32 | 33 |

View your metrics history over time, allowing you to see the progress or setbacks of your website.

34 | Image from Gyazo 35 |
36 | 37 |

Supercharge Panoptic by connecting your Github, and link your repositories to endpoints.

38 | Image from Gyazo 39 |
40 |
41 |
42 |
43 | ); 44 | }; 45 | export default FeatSect; 46 | -------------------------------------------------------------------------------- /pages/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text, Link } from '@chakra-ui/react'; 2 | import React from 'react'; 3 | 4 | const Footer = () => { 5 | return ( 6 | 15 |
16 | Panoptic Social Media:{' '} 17 | 18 | 19 | Github 20 | 21 |     22 | 23 | LinkedIn 24 | 25 | 26 |
27 | Open-Source Project - Built with Next.js 28 |
29 | ); 30 | }; 31 | 32 | export default Footer; 33 | -------------------------------------------------------------------------------- /pages/components/Hero.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../../styles/Home.module.scss'; 2 | import Link from 'next/link'; 3 | import React, { FC } from 'react'; 4 | import { 5 | Box, 6 | Button, 7 | Flex, 8 | Image, 9 | Spacer, 10 | Text, 11 | useMediaQuery, 12 | } from '@chakra-ui/react'; 13 | 14 | const Hero: FC = (): JSX.Element => { 15 | return ( 16 | 17 |
18 | 19 |

20 | Track Your Web Performance 21 | Metrics Per Commit 22 |

23 | {/*

Refer to the docs for more information

*/} 24 | 25 | 26 | 29 | 30 | 31 | 34 | 35 | 36 |
37 | {/*
*/} 38 |
39 | ); 40 | }; 41 | 42 | export default Hero; 43 | -------------------------------------------------------------------------------- /pages/components/InfoSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Flex, Spacer, Text, Box, Heading } from '@chakra-ui/react'; 3 | import style from '../../styles/Home.module.scss' 4 | import { v4 as uuidv4 } from 'uuid'; 5 | // import { FaTools, FaHandshake, FaStar } from 'react-icons/fa'; 6 | 7 | const AboutUs = () => { 8 | // const [isLargerThan48] = useMediaQuery('(min-width: 48em)'); 9 | 10 | const array = [ 11 | { 12 | title: 'Technical Website Audits ', 13 | info: "Discover powerful insights about the strengths and weaknesses of your website, including: Performance, Accessibility, Best Practices, and SEO" 14 | 15 | }, 16 | { 17 | title: 'Github', 18 | info: 'Login with Github to link your repositories and websites', 19 | info1: 'Keep track of audit metrics over a history of commits' 20 | }, 21 | { 22 | title: 'Visual Reporting', 23 | info: 'View your report with gauges, charts, and interactive lists, making it easy to understand the wealth of information from your report', 24 | }, 25 | ]; 26 | 27 | return ( 28 | 29 | {array.map((arr) => ( 30 |
31 | 48 | {arr.title} 49 | 50 | {arr.info} 51 |

52 | {arr.info1} 53 |
54 |
55 |
56 | ))} 57 |
58 | ); 59 | }; 60 | 61 | export default AboutUs; 62 | -------------------------------------------------------------------------------- /pages/components/Intro.tsx: -------------------------------------------------------------------------------- 1 | // import styles from '../../styles/Home.module.scss'; 2 | // import Link from 'next/link'; 3 | // import React, { FC } from 'react'; 4 | // import { 5 | // Box, 6 | // Button, 7 | // Flex, 8 | // Image, 9 | // Spacer, 10 | // Text, 11 | // useMediaQuery, 12 | // Heading, 13 | // Center, 14 | // Divider, 15 | // } from '@chakra-ui/react'; 16 | 17 | // const Intro: FC = (): JSX.Element => { 18 | // return ( 19 | // 20 | // 32 | // 33 | // Panoptic 34 | // 35 | // 36 | // An innovative performance tracker 37 | // 38 | // 39 | // for Web Applications 40 | // 41 | // 42 | // A cloud based solution for montiering web application over any given 43 | // period of time. 44 | // 45 | 46 | // {/* 47 | // An innovative process of tracking Your Web App Performance Metric 48 | // */} 49 | // 50 | // {/* */} 51 | // 52 | // 53 | // Empowering those that create to
54 | //
Create more efficiently
55 | //
56 | // 57 | // 58 | // 61 | // 62 | // 63 | // 66 | // 67 | // 68 | //
69 | // {/*
*/} 70 | // {/*
*/} 71 | //
72 | // ); 73 | // }; 74 | 75 | // export default Intro; 76 | 77 | import styles from '../../styles/Home.module.scss'; 78 | import Link from 'next/link'; 79 | import React, { FC } from 'react'; 80 | import { 81 | Box, 82 | Button, 83 | Flex, 84 | Image, 85 | Spacer, 86 | Text, 87 | useMediaQuery, 88 | Heading, 89 | Center, 90 | Divider, 91 | } from '@chakra-ui/react'; 92 | 93 | const Intro: FC = (): JSX.Element => { 94 | return ( 95 | 96 | 103 | 104 | Panoptic 105 | 106 | 107 | An innovative performance tracker 108 | 109 | 110 | for Web Applications 111 | 112 | 113 | An intuitive solution for monitoring key web application metrics over 114 | any given period of time 115 | 116 | 117 | {/* 118 | An innovative process of tracking Your Web App Performance Metric 119 | */} 120 | 121 | {/* */} 122 | 123 | 124 | Empowering those that create to
125 | Create more efficiently 126 |
127 | 128 | 129 | 132 | 133 | 134 | 137 | 138 | 139 |
140 |
141 | ); 142 | }; 143 | 144 | export default Intro; 145 | -------------------------------------------------------------------------------- /pages/components/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react'; 2 | import { RingLoader } from 'react-spinners'; 3 | import React, { FC } from 'react'; 4 | 5 | const LoadingSpinner:FC = (): JSX.Element => { 6 | return ( 7 |
8 | 9 | 10 | 11 |
12 | ); 13 | }; 14 | 15 | export default LoadingSpinner; 16 | -------------------------------------------------------------------------------- /pages/components/MainLineChartRE.tsx: -------------------------------------------------------------------------------- 1 | // https://www.chartjs.org/docs/latest/charts/line.html 2 | import { v4 as uuidv4 } from 'uuid'; 3 | import style from '../../styles/Dashboard.module.scss'; 4 | import axios from 'axios'; 5 | import { Line } from 'react-chartjs-2'; 6 | import { MainLCOptions, ChartData } from '../../types'; 7 | import { useState, useEffect, useRef } from 'react'; 8 | import { Progress } from '@chakra-ui/react'; 9 | import { 10 | Chart as ChartJS, 11 | CategoryScale, 12 | LinearScale, 13 | PointElement, 14 | LineElement, 15 | Title, 16 | Tooltip, 17 | Legend, 18 | } from 'chart.js'; 19 | ChartJS.register( 20 | CategoryScale, 21 | LinearScale, 22 | PointElement, 23 | LineElement, 24 | Title, 25 | Tooltip, 26 | Legend 27 | ); 28 | 29 | export const options: any = { 30 | borderWidth: 2, 31 | responsive: true, 32 | maintainAspectRatio: true, 33 | chart: { 34 | width: '100%', 35 | height: '100%', 36 | }, 37 | plugins: { 38 | legend: { 39 | position: 'bottom', 40 | }, 41 | title: { 42 | text: 'Panoptic Line Chart', 43 | }, 44 | }, 45 | scales: { 46 | y: { 47 | type: 'linear', 48 | min: 0, 49 | max: 100, 50 | }, 51 | }, 52 | }; 53 | 54 | const MainLineChartRE = (props: any): JSX.Element => { 55 | const labels: String[] = props.labelTimes; 56 | const arrPerformance: Number[] = [0]; 57 | const arrAccessibility: Number[] = [0]; 58 | const arrBestPractice: Number[] = [0]; 59 | const arrSEO: Number[] = [0]; 60 | 61 | // CHART DATA 62 | const data: ChartData = { 63 | labels, // Array of label names 64 | datasets: [ 65 | { 66 | label: 'Performance', 67 | data: props.performanceData ? props.performanceData : arrPerformance, 68 | borderColor: 'rgb(213, 77, 224)', 69 | backgroundColor: 'rgb(223, 77, 224)', 70 | // showLine: false, // Removes line but leaves dots 71 | pointHoverBackgroundColor: 'black', // Hover DOT background color 72 | pointHoverBorderWidth: 10, // Hover DOT border size 73 | pointHoverRadius: 7, // Hover DOT border size 74 | fill: true, 75 | tension: 0.4, 76 | }, 77 | { 78 | label: 'Accessibility', 79 | data: props.accessibilityData 80 | ? props.accessibilityData 81 | : arrAccessibility, 82 | borderColor: 'rgb(4, 221, 205)', 83 | backgroundColor: 'rgb(4, 221, 205)', 84 | pointHoverBackgroundColor: 'black', // Hover DOT background color 85 | pointHoverBorderWidth: 10, // Hover DOT border size 86 | pointHoverRadius: 7, // Hover DOT border size 87 | fill: true, 88 | tension: 0.4, 89 | }, 90 | { 91 | label: 'Best Practices', 92 | data: props.bestPracticeData ? props.bestPracticeData : arrBestPractice, 93 | borderColor: 'rgb(12, 255, 12)', 94 | backgroundColor: 'rgb(12, 255, 12)', 95 | pointHoverBackgroundColor: 'black', // Hover DOT background color 96 | pointHoverBorderWidth: 10, // Hover DOT border size 97 | pointHoverRadius: 7, // Hover DOT border size 98 | fill: true, 99 | tension: 0.4, 100 | }, 101 | { 102 | label: 'SEO', 103 | data: props.seoData ? props.seoData : arrSEO, 104 | borderColor: 'rgb(250, 83, 128)', 105 | backgroundColor: 'rgb(250, 83, 128)', 106 | pointHoverBackgroundColor: 'black', // Hover DOT background color 107 | pointHoverBorderWidth: 10, // Hover DOT border size 108 | pointHoverRadius: 7, // Hover DOT border size 109 | fill: true, 110 | tension: 0.4, 111 | }, 112 | ], 113 | }; 114 | 115 | return ( 116 |
117 |

{props.selectedEndpoint}

118 |
119 | 120 |
121 |
122 | ); 123 | }; 124 | 125 | export default MainLineChartRE; 126 | -------------------------------------------------------------------------------- /pages/components/Nav.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../../styles/Nav.module.scss'; 2 | import { useState, FC } from 'react'; 3 | import Link from 'next/link'; 4 | import Image from 'next/image'; 5 | import logoPic from '../../assets/PanLogo.png'; 6 | 7 | const Nav: FC = (): JSX.Element => { 8 | const [isLoggedIn, setIsLoggedIn] = useState(false); 9 | 10 | return ( 11 | 28 | ); 29 | }; 30 | 31 | export default Nav; 32 | -------------------------------------------------------------------------------- /pages/components/Newsletter.tsx: -------------------------------------------------------------------------------- 1 | import {useState} from 'react' 2 | import styles from '../../styles/Newsletter.module.scss' 3 | import axios from 'axios'; 4 | 5 | const Newsletter = () => { 6 | const [emailValue, setEmailValue] = useState(''); 7 | const [submitted, setSubmitted] = useState(false); 8 | const [error, setError] = useState(false); 9 | const inputHandler = (e: any) => { 10 | setEmailValue(e.target.value) 11 | } 12 | // send to mailchimp 13 | const subscribeUser = async () => { 14 | const url = '/api/newsletter/1' 15 | // checks if email 16 | if(!emailValue.match( 17 | /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 18 | )) { 19 | return setError(true); 20 | } 21 | await axios.post(url, { 22 | EMAIL: emailValue 23 | }); 24 | setEmailValue(''); 25 | setSubmitted(true); 26 | } 27 | return ( 28 |
29 |

{submitted ? 'Preciate it, g.' : 'Sign Up For Our Newsletter!'}

30 |

Be the first to know the latest updates on Panoptic in Beta! Great idea if you want to become a contributor.

31 |
32 | 33 | 34 |
35 |

NOOOO! BAD EMAIL!

36 |
37 | ) 38 | } 39 | 40 | export default Newsletter -------------------------------------------------------------------------------- /pages/components/Sidenav.tsx: -------------------------------------------------------------------------------- 1 | // import React from 'react' 2 | import Link from 'next/link'; 3 | import styles from '../../styles/Sidenav.module.scss'; 4 | import Image from 'next/image'; 5 | import logoPic from '../../assets/PanLogo.png'; 6 | // import { Show, Hide } from '@chakra-ui/react' 7 | import axios from 'axios'; 8 | import { 9 | Drawer, 10 | DrawerBody, 11 | DrawerHeader, 12 | DrawerOverlay, 13 | DrawerContent, 14 | useDisclosure, 15 | Button, 16 | } from '@chakra-ui/react'; 17 | import { HamburgerIcon } from '@chakra-ui/icons'; 18 | import { useState, useEffect } from 'react'; 19 | 20 | function Sidenav(props: any): JSX.Element { 21 | const { isOpen, onOpen, onClose } = useDisclosure(); 22 | 23 | return ( 24 | 68 | ); 69 | } 70 | 71 | export default Sidenav; 72 | -------------------------------------------------------------------------------- /pages/components/UsingApp.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../../styles/Docs.module.scss'; 2 | import { FC } from 'react'; 3 | import { flexbox, Heading, VStack } from '@chakra-ui/react'; 4 | import Image from 'next/image'; 5 | import repoPic from '../../public/repoPic.png'; 6 | import centerDash from '../../public/centerDash.png'; 7 | import rightSideDash from '../../public/rightSideDash.png'; 8 | const UsingApp: FC = (props): JSX.Element => { 9 | return ( 10 |
11 | 12 | 13 | What is Panoptic? 14 |
15 |

16 | Panoptic is an open-source website metrics tracking and visualization application, made with developers in mind to create a seamless development experience while optimizing your website. 17 |

18 |
19 | Why use Panoptic? 20 |
21 |

22 | During development, it can be difficult to keep track of the performance of your application. There could be some change that is made which happens to degrade the user experience in some way. This is even harder to track when the losses are minute, or are related to something that is hidden unless you are expressely looking for it (i.e. accessibility). Using Panoptic you are able to track the metrics of your application, including: Performance, Accessibility, Best Practices, and SEO, over a long period of time, to catch exactly where and why a regression occurred. 23 |
24 |
25 | With GitHub integration we are able to automatically run a test daily if there has been a commit to an endpoints associated repository, making it simple and effective to visit our application at any point in your development process. 26 |

27 |
28 | Demo 29 |
30 |

31 | If you would like to test the application prior to signing up, please utilize our Demo Page. From there you can run a test on any endpoint with our application. This data is not saved but a new test can be run at any time. 32 |

33 |
34 |
35 | Image from Gyazo 36 |
37 |
38 |

39 | Clicking on one of the scores will then reveal an in depth look at more advanced metrics, including a score and timer for some. 40 |

41 |
42 |
43 | 44 |
45 | Image from Gyazo 46 |
47 |
48 | Making an Account 49 |
50 |

51 | If you would like to have your data stored to be referenced over time, then please head over to the signup/login pages. 52 |
53 |
54 | Log in to the application by making an account with us or sign up with your GitHub account. 55 |
56 |
57 | If you choose to log in with GitHub, then you will have access to organizing your testable endpoints within a list of your existing GitHub Repositories. 58 |

59 |
60 | Dashboard 61 |
62 |

63 | After logging in to the application you will be greeted by our dashboard. On the left side you will see our enpoint selector, which if logged in with GitHub will also include all of your repositories. Here you can add endpoints to, or delete them from your repositories. Be careful when deleting endpoints as this will also delete all associated saved data on them. 64 |
65 |
66 |

67 | hi 68 |
69 |
70 |
71 | In the center of the page we have our graphing tools, upon first load it will always show the most recent test ran on the first endpoint of your first listed repository. 72 |
73 |
74 |
75 | center of dashboard 76 |
77 |
78 |
79 | On the right side of the page is our in depth metrics. These are the same that you would find on the demo page, the include an in depth look at your webpage and a breakdown of what is going well vs what can be improved upon. If you expand them you will find a brief discription about them and a link to the associated Lighthouse documentation for more information. 80 |
81 |
82 |
83 | right side of dashboard 84 |
85 |
86 |
87 |
88 |

89 |
90 |
91 |
92 | ); 93 | }; 94 | 95 | export default UsingApp; 96 | -------------------------------------------------------------------------------- /pages/components/UsingMetrics.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../../styles/Docs.module.scss'; 2 | import { FC } from 'react'; 3 | import { Heading, VStack } from '@chakra-ui/react'; 4 | 5 | const UsingMetrics: FC = (props): JSX.Element => { 6 | return ( 7 |
8 | 9 | 10 | After Running an Audit 11 |
12 |

13 | After you run an audit you are greeted with quite a bit of data. Lighthouse breaks this down into four categories: 14 |

15 |
16 | Primary Categories 17 |

18 |
19 | Performance - is a measure of how long it takes a website to load, and is timed at several different events including when the first images and text appear, last images and text appear, time before the page is interactive, and many others. 20 |
21 |
22 | Accessibility - shows how easily accessable your website is by the average person, as well as how someone with a disability could navigate the page. It checks if you have followed the ARIA recommendations and that everything on the page is easily readbable by someone using a screen reader. 23 |
24 |
25 | Best Practices - checks the application against recommended coding practices. Including website security, efficiency, and up to date coding libraries/dependencies. 26 |
27 |
28 | SEO - ensures your application is showing up in search results for related entities. Checks data on the page on first page load, if the page is legible, makes sure links are valid and have descriptive text surrounding them, among other things. 29 |

30 |
31 | {/* Additional Performance Metrics 32 |
33 |

34 | The performance category is further broken down into an additional six cetegories: 35 |
36 | First Contentful Paint - measures how long it takes the browser to render the first piece of DOm content after a user navigates to your page. Images, non-white canvas elements, and SVGs on are considered DOM content, anything inside an iframe is not included. 37 |
38 | Largest Contentful Paint - reports the render time of the 39 |

*/} 40 |
41 |
42 |
43 | ); 44 | }; 45 | 46 | export default UsingMetrics; 47 | -------------------------------------------------------------------------------- /pages/components/employeeID.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../../styles/Home.module.scss'; 2 | import { FaGithub, FaLinkedin } from 'react-icons/fa'; 3 | import React, { FC } from 'react'; 4 | 5 | const employeeID: FC = (props: any): JSX.Element => { 6 | const githubIcon: JSX.Element = ; 7 | const linkedinIcon: JSX.Element = ( 8 | 9 | ); 10 | return ( 11 |
12 |
13 |

{props.employee}

14 | 22 |
23 | ); 24 | }; 25 | 26 | export default employeeID; 27 | -------------------------------------------------------------------------------- /pages/components/lhGauge.tsx: -------------------------------------------------------------------------------- 1 | import { Chart as ChartJs, Tooltip, Title, ArcElement, Legend } from 'chart.js'; 2 | import { useState, useEffect } from 'react'; 3 | import { Doughnut } from 'react-chartjs-2'; 4 | import { Box } from '@chakra-ui/react'; 5 | import React, { FC } from 'react'; 6 | import { ChartData, LHGaugeOptions } from '../../types'; 7 | 8 | ChartJs.register(Tooltip, Title, ArcElement, Legend); 9 | 10 | function lhGauge(props: any): JSX.Element { 11 | const data: any = { 12 | labels: ['%', '-'], 13 | datasets: [ 14 | { 15 | label: 'My First Dataset', 16 | data: [props.score, 100 - props.score], 17 | backgroundColor: ['#266ef6', 'white'], 18 | 19 | borderAlign: 'center', 20 | }, 21 | ], 22 | }; 23 | 24 | const options: any = { 25 | responsive: true, 26 | maintainAspectRatio: false, 27 | plugins: { 28 | legend: { 29 | display: false, 30 | }, 31 | title: { 32 | display: true, 33 | text: props.title, 34 | font: { size: 18 }, 35 | color: 'white', 36 | padding: { 37 | top: 5, 38 | bottom: 5, 39 | }, 40 | }, 41 | }, 42 | }; 43 | 44 | const plugins: any = [ 45 | { 46 | beforeDraw: function (chart: any) { 47 | // beforeRender: function (chart) { 48 | let width = chart.width, 49 | height = chart.height, 50 | ctx = chart.ctx; 51 | ctx.restore(); 52 | let fontSize = (height / 160).toFixed(2); 53 | ctx.font = fontSize + 'em sans-serif'; 54 | ctx.fillStyle = 'white'; 55 | ctx.textAlign = 'center'; 56 | ctx.textBaseline = 'middle'; 57 | let text = chart.data.datasets[0].data[0] + '%', 58 | textX = (chart.chartArea.left + chart.chartArea.right) / 2, 59 | textY = (chart.chartArea.top + chart.chartArea.bottom) / 2; 60 | ctx.fillText(text, textX, textY); 61 | ctx.save(); 62 | }, 63 | }, 64 | ]; 65 | 66 | return ( 67 | 68 | 74 | 75 | ); 76 | }; 77 | 78 | export default lhGauge; 79 | -------------------------------------------------------------------------------- /pages/components/wrightDetails.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Accordion, 3 | AccordionItem, 4 | AccordionButton, 5 | AccordionPanel, 6 | AccordionIcon, 7 | Box, 8 | propNames, 9 | } from '@chakra-ui/react'; 10 | import style from '../../styles/Dashboard.module.scss'; 11 | import { FC } from 'react'; 12 | 13 | const wrightDetails = (props: any) => { 14 | const tempArr: JSX.Element[] = []; 15 | let metrics; 16 | 17 | if (props.user && props.selectedEndpoint !== 'Select An Endpoint' && !props.user[props.selectedEndpoint]) { 18 | tempArr.push( 19 | 20 |

The endpoint that you selected has no data, try running a test on the endpoint

21 |
22 | ) 23 | } 24 | 25 | // extract the most current date of the user's history 26 | else if (props.user && props.selectedEndpoint !== 'Select An Endpoint') { 27 | // typing is a bit odd here, will come back to this 28 | const mainObj: any = props.user[props.selectedEndpoint]['desktop']; 29 | const dateArr: string[] = Object.keys(mainObj); 30 | const recentDate: string = dateArr[dateArr.length - 1]; 31 | metrics = mainObj[recentDate].metrics; 32 | } 33 | 34 | if (metrics) { 35 | for (const i in metrics[props.selectedMetric]) { 36 | const fullDescription = metrics[props.selectedMetric][i].description; 37 | const descritpionText = fullDescription.substring( 38 | 0, 39 | fullDescription.indexOf('Learn more') - 1 40 | ); 41 | const descriptionLink = fullDescription.substring( 42 | fullDescription.indexOf('https'), 43 | fullDescription.length - 2 44 | ); 45 | let elementStyle: string; 46 | metrics[props.selectedMetric][i].score < 1 47 | ? (elementStyle = style.detailElementFlaw) 48 | : (elementStyle = style.detailElement); 49 | let myCard: JSX.Element = ( 50 | 51 |

52 | 53 | 54 | {metrics[props.selectedMetric][i].title} 55 |

{`Score: ${Math.round( 56 | metrics[props.selectedMetric][i].score * 100 57 | )}`}

58 |
59 | 60 |
61 |

62 | 63 | {descritpionText} 64 | 65 |
Learn More
66 |
67 |
68 |
69 | ); 70 | tempArr.push(myCard); 71 | } 72 | } 73 | 74 | return ( 75 | 76 | {tempArr} 77 | 78 | ); 79 | }; 80 | 81 | export default wrightDetails; 82 | -------------------------------------------------------------------------------- /pages/components/wrightDetailsDemo.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Accordion, 3 | AccordionItem, 4 | AccordionButton, 5 | AccordionPanel, 6 | AccordionIcon, 7 | Box, 8 | } from '@chakra-ui/react'; 9 | import style from '../../styles/Demo.module.scss'; 10 | import { FC } from 'react'; 11 | 12 | const wrightDetailsDemo: FC = (props: any) => { 13 | const tempArr: JSX.Element[] = []; 14 | let metrics; 15 | if (props.data) { 16 | metrics = props.data; 17 | } 18 | 19 | if (metrics) { 20 | for (let i in metrics[props.selectedMetric]) { 21 | const fullDescription = metrics[props.selectedMetric][i].description; 22 | const descritpionText = fullDescription.substring( 23 | 0, 24 | fullDescription.indexOf('Learn more') - 1 25 | ); 26 | const descriptionLink = fullDescription.substring( 27 | fullDescription.indexOf('https'), 28 | fullDescription.length - 2 29 | ); 30 | 31 | let elementStyle; 32 | metrics[props.selectedMetric][i].score < 1 33 | ? (elementStyle = style.detailElementFlaw) 34 | : (elementStyle = style.detailElement); 35 | let myCard: JSX.Element = ( 36 | 37 |

38 | 39 | 44 |
{metrics[props.selectedMetric][i].title}
45 |
53 |

54 | {metrics[props.selectedMetric][i].displayValue !== 'numeric' 55 | ? metrics[props.selectedMetric][i].displayValue 56 | : ''} 57 |

58 |

{`Score: ${Math.round( 59 | metrics[props.selectedMetric][i].score * 100 60 | )}`}

61 |
62 |
63 | 64 |
65 |

66 | 67 | {descritpionText} 68 | 69 |
Learn More
70 |
71 |
72 |
73 | ); 74 | tempArr.push(myCard); 75 | } 76 | } 77 | 78 | return ( 79 | 80 | {tempArr} 81 | 82 | ); 83 | }; 84 | 85 | export default wrightDetailsDemo; 86 | -------------------------------------------------------------------------------- /pages/dashboard.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../styles/Dashboard.module.scss'; 2 | import ControlPanel from './components/ControlPanel'; 3 | import WrightDetails from './components/wrightDetails'; 4 | import EndpointsList from './components/EndpointsList'; 5 | import { useState, useEffect, useRef } from 'react'; 6 | import { Box, VStack, Grid, GridItem, Heading, Center } from '@chakra-ui/react'; 7 | import MainLineChartRE from './components/MainLineChartRE'; 8 | import { RingLoader } from 'react-spinners'; 9 | import axios from 'axios'; 10 | import type { NextPage } from 'next'; 11 | import { parseCookies } from '../lib/parseCookies'; 12 | import { Octokit } from 'octokit'; 13 | import Sidenav from './components/Sidenav'; 14 | 15 | // when we make the api call to back end 16 | // need to pass url, last commit, reponame, and platform(mobile/desktop) 17 | // req.body.reponame, req.body.url, req.body.commit, req.body.platform 18 | 19 | const Dashboard: NextPage = ({ initialRememberValue }: any) => { 20 | // const = props; 21 | 22 | // type currentUser = any; 23 | // type lighthouseData = any; 24 | const [currentUser, setCurrentUser] = useState({}); 25 | const [userData, setUserData] = useState({}); 26 | const [lighthouseData, setLighthouseData] = useState({ 27 | performance: 0, 28 | accessibility: 0, 29 | bestPractices: 0, 30 | seo: 0, 31 | }); 32 | 33 | const [selected, setSelected] = useState('Select An Endpoint'); 34 | const [scores, setScores] = useState( 35 | 36 | 37 | 38 | ); 39 | 40 | const [GaugeData, setGaugeData] = useState({}); 41 | const didMount = useRef(false); 42 | // const repoNames: { Other: string[] }|{} = { Other: [] }; 43 | const repoNames: {} = { Other: [] }; 44 | 45 | // Get Current User 46 | const getUser = async (): Promise => { 47 | const result: any = await axios.get(`/api/user/${initialRememberValue}`); 48 | setCurrentUser(result.data); 49 | 50 | const foundUserData: any = await axios.post(`/api/finduser/`, { 51 | username: initialRememberValue, 52 | }); 53 | 54 | setUserData(foundUserData.data); 55 | 56 | if (Object.keys(result.data).length > 0) { 57 | let keyEndpoint = Object.keys(result.data)[0]; 58 | // console.log('logging var', keyEndpoint); 59 | let keyDesktop: any = Object.keys(result.data[keyEndpoint]); 60 | // console.log('logging var', keyDesktop); 61 | let keyDate = Object.keys(result.data[keyEndpoint][keyDesktop]); 62 | // console.log('logging var', keyDate); 63 | setLighthouseData( 64 | result.data[keyEndpoint][keyDesktop][keyDate[keyDate.length - 1]] 65 | .metrics 66 | ); 67 | // console.log(result.data[keyEndpoint]); 68 | loadEndPointDataToChart(result.data[keyEndpoint], keyEndpoint); 69 | } 70 | didMount.current = true; 71 | }; 72 | 73 | useEffect((): void => { 74 | getUser(); 75 | }, []); 76 | 77 | useEffect((): void => { 78 | if (didMount.current) { 79 | // console.log('LHDATA IN USEEFFECT', lighthouseData); 80 | setScores( 81 |
82 | 87 |
88 | ); 89 | } 90 | }, [ 91 | lighthouseData, 92 | lighthouseData.performance, 93 | lighthouseData.accessibility, 94 | lighthouseData.bestPractices, 95 | lighthouseData.seo, 96 | ]); 97 | 98 | const helperFunc = async () => { 99 | const urlData: any = document.querySelector('#urlData'); 100 | setScores( 101 | 102 | 103 | Data Loading 104 |
105 | 106 |
107 |
108 |
109 | ); 110 | // get data from lighthouse api 111 | await axios 112 | .post(`/api/lighthouse`, { 113 | url: urlData.value, 114 | }) 115 | .then((result: any) => { 116 | didMount.current = true; 117 | setLighthouseData(result.data); 118 | }); 119 | urlData.value = ''; 120 | }; 121 | 122 | const [selectedMetric, setSelectedMetric] = useState('seoMetrics'); 123 | const [performanceData, setPerformanceData] = useState(null); 124 | const [seoData, setSeoData] = useState(null); 125 | const [bestPracticeData, setBestPracticeData] = useState(null); 126 | const [accessibilityData, setAccessibilityData] = useState(null); 127 | const [times, setTimes] = useState([ 128 | '0', 129 | '1', 130 | '2', 131 | '3', 132 | '4', 133 | '5', 134 | '6', 135 | '7', 136 | '8', 137 | '9', 138 | ]); 139 | 140 | const loadEndPointDataToChart = (e = currentUser, defaultKey: any): void => { 141 | const performanceArray: number[] = []; 142 | const seoArray: number[] = []; 143 | const accessibilityArray: number[] = []; 144 | const bestPracticeArray: number[] = []; 145 | let arrOfTime = []; 146 | let defaultList: any; 147 | let tempLatestVAl; 148 | let date; 149 | // console.log(e); 150 | // console.log(currentUser); 151 | if (currentUser[defaultKey]) { 152 | // console.log('LINE 147', currentUser[defaultKey]); 153 | for (const date in currentUser[defaultKey].desktop) { 154 | if (date != 'reponame') { 155 | // console.log(currentUser[defaultKey][date].metrics.performance); 156 | 157 | performanceArray.push( 158 | currentUser[defaultKey].desktop[date].metrics.performance 159 | ); 160 | seoArray.push(currentUser[defaultKey].desktop[date].metrics.seo); 161 | accessibilityArray.push( 162 | currentUser[defaultKey].desktop[date].metrics.accessibility 163 | ); 164 | bestPracticeArray.push( 165 | currentUser[defaultKey].desktop[date].metrics.bestPractices 166 | ); 167 | } 168 | } 169 | if (arrOfTime.length < 8) 170 | // CONSOLE LOG BATTLES 171 | arrOfTime.push( 172 | Object.keys(currentUser[defaultKey].desktop).map((el) => el) 173 | ); 174 | // console.log('ARR?', arrOfTime); 175 | // console.log('what we pushing?', currentUser[defaultKey].desktop); // ROUND 1 - correct 176 | defaultList = Object.keys(currentUser[defaultKey]); // ROUND 2 - desktop - got it 177 | // console.log('defList', defaultList); // ROUND 3 - Low Level Boss Fight - correct 178 | date = Object.keys(currentUser[defaultKey][defaultList]); // date 179 | // console.log('DATE ', date); 180 | // console.log( 181 | // 'FINAL BOSS FIGHT: ', 182 | // currentUser[defaultKey][defaultList][date] 183 | // ); 184 | // console.log( 185 | // 'FINAL BOSS FIGHT 2: ', 186 | // currentUser[defaultKey][defaultList][date[date.length - 1]].metrics 187 | // ); 188 | setLighthouseData( 189 | currentUser[defaultKey][defaultList][date[date.length - 1]].metrics 190 | ); 191 | } 192 | 193 | setAccessibilityData([...accessibilityArray]); 194 | setSeoData([...seoArray]); 195 | setBestPracticeData([...bestPracticeArray]); 196 | setPerformanceData([...performanceArray]); 197 | if (arrOfTime[0]) { 198 | arrOfTime[0].length === 1 199 | ? setTimes([...arrOfTime[0], ...arrOfTime[0]]) 200 | : setTimes([...arrOfTime[0]]); 201 | } 202 | }; 203 | 204 | if (userData.github) { 205 | // repoNames.repos = []; 206 | // // console.log(userData); 207 | // for (let i = 0; i < userData.github.repos.length; i++) { 208 | for (let key in userData.github.repos) { 209 | // const name = Object.keys(userData.github.repos[i])[0]; 210 | repoNames[key] = []; 211 | // console.log(userData.github.repos[key]); 212 | for (let item of userData.github.repos[key].repoPoints) { 213 | repoNames[key].push(item); 214 | } 215 | // console.log(key); 216 | } 217 | for (let endpoint in userData.endpoints) { 218 | // console.log(endpoint); 219 | // console.log(repoNames['Other']); 220 | repoNames['Other'].push(endpoint); 221 | // console.log(repoNames['Other']); 222 | } 223 | //pass 224 | } else { 225 | for (let endpoint in userData.endpoints) { 226 | // console.log(endpoint); 227 | // console.log(repoNames['Other']); 228 | repoNames['Other'].push(endpoint); 229 | // console.log(repoNames['Other']); 230 | } 231 | } 232 | // console.log(repoNames); 233 | 234 | return ( 235 | // className={styles.Dashboard} 236 |
237 | { userData.github ? : <> } 238 | 245 | 246 | 247 |

Enter New Endpoint Below

248 | 255 | 258 | 267 |
268 |
269 | 270 | 271 | 272 | {scores} 273 | 274 | 281 | 282 | 283 | 284 | 285 | 286 |
287 |

288 | {selected} |{' '} 289 | {selectedMetric === 'seoMetrics' 290 | ? 'SEO Metrics' 291 | : selectedMetric === 'bestPracticesMetrics' 292 | ? 'Best Practices Metrics' 293 | : selectedMetric === 'accessibilityMetrics' 294 | ? 'Accessibility Metrics' 295 | : 'Performance Metrics'} 296 |

297 | 302 |
303 |
304 |
305 |
306 | ); 307 | }; 308 | 309 | Dashboard.getInitialProps = async ({ 310 | req, 311 | }: any): Promise<{ initialRememberValue: string }> => { 312 | // Parseing cookie with our own function so we can read it 313 | const cookies = parseCookies(req); 314 | // Return our cookie and grab name from cookie 315 | return { 316 | initialRememberValue: cookies.userId, 317 | }; 318 | }; 319 | 320 | export default Dashboard; 321 | -------------------------------------------------------------------------------- /pages/demo.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next'; 2 | import { useState, useEffect, useRef } from 'react'; 3 | import logoPic from '../assets/PanLogo.png'; 4 | import Link from 'next/link'; 5 | import Image from 'next/image'; 6 | import LH_Gauge from './components/lhGauge'; 7 | import ControlPanelDemo from './components/ControlPanelDemo'; 8 | import WrightDetailsDemo from './components/wrightDetailsDemo'; 9 | import styles from '../styles/Demo.module.scss'; 10 | import Sidenav from './components/Sidenav'; 11 | import axios from 'axios'; 12 | import { parseCookies } from '../lib/parseCookies'; 13 | import EndpointsList from './components/EndpointsList'; 14 | import { 15 | Flex, 16 | Box, 17 | VStack, 18 | Center, 19 | Heading, 20 | HStack, 21 | Progress, 22 | } from '@chakra-ui/react'; 23 | import { RingLoader } from 'react-spinners'; 24 | import { any } from 'webidl-conversions'; 25 | import { ClassNames } from '@emotion/react'; 26 | 27 | const DataTest: NextPage = ( 28 | { initialRememberValue }, 29 | props: any 30 | ): JSX.Element => { 31 | const [lighthouseData, setLighthouseData] = useState({ 32 | performance: 0, 33 | accessibility: 0, 34 | bestPractices: 0, 35 | seo: 0, 36 | }); 37 | const [scores, setScores] = useState( 38 | 39 | 40 | Waiting For Tests... 41 | 42 | 43 | ); 44 | // selected metric type state 45 | const [selectedMetric, setSelectedMetric] = useState('performanceMetrics'); 46 | const didMount = useRef(false); 47 | 48 | const helperFunc = async (): Promise => { 49 | const urlData: any = document.querySelector('#urlData'); 50 | setScores( 51 | 52 | 53 | Data Loading 54 |
55 | 56 |
57 |
58 |
59 | ); 60 | // get data from lighthouse api 61 | await fetch(`/api/lighthousedemo`, { 62 | method: 'POST', 63 | headers: { 64 | 'Content-Type': 'application/json', 65 | }, 66 | body: JSON.stringify(urlData.value), 67 | }) 68 | .then((res) => res.json()) 69 | .then((data) => { 70 | didMount.current = true; 71 | setLighthouseData(data); 72 | }); 73 | // clear input value after clicking 74 | urlData.value = ''; 75 | }; 76 | 77 | useEffect((): void => { 78 | if (didMount.current) { 79 | setScores( 80 |
81 | 86 |
87 | ); 88 | } 89 | }, [ 90 | lighthouseData.performance, 91 | lighthouseData.accessibility, 92 | lighthouseData.bestPractices, 93 | lighthouseData.seo, 94 | ]); 95 | 96 | return ( 97 |
98 |
99 |
100 | 101 | Panoptic Logo 106 | 107 |
108 |
109 |

Enter New Endpoint Below

110 | 117 | 125 |
126 |
127 | 128 |
129 |
{scores}
130 |
131 |

132 | {selectedMetric !== 'seoMetrics' && 133 | selectedMetric !== 'bestPracticesMetrics' 134 | ? selectedMetric[0].toUpperCase() + 135 | (selectedMetric.substring(1, selectedMetric.length - 7) + 136 | ' Metrics') 137 | : selectedMetric === 'seoMetrics' 138 | ? 'SEO Metrics' 139 | : 'Best Practices Metrics'} 140 |

141 | 145 |
146 |
147 |
148 |
149 |

Welcome to Panoptic!

150 |

151 | Enter a url into the search bar, and then click Run 152 | Tests to begin! 153 |

154 |

155 | You can change the details list to show specific metric types by 156 | clicking on the corresponding gauges at the top of the dashboard! 157 |

158 |
159 |
160 |
161 | ); 162 | }; 163 | DataTest.getInitialProps = async ({ 164 | req, 165 | }: any): Promise<{ initialRememberValue: string }> => { 166 | // Parseing cookie with our own function so we can read it 167 | const cookies = parseCookies(req); 168 | // Return our cookie and grab name from cookie 169 | return { 170 | initialRememberValue: cookies.userId, 171 | }; 172 | }; 173 | export default DataTest; 174 | -------------------------------------------------------------------------------- /pages/docs.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next'; 2 | import Nav from './components/Nav'; 3 | import styles from '../styles/Docs.module.scss'; 4 | import { useState } from 'react'; 5 | import React from 'react'; 6 | import UsingApp from './components/UsingApp'; 7 | import AboutLH from './components/AboutLH'; 8 | import UsingMetrics from './components/UsingMetrics'; 9 | 10 | const Docs: NextPage = (): JSX.Element => { 11 | const [activeBtn, setActiveBtn] = useState(0); 12 | let buttons:JSX.Element = ( 13 |
14 |
15 |
    16 |
  • 17 | 25 |
  • 26 |
  • 27 | 34 |
  • 35 |
  • 36 | 43 |
  • 44 |
45 |
46 |
47 | 48 |
49 |
50 | ); 51 | if (activeBtn === 0) { 52 | buttons = ( 53 |
54 |
55 |
    56 |
  • 57 | 65 |
  • 66 |
  • 67 | 74 |
  • 75 |
  • 76 | 83 |
  • 84 |
85 |
86 |
87 | 88 |
89 |
90 | ); 91 | }; 92 | if (activeBtn === 1) { 93 | buttons = ( 94 |
95 |
96 |
    97 |
  • 98 | 105 |
  • 106 |
  • 107 | 115 |
  • 116 |
  • 117 | 124 |
  • 125 |
126 |
127 |
128 | 129 |
130 |
131 | ); 132 | }; 133 | if (activeBtn === 2) { 134 | buttons = ( 135 |
136 |
137 |
    138 |
  • 139 | 146 |
  • 147 |
  • 148 | 155 |
  • 156 |
  • 157 | 165 |
  • 166 |
167 |
168 |
169 | 170 |
171 |
172 | ); 173 | }; 174 | return ( 175 |
176 |
179 | ); 180 | }; 181 | 182 | export default Docs; 183 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next'; 2 | import Nav from './components/Nav'; 3 | import Link from 'next/link'; 4 | import styles from '../styles/Home.module.scss'; 5 | import Newsletter from './components/Newsletter'; 6 | import Intro from './components/Intro'; 7 | import InfoSect from './components/InfoSection'; 8 | import FeatureSect from './components/FeatureSection'; 9 | import EmployeeID from './components/employeeID'; 10 | import Footer from './components/Footer'; 11 | 12 | const Home: NextPage = (): JSX.Element => { 13 | return ( 14 |
15 |
59 | ); 60 | }; 61 | 62 | export default Home; 63 | -------------------------------------------------------------------------------- /pages/login.tsx: -------------------------------------------------------------------------------- 1 | // import styles from '../styles/Home.module.css'; 2 | import styles from '../styles/Login.module.scss'; 3 | import Nav from './components/Nav'; 4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 5 | import { FaGithub, FaLinkedin } from 'react-icons/fa'; 6 | import Link from 'next/link'; 7 | // Login page serving file 8 | function LoginPage(): JSX.Element { 9 | const githubIcon: JSX.Element = ; 10 | return ( 11 |
12 |
52 | ); 53 | } 54 | 55 | export default LoginPage; 56 | -------------------------------------------------------------------------------- /pages/manager.tsx: -------------------------------------------------------------------------------- 1 | import Sidenav from './components/Sidenav'; 2 | import styles from '../styles/Manager.module.scss'; 3 | import { NextPage } from 'next'; 4 | 5 | const Manager: NextPage = (): JSX.Element => { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default Manager; 14 | -------------------------------------------------------------------------------- /pages/repoendpoints.tsx: -------------------------------------------------------------------------------- 1 | import Sidenav from './components/Sidenav'; 2 | import styles from '../styles/Repoendpoints.module.scss'; 3 | import axios from 'axios'; 4 | import { Octokit } from 'octokit'; 5 | import { parseCookies } from '../lib/parseCookies'; 6 | import { useState, useEffect } from 'react'; 7 | import { v4 as uuidv4 } from 'uuid'; 8 | import { NextPage } from 'next'; 9 | // const User = require('../models/loginModel.ts'); 10 | 11 | const Repoendpoints: NextPage = ({ 12 | initialRememberValue, 13 | }: any): JSX.Element => { 14 | const [repos, setRepos] = useState([false]); // lists their repos 15 | const [repoClicked, setRepoClicked] = useState(null); // self explanatory...displays repo clicked 16 | const [inputToggle, setInputToggle] = useState(false); // will display if toggle true 17 | const [inputValue, setInputValue] = useState(''); // get input value onChange 18 | 19 | /********************** 20 | function to toggle inputToggle state 21 | ************************/ 22 | const toggleInput = () => { 23 | inputToggle ? setInputToggle(false) : setInputToggle(true); 24 | }; 25 | 26 | /*************************** 27 | function to get inputValue 28 | passed into input's onChange 29 | React way of doing a document.querySelector(input).value 30 | ***************************/ 31 | const inputChange = async (e: any) => { 32 | setInputValue(e.target.value); 33 | }; 34 | 35 | /*********************************************** 36 | function for clicking a repo to link, 37 | should open up an input menu, then save to DB 38 | ***********************************************/ 39 | const selectRepo = (e: any) => { 40 | setRepoClicked(e.target.textContent); 41 | toggleInput(); 42 | }; 43 | 44 | /*********************************** 45 | function for updating user repos 46 | - should happen on click of LINK button 47 | - send repoClicked and inputValue 48 | ***********************************/ 49 | const updateUserRepos = async () => { 50 | // const urlname: string = inputValue; 51 | // console.log(urlname.value); 52 | const result = await axios.post('/api/updateuser/', { 53 | id: initialRememberValue, 54 | url: inputValue, 55 | reponame: repoClicked, 56 | }); 57 | // console.log({ result }); 58 | // const users = require('../models/loginModel.ts'); 59 | // let currentUser = await users.findOne({ _id: initialRememberValue }); 60 | }; 61 | 62 | /*************************************************** 63 | getting commit history for a specific repo using Octokit 64 | (Github recommended) 65 | ****************************************************/ 66 | const printInfo = async () => { 67 | // get user access token 68 | const result = await axios.post(`/api/finduser/`, { 69 | username: initialRememberValue, 70 | }); 71 | 72 | // store the github access token in a variable 73 | const token = result.data.github.token; 74 | // console.log(token); 75 | // octokit (just cuz github used examples with this and it seemed simpler) 76 | const octokit = new Octokit({ auth: `${token}` }); // give auth the token as a string 77 | // store repos result in variable 78 | // console.log(octokit); 79 | const test = await octokit.request(`GET /user/repos`); 80 | // Loop through array of objects (result (test)) 81 | const displayRepos: any = []; 82 | for (const key of test.data) { 83 | displayRepos.push( 84 |
  • 85 | {key.name} 86 |
  • 87 | ); 88 | } 89 | // State will update and display list of repos 90 | setRepos(displayRepos); 91 | }; 92 | 93 | // start getting repos when component renders 94 | useEffect(() => { 95 | printInfo(); 96 | }, []); 97 | 98 | return ( 99 |
    100 | {/* Sidenav should get passed the userData if you want the github info to display on it */} 101 | 102 | 103 | {/* The Card that is initially hidden until you click on a repo */} 104 |
    105 |
    106 |

    107 | Enter an Endpoint for{' '} 108 | {repoClicked} 109 |

    110 | 113 | 120 | {/* button to update user repo on click */} 121 | 124 |
    125 |
    126 | 127 | {/* Container For List Of Repos */} 128 |
    129 |

    Select A Repo to Link with an Endpoint

    130 |
      131 | {/* logic to check if repos is false-y, 132 | displays 'loading repos...' or something 133 | (which could be empty if they have no repos, so...) */} 134 | {repos[0] ? repos : 'Loading Repos...'} 135 |
    136 |
    137 |
    138 | ); 139 | }; 140 | 141 | // Was gonna use this just to put the github info on the sidenav... 142 | 143 | // cookies 144 | Repoendpoints.getInitialProps = async ({ req }: any) => { 145 | // Parseing cookie with our own function so we can read it 146 | const cookies = parseCookies(req); 147 | // Return our cookie and grab name from cookie 148 | return { 149 | initialRememberValue: cookies.userId, 150 | }; 151 | }; 152 | 153 | export default Repoendpoints; 154 | -------------------------------------------------------------------------------- /pages/signup.tsx: -------------------------------------------------------------------------------- 1 | import { signIn, signOut, useSession, getSession } from 'next-auth/react'; 2 | // import styles from '../styles/Home.module.css'; 3 | import styles from '../styles/Login.module.scss'; 4 | import Nav from './components/Nav'; 5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 6 | import { FaGithub, FaLinkedin } from 'react-icons/fa'; 7 | import { Box } from '@chakra-ui/react'; 8 | 9 | // Sign Up page serving file 10 | function signupPage():JSX.Element { 11 | const githubIcon = ; 12 | // const { data: session, status } = useSession(); 13 | 14 | return ( 15 |
    16 |
    80 | ); 81 | }; 82 | 83 | export default signupPage; 84 | -------------------------------------------------------------------------------- /panoptic-nextjs-docs/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 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 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 | -------------------------------------------------------------------------------- /public/centerDash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Panoptic/478f453a44790dcd7a1555380edae989fee83c23/public/centerDash.png -------------------------------------------------------------------------------- /public/icons8-watchtower-96.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Panoptic/478f453a44790dcd7a1555380edae989fee83c23/public/icons8-watchtower-96.ico -------------------------------------------------------------------------------- /public/icons8-watchtower-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Panoptic/478f453a44790dcd7a1555380edae989fee83c23/public/icons8-watchtower-96.png -------------------------------------------------------------------------------- /public/repoPic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Panoptic/478f453a44790dcd7a1555380edae989fee83c23/public/repoPic.png -------------------------------------------------------------------------------- /public/rightSideDash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Panoptic/478f453a44790dcd7a1555380edae989fee83c23/public/rightSideDash.png -------------------------------------------------------------------------------- /styles/Dashboard.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | .Dashboard { 4 | // display: grid; 5 | // grid-template-columns: 1fr 3fr 1fr; 6 | height: 100vh; 7 | width: 100vw; 8 | max-height: 100vh; 9 | max-width: 100vw; 10 | background: linear-gradient(45deg, 11 | $dark-blue 0%, 12 | $primary-blue 20%, 13 | $primary-blue 45%, 14 | $secondary-blue 70%, 15 | $dark-blue 100%); 16 | overflow: hidden; 17 | } 18 | 19 | .dataLoading { 20 | margin-bottom: 20px; 21 | } 22 | 23 | .metricsContainer { 24 | display: flex; 25 | flex-direction: column; 26 | justify-content: center; 27 | align-items: center; 28 | color: white; 29 | padding-top: 3rem; 30 | } 31 | 32 | // SELECT DROPDOWN THING 33 | .selectMe { 34 | color: black; 35 | padding: 0.5em 2em; 36 | border-radius: 30px; 37 | background-color: $primary-blue; 38 | border: 1px solid whitesmoke; 39 | 40 | option { 41 | color: black; 42 | padding: 80px; 43 | font-size: 1rem; 44 | } 45 | } 46 | 47 | .Grid { 48 | background: linear-gradient(45deg, 49 | $dark-blue 0%, 50 | $primary-blue 20%, 51 | $primary-blue 45%, 52 | $secondary-blue 70%, 53 | $dark-blue 100%); 54 | } 55 | 56 | #endpointBtn { 57 | font-family: $primary-font; 58 | padding: 0.7em 1em; 59 | border: 2px solid $blue-stone; 60 | border-radius: $round-dem-edges; 61 | font-weight: 700; 62 | background-color: $blue-stone; 63 | color: $lavender; 64 | white-space: nowrap; 65 | margin-top: 1rem; 66 | cursor: pointer; 67 | } 68 | 69 | #endpointBtn:hover { 70 | color: $blue-stone; 71 | background-color: $lavender; 72 | border: 2px solid $lavender; 73 | } 74 | 75 | .containerLeft { 76 | text-align: center; 77 | display: flex; 78 | flex-direction: column; 79 | height: 100vh; 80 | background: black; 81 | opacity: 55%; 82 | color: white; 83 | margin-right: 3rem; 84 | font-size: 1.2rem; 85 | 86 | input { 87 | text-align: center; 88 | color: white; 89 | } 90 | } 91 | 92 | .containerMid { 93 | color: white; 94 | display: flex; 95 | height: 100%; 96 | width: 100%; 97 | justify-content: flex-start; 98 | align-items: center; 99 | flex-direction: column; 100 | text-align: center; 101 | } 102 | 103 | .containerRight { 104 | text-align: center; 105 | color: white; 106 | display: flex; 107 | flex-direction: column; 108 | justify-content: center; 109 | align-items: center; 110 | padding: 1em; 111 | } 112 | 113 | .containerGauge { 114 | display: flex; 115 | height: 90%; 116 | width: 95%; 117 | justify-content: center; 118 | align-items: center; 119 | } 120 | 121 | .dropdownMenu { 122 | display: flex; 123 | width: 100%; 124 | align-items: center; 125 | flex-direction: column; 126 | } 127 | 128 | // parts 129 | 130 | .enterUrl { 131 | margin: 10% 0 10% 0; 132 | font-family: $primary-font; 133 | font-size: 1.5rem; 134 | font-weight: 700; 135 | width: 80%; 136 | color: $lavender; 137 | } 138 | 139 | .endpointInput { 140 | color: $primary-black; 141 | font-family: $primary-font; 142 | height: 2.5rem; 143 | min-width: 15vw; 144 | width: 65%; 145 | border-bottom: 2px solid $secondary-blue; 146 | font-weight: 500; 147 | outline: 0; 148 | background: transparent; 149 | transition: border-color 0.2s; 150 | } 151 | 152 | .endpointInput:focus { 153 | border-bottom: 2px solid $secondary-blue; 154 | font-weight: 700; 155 | animation-name: lineAnimation; 156 | animation-duration: 4s; 157 | animation-iteration-count: infinite; 158 | } 159 | 160 | @keyframes lineAnimation { 161 | 0% { 162 | border-bottom: 2px solid $secondary-blue; 163 | } 164 | 165 | 50% { 166 | border-bottom: 2px solid $dark-blue; 167 | } 168 | 169 | 100% { 170 | border-bottom: 2px solid $secondary-blue; 171 | } 172 | } 173 | 174 | .dropdownMenu { 175 | display: flex; 176 | justify-content: center; 177 | } 178 | 179 | .controlPanel { 180 | display: flex; 181 | justify-content: center; 182 | align-items: center; 183 | width: 100%; 184 | background: black; 185 | opacity: 55%; 186 | border-radius: 0px 0px 10px 10px; 187 | } 188 | 189 | .lineChart { 190 | background: black; 191 | opacity: 75%; 192 | border-radius: 10px; 193 | padding: 1em 2em 0 2em; 194 | } 195 | 196 | .chartMaybe { 197 | height: 100vh; 198 | width: 100%; 199 | } 200 | 201 | .detailsList { 202 | display: flex; 203 | flex-direction: column; 204 | justify-content: center; 205 | align-items: center; 206 | width: 100%; 207 | } 208 | 209 | .detailsHeader { 210 | display: flex; 211 | justify-content: center; 212 | align-items: center; 213 | font-size: large; 214 | color: $primary-blue; 215 | width: 100%; 216 | background-color: $lavender; 217 | border-radius: 4px 4px 0px 0px; 218 | } 219 | 220 | 221 | 222 | .detailElement { 223 | width: 100%; 224 | background-color: $primary-blue; 225 | } 226 | 227 | .detailElementFlaw { 228 | width: 100%; 229 | background-color: $secondary-blue; 230 | } 231 | 232 | .learnMoreBtn { 233 | border-radius: 6px; 234 | padding: 0rem 1rem; 235 | background: $lavender; 236 | color: $primary-blue; 237 | font-weight: 500; 238 | } -------------------------------------------------------------------------------- /styles/Demo.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | .threeParts { 4 | display: grid; 5 | grid-template-columns: 1fr 3fr 1fr; 6 | min-height: 100vh; 7 | width: 100%; 8 | height: 100%; 9 | background: linear-gradient(45deg, 10 | $dark-blue 0%, 11 | $primary-blue 20%, 12 | $primary-blue 45%, 13 | $secondary-blue 70%, 14 | $dark-blue 100%); 15 | overflow: hidden; 16 | } 17 | 18 | .dataLoading { 19 | margin-bottom: 20px; 20 | } 21 | 22 | .metricsContainer { 23 | margin-top: 17rem; 24 | display: flex; 25 | flex-direction: column; 26 | justify-content: center; 27 | align-items: center; 28 | color: white; 29 | } 30 | 31 | .endpointBtn { 32 | font-family: $primary-font; 33 | padding: 0.7em 1em; 34 | border: 2px solid $blue-stone; 35 | border-radius: $round-dem-edges; 36 | font-weight: 700; 37 | background-color: $blue-stone; 38 | color: $lavender; 39 | white-space: nowrap; 40 | margin-top: 1rem; 41 | cursor: pointer; 42 | } 43 | 44 | .endpointBtn:hover { 45 | color: $blue-stone; 46 | background-color: $lavender; 47 | } 48 | 49 | .containerLeft { 50 | text-align: center; 51 | display: flex; 52 | flex-direction: column; 53 | height: 100vh; 54 | background: black; 55 | opacity: 55%; 56 | color: white; 57 | margin-right: 3rem; 58 | font-size: 1.2rem; 59 | 60 | input { 61 | text-align: center; 62 | color: white; 63 | } 64 | } 65 | 66 | .containerMid { 67 | color: white; 68 | display: flex; 69 | height: 100%; 70 | width: 100%; 71 | justify-content: flex-start; 72 | align-items: center; 73 | flex-direction: column; 74 | text-align: center; 75 | } 76 | 77 | .containerRight { 78 | color: white; 79 | display: flex; 80 | height: 100%; 81 | justify-content: center; 82 | align-items: center; 83 | padding: 0rem 3rem; 84 | } 85 | 86 | .demoIntro { 87 | display: flex; 88 | flex-direction: column; 89 | min-height: auto; 90 | overflow: hidden; 91 | min-height: 15rem; 92 | height: 25%; 93 | justify-content: space-between; 94 | padding: 1.5rem; 95 | border-radius: 10px; 96 | background: black; 97 | opacity: 55%; 98 | font-size: large; 99 | font-family: $primary-font; 100 | } 101 | 102 | .containerGauge { 103 | display: flex; 104 | height: 90%; 105 | width: 95%; 106 | justify-content: center; 107 | align-items: center; 108 | // background: #111c44; 109 | } 110 | 111 | .dropdownMenu { 112 | display: flex; 113 | width: 100%; 114 | align-items: center; 115 | flex-direction: column; 116 | } 117 | 118 | // parts 119 | 120 | .enterUrl { 121 | margin: 10% 0 10% 0; 122 | font-family: $primary-font; 123 | color: $lavender; 124 | } 125 | 126 | .endpointInput { 127 | color: $primary-black; 128 | font-family: $primary-font; 129 | height: 2.5rem; 130 | min-width: 15vw; 131 | width: 65%; 132 | border-bottom: 2px solid $secondary-blue; 133 | font-weight: 500; 134 | outline: 0; 135 | background: transparent; 136 | transition: border-color 0.2s; 137 | } 138 | 139 | .endpointInput:focus { 140 | border-bottom: 2px solid $secondary-blue; 141 | font-weight: 700; 142 | animation-name: lineAnimation; 143 | animation-duration: 4s; 144 | animation-iteration-count: infinite; 145 | } 146 | 147 | @keyframes lineAnimation { 148 | 0% { 149 | border-bottom: 2px solid $secondary-blue; 150 | } 151 | 152 | 50% { 153 | border-bottom: 2px solid $dark-blue; 154 | } 155 | 156 | 100% { 157 | border-bottom: 2px solid $secondary-blue; 158 | } 159 | } 160 | 161 | .dropdownMenu { 162 | display: flex; 163 | justify-content: center; 164 | } 165 | 166 | .controlPanel { 167 | display: flex; 168 | justify-content: center; 169 | align-items: center; 170 | width: 100%; 171 | height: 30%; 172 | background: black; 173 | opacity: 55%; 174 | margin-bottom: 3rem; 175 | border-radius: 0px 0px 10px 10px; 176 | } 177 | 178 | .detailsList { 179 | display: flex; 180 | flex-direction: column; 181 | justify-content: center; 182 | align-items: center; 183 | width: 100%; 184 | } 185 | 186 | .detailsHeader { 187 | display: flex; 188 | justify-content: center; 189 | align-items: center; 190 | font-size: large; 191 | font-weight: 700; 192 | color: $primary-blue; 193 | width: 100%; 194 | height: 2rem; 195 | background-color: $lavender; 196 | border-radius: 4px 4px 0px 0px; 197 | } 198 | 199 | .detailElement { 200 | width: 100%; 201 | background-color: $primary-blue; 202 | } 203 | 204 | .detailElementFlaw { 205 | width: 100%; 206 | background-color: $secondary-blue; 207 | } 208 | 209 | .homebtn { 210 | margin-top: 1rem; 211 | opacity: 55%; 212 | } 213 | 214 | .logoPic:hover { 215 | cursor: pointer; 216 | } 217 | 218 | .desContainer { 219 | display: flex; 220 | justify-content: space-between; 221 | } 222 | 223 | @media screen and (max-width: 800px) { 224 | .threeParts { 225 | display: flex; 226 | flex-direction: column; 227 | } 228 | .containerLeft { 229 | margin: 0%; 230 | height: 60vh; 231 | } 232 | .containerMid { 233 | width: 100%; 234 | max-height: 1650px; 235 | min-height: 300px; 236 | } 237 | 238 | .containerRight { 239 | justify-content: flex-start; 240 | margin: 50px 0px; 241 | 242 | } 243 | .metricsContainer { 244 | margin: 0px; 245 | } 246 | 247 | .controlPanel { 248 | margin-bottom: 0px; 249 | min-height: 700px; 250 | } 251 | .endpointInput { 252 | font-size: small; 253 | } 254 | 255 | .containerGauge { 256 | height: 100%; 257 | } 258 | 259 | .mobileBoxHeight { 260 | height: 100%; 261 | } 262 | 263 | .innerGaugeContainer { 264 | min-height: 100%; 265 | display: flex; 266 | flex-direction: column; 267 | justify-content: space-evenly; 268 | } 269 | 270 | .mobileSpacing { 271 | display: flex; 272 | justify-content: space-between; 273 | min-width: 140px; 274 | } 275 | } -------------------------------------------------------------------------------- /styles/Docs.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | #docsPage { 4 | display: flex; 5 | height: 100vh; 6 | align-items: center; 7 | } 8 | 9 | #docsNav { 10 | display: flex; 11 | padding: 10rem; 12 | height: 100vh; 13 | width: 100%; 14 | } 15 | 16 | #docsList { 17 | display: flex; 18 | margin-top: 12vh; 19 | font-size: 0.8em; 20 | flex-direction: column; 21 | justify-content: flex-start; 22 | list-style: none; 23 | height: 100%; 24 | } 25 | 26 | #rightside { 27 | width: 100%; 28 | margin-top: 3vh; 29 | display: flex; 30 | justify-content: center; 31 | } 32 | 33 | #docsContainer { 34 | display: flex; 35 | font-size: 1.2em; 36 | justify-content: center; 37 | max-width: 800px; 38 | height: 100vh; 39 | } 40 | 41 | .docsLink { 42 | color: $primary-blue; 43 | } 44 | 45 | .imageContainer { 46 | max-width: 800px; 47 | } 48 | 49 | .listButton { 50 | font-family: $primary-font; 51 | padding: 0.7rem 1rem; 52 | min-width: 180px; 53 | font-size: 1.5rem; 54 | border: none; 55 | border-radius: $round-dem-edges; 56 | font-weight: 700; 57 | background-color: rgba(0, 0, 0, 0); 58 | color: $blue-stone; 59 | max-width: 140px; 60 | cursor: pointer; 61 | } 62 | 63 | @media only screen and (max-width: 700px) { 64 | .listButton { 65 | font-size: .7rem; 66 | width: 100px; 67 | min-width: 50px; 68 | } 69 | 70 | #docsNav { 71 | padding: 20% 0; 72 | } 73 | } 74 | 75 | .listButton:disabled { 76 | color: $lavender; 77 | background-color: $blue-stone; 78 | box-shadow: 0px 0px 10px 1px $primary-blue; 79 | transition: 0.3s ease; 80 | } 81 | -------------------------------------------------------------------------------- /styles/Endpoints.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | .ul { 4 | display: flex; 5 | font-family: $primary-font; 6 | flex-direction: column; 7 | align-items: center; 8 | justify-content: center; 9 | padding: 1em 1em; 10 | overflow-y: auto; 11 | min-height: 90%; 12 | max-height: 100%; 13 | } 14 | 15 | .li { 16 | text-align: center; 17 | padding: 0.8em; 18 | font-size: 1.2rem; 19 | list-style: none; 20 | } 21 | 22 | .li:hover { 23 | background: $secondary-blue; 24 | } 25 | 26 | .EndpointsList { 27 | margin-top: 2em; 28 | padding: 1rem; 29 | width: 100%; 30 | } 31 | 32 | .endpointsTitle { 33 | font-family: $primary-font; 34 | font-size: 2rem; 35 | font-weight: 900; 36 | border-bottom: 1px solid $lavender; 37 | color: $secondary-blue; 38 | text-align: center; 39 | } 40 | 41 | .endpointContainer { 42 | border: 1px solid $lavender; 43 | border-radius: 8px; 44 | width: 100%; 45 | overflow: hidden; 46 | } 47 | 48 | .endpointBtn { 49 | background-color: #266ef6; 50 | color: $lavender; 51 | 52 | } -------------------------------------------------------------------------------- /styles/Home.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | .home { 4 | background-color: $lavender; 5 | width: 100%; 6 | height: 100%; 7 | padding: 10% 9% 1% 9%; 8 | overflow-x: hidden; 9 | } 10 | 11 | .metricsContainer { 12 | display: flex; 13 | margin-top: 5rem; 14 | flex-direction: column; 15 | justify-content: center; 16 | align-items: center; 17 | } 18 | 19 | #endpointBtn { 20 | font-family: $primary-font; 21 | padding: 0.7em 1em; 22 | border: 2px solid $blue-stone; 23 | border-radius: $round-dem-edges; 24 | font-weight: 700; 25 | background-color: rgba(0, 0, 0, 0); 26 | color: $blue-stone; 27 | white-space: nowrap; 28 | margin-top: 1rem; 29 | cursor: pointer; 30 | } 31 | 32 | #endpointBtn:hover { 33 | color: $lavender; 34 | background-color: $blue-stone; 35 | box-shadow: 0px 0px 10px 1px $primary-blue; 36 | transition: 0.3s ease-in-out; 37 | } 38 | 39 | .hero { 40 | display: grid; 41 | grid-template-columns: 1fr 1fr; 42 | height: 50vh; 43 | } 44 | 45 | .introContainer { 46 | display: flex; 47 | flex-direction: column; 48 | background-color: $blue-stone; 49 | width: 100%; 50 | height: auto; 51 | color: $lavender; 52 | font-family: $primary-font; 53 | } 54 | 55 | .left { 56 | display: flex; 57 | flex-direction: column; 58 | justify-content: center; 59 | align-items: flex-start; 60 | } 61 | 62 | .leftTitle { 63 | color: #121212; 64 | font-size: 3rem; 65 | font-family: 'Montserrat', sans-serif; 66 | font-weight: 900; 67 | margin: 0; 68 | } 69 | 70 | .panopticWord { 71 | color: $blue-stone; 72 | } 73 | 74 | .subText { 75 | color: $secondary-blue; 76 | } 77 | 78 | .right { 79 | background: url('../assets/demoPic.png'); 80 | background-size: contain; 81 | background-position: center; 82 | background-repeat: no-repeat; 83 | width: 100%; 84 | height: auto; 85 | border-radius: $round-dem-edges; 86 | } 87 | 88 | .heroBtnContainer { 89 | margin-top: 20px; 90 | display: flex; 91 | } 92 | 93 | .readDocs { 94 | font-family: $primary-font; 95 | padding: 1em 1.5rem; 96 | border: 2px solid $blue-stone; 97 | border-radius: $round-dem-edges; 98 | font-weight: 700; 99 | background-color: rgba(0, 0, 0, 0); 100 | color: $blue-stone; 101 | margin-right: 1vw; 102 | font-size: 1rem; 103 | cursor: pointer; 104 | } 105 | 106 | .readDocs:hover { 107 | color: $lavender; 108 | background-color: $blue-stone; 109 | box-shadow: 0px 0px 10px 1px $primary-blue; 110 | transition: 0.3s ease-in-out; 111 | } 112 | 113 | .install { 114 | margin-left: 10px; 115 | font-family: $primary-font; 116 | padding: 1em 1.5rem; 117 | border: 2px solid $blue-stone; 118 | border-radius: $round-dem-edges; 119 | font-weight: 700; 120 | background-color: $blue-stone; 121 | color: $lavender; 122 | font-size: 1rem; 123 | cursor: pointer; 124 | } 125 | 126 | .install:hover { 127 | background-color: rgba(0, 0, 0, 0); 128 | color: $blue-stone; 129 | box-shadow: 0px 0px 10px 1px $primary-blue; 130 | transition: 0.3s ease-in-out; 131 | } 132 | 133 | .homeGifs { 134 | height: 45rem; 135 | width: auto; 136 | border-radius: 8px; 137 | box-shadow: 138 | 0 2.8px 2.2px rgba(0, 0, 0, 0.034), 139 | 0 6.7px 5.3px rgba(0, 0, 0, 0.048), 140 | 0 12.5px 10px rgba(0, 0, 0, 0.06), 141 | 0 22.3px 17.9px rgba(0, 0, 0, 0.072), 142 | 0 41.8px 33.4px rgba(0, 0, 0, 0.086), 143 | 0 100px 80px rgba(0, 0, 0, 0.12); 144 | 145 | } 146 | 147 | .featText { 148 | justify-self: flex-start; 149 | align-self: center; 150 | text-align: center; 151 | margin-right: 45px; 152 | font-weight: 900; 153 | } 154 | 155 | .lowcontainer { 156 | display: flex; 157 | flex-direction: column; 158 | width: 100%; 159 | align-items: center; 160 | justify-content: center; 161 | margin-top: 8%; 162 | } 163 | 164 | .mdeak { 165 | font-size: 4rem; 166 | text-align: center; 167 | color: $blue-stone; 168 | font-weight: 700; 169 | } 170 | 171 | .staffcontainer { 172 | display: flex; 173 | width: 100%; 174 | justify-content: space-evenly; 175 | text-align: center; 176 | flex-direction: row; 177 | color: $dark-blue; 178 | font-size: large; 179 | font-weight: bold; 180 | padding: 1rem; 181 | } 182 | 183 | #marcpic { 184 | margin: auto; 185 | background: url('https://media-exp1.licdn.com/dms/image/C5603AQGomH8kwmHfxQ/profile-displayphoto-shrink_400_400/0/1630615973512?e=1662595200&v=beta&t=IlIzzFPLa7EmXXKFBaPhi5TNPv0GrjvhLd_GGAo8XV4'); 186 | background-size: cover; 187 | background-position: center; 188 | background-repeat: no-repeat; 189 | width: 7rem; 190 | height: 7rem; 191 | border-radius: 100%; 192 | } 193 | 194 | #davispic { 195 | margin: auto; 196 | background: url('https://avatars.githubusercontent.com/u/95515224?v=4'); 197 | background-size: cover; 198 | background-position: center; 199 | background-repeat: no-repeat; 200 | width: 7rem; 201 | height: 7rem; 202 | border-radius: 100%; 203 | } 204 | 205 | #elliotpic { 206 | margin: auto; 207 | background: url('https://avatars.githubusercontent.com/u/100388743?v=4'); 208 | background-size: cover; 209 | background-position: center; 210 | background-repeat: no-repeat; 211 | width: 7rem; 212 | height: 7rem; 213 | border-radius: 100%; 214 | } 215 | 216 | #austinpic { 217 | margin: auto; 218 | background: url('https://avatars.githubusercontent.com/u/103286713?v=4'); 219 | background-size: cover; 220 | background-position: center; 221 | background-repeat: no-repeat; 222 | width: 7rem; 223 | height: 7rem; 224 | border-radius: 100%; 225 | } 226 | 227 | #karlpic { 228 | margin: auto; 229 | background: url('https://avatars.githubusercontent.com/u/12672823?v=4'); 230 | background-size: cover; 231 | background-position: center; 232 | background-repeat: no-repeat; 233 | width: 7rem; 234 | height: 7rem; 235 | border-radius: 100%; 236 | } 237 | 238 | .linkContainer { 239 | font-size: 2rem; 240 | display: flex; 241 | align-items: center; 242 | justify-content: center; 243 | } 244 | 245 | .githubIcon { 246 | margin-right: 10px; 247 | color: black; 248 | } 249 | 250 | .linkedinIcon { 251 | color: #0a66c2; 252 | } 253 | 254 | .Ad { 255 | margin-top: 80px; 256 | justify-content: space-evenly; 257 | align-items: center; 258 | border: 1px solid black; 259 | color: black; 260 | } 261 | 262 | 263 | 264 | @media screen and (max-width: 800px) { 265 | 266 | // Turn Hero Into Flexbox for mobile 267 | .hero { 268 | padding-top: 20%; 269 | text-align: center; 270 | display: flex; 271 | flex-direction: column; 272 | align-items: center; 273 | justify-content: center; 274 | height: 100vh; 275 | 276 | .left { 277 | align-items: center; 278 | } 279 | 280 | .right { 281 | margin-top: 10%; 282 | width: 100%; 283 | height: 100%; 284 | background-size: contain; 285 | background-position: top; 286 | background-repeat: no-repeat; 287 | } 288 | } 289 | 290 | // Meet Team 291 | .lowcontainer { 292 | margin-top: -50px; 293 | } 294 | 295 | .staffcontainer { 296 | display: grid; 297 | grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); 298 | gap: 50px; 299 | } 300 | 301 | .mdeak { 302 | margin-top: 60px; 303 | } 304 | } 305 | 306 | // APP 307 | 308 | .body { 309 | display: flex; 310 | height: 100vh; 311 | width: 100vw; 312 | } 313 | 314 | // containers 315 | 316 | .threeParts { 317 | display: flex; 318 | flex-direction: row; 319 | height: 100vh; 320 | width: 100vw; 321 | justify-content: space-evenly; 322 | padding: 2rem; 323 | margin-top: 4em; 324 | } -------------------------------------------------------------------------------- /styles/Login.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | .body { 4 | height: 100vh; 5 | width: 100vw; 6 | display: flex; 7 | flex-direction: column; 8 | justify-content: center; 9 | align-items: center; 10 | background-color: $lavender; 11 | background-image: linear-gradient(315deg, $blue-stone 0%, $lavender 74%); 12 | } 13 | 14 | .username, 15 | .password { 16 | margin: 0.6rem; 17 | padding: 0.6em 0.5em; 18 | background-color: rgba(0, 0, 0, 0); 19 | border: none; 20 | border-bottom: 2px solid $primary-blue; 21 | width: 100%; 22 | } 23 | 24 | .username:focus, 25 | .password:focus { 26 | outline: none; 27 | } 28 | 29 | .username::placeholder, 30 | .password::placeholder { 31 | color: black; 32 | font-size: 1.2rem; 33 | } 34 | 35 | .construction { 36 | margin-top: 2vh; 37 | font-weight: bold; 38 | } 39 | 40 | .background { 41 | background-color: black; 42 | } 43 | 44 | .loginForm { 45 | min-width: 20rem; 46 | width: 25%; 47 | height: 50%; 48 | padding: 2% 3%; 49 | border-radius: 10px; 50 | display: flex; 51 | flex-direction: column; 52 | justify-content: center; 53 | align-items: center; 54 | } 55 | 56 | .oauthBtn { 57 | display: flex; 58 | justify-content: center; 59 | align-items: center; 60 | margin-top: 0.8rem; 61 | padding: 0.8em 1em; 62 | border: 3px solid whitesmoke; 63 | border-radius: 10px; 64 | color: $lavender; 65 | width: 100%; 66 | background: $primary-black; 67 | } 68 | 69 | .loginButton { 70 | font-family: $primary-font; 71 | padding: 0.7em 1.5em; 72 | border: 2px solid $blue-stone; 73 | border-radius: $round-dem-edges; 74 | font-weight: 700; 75 | font-size: large; 76 | width: 100%; 77 | margin-top: 20px; 78 | background-color: transparent; 79 | color: $blue-stone; 80 | white-space: nowrap; 81 | } 82 | 83 | .loginButton:hover { 84 | color: $lavender; 85 | background-color: $blue-stone; 86 | box-shadow: 0px 0px 10px 2px $primary-blue; 87 | transition: 0.3s ease-in; 88 | } 89 | 90 | .githubLogin { 91 | margin-right: 5px; 92 | font-size: 1.4rem; 93 | } -------------------------------------------------------------------------------- /styles/Manager.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | .manager { 4 | width: 100vw; 5 | height: 100vh; 6 | overflow: hidden; 7 | background: linear-gradient(45deg, 8 | $dark-blue 0%, 9 | $primary-blue 20%, 10 | $primary-blue 45%, 11 | $secondary-blue 70%, 12 | $dark-blue 100%); 13 | } -------------------------------------------------------------------------------- /styles/Nav.module.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300;1,800;1,900&display=swap'); 2 | @import './Variables.scss'; 3 | // Color inspiration 4 | // https://www.pinterest.com/pin/ice-blue-color-palette-winter-colour-inspiration-color-scheme--574842339932570411/ 5 | 6 | .yeahBoi { 7 | position: fixed; 8 | top: 0; 9 | min-width: 100%; 10 | display: flex; 11 | justify-content: space-between; 12 | padding: 2% 5%; 13 | align-items: center; 14 | font-size: 0.9rem; 15 | max-height: 12vh; 16 | background-color: $lavender; 17 | border-bottom: solid rgb(198, 198, 198) 1px; 18 | box-shadow: 0px 1px 10px 2px rgb(182, 182, 182); 19 | } 20 | 21 | .logo { 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | font-family: 'Montserrat', sans-serif; 26 | font-weight: 900; 27 | color: $blue-stone; 28 | max-width: 140px; 29 | } 30 | 31 | .logoPic { 32 | max-width: 180px; 33 | height: auto; 34 | cursor: pointer; 35 | } 36 | 37 | .loginBtn { 38 | font-family: $primary-font; 39 | padding: 0.7rem 1rem; 40 | border: 2px solid $blue-stone; 41 | border-radius: $round-dem-edges; 42 | font-weight: 700; 43 | background-color: rgba(0, 0, 0, 0); 44 | color: $blue-stone; 45 | white-space: nowrap; 46 | cursor: pointer; 47 | } 48 | 49 | .readDocsNav { 50 | font-family: $primary-font; 51 | padding: 0.7rem 1rem; 52 | border: 0.2rem solid $blue-stone; 53 | border-radius: $round-dem-edges; 54 | font-weight: 700; 55 | background-color: $blue-stone; 56 | color: $lavender; 57 | margin-right: 1vw; 58 | cursor: pointer; 59 | } 60 | 61 | .loginBtn:hover { 62 | color: $lavender; 63 | background-color: $blue-stone; 64 | box-shadow: 0px 0px 10px 1px $primary-blue; 65 | transition: 0.3s ease; 66 | } 67 | 68 | .readDocsNav:hover { 69 | color: $lavender; 70 | background-color: $blue-stone; 71 | box-shadow: 0px 0px 10px 1px $primary-blue; 72 | transition: 0.3s ease; 73 | } 74 | 75 | #navBtns { 76 | display: flex; 77 | justify-content: space-between; 78 | } 79 | 80 | .signupBtn { 81 | font-family: $primary-font; 82 | padding: 0.5em 1.5em; 83 | border: 2px solid HSL(114, 96.1%, 59.6%); 84 | border-radius: $round-dem-edges; 85 | box-shadow: 0px 0px 10px 2px HSL(114, 96.1%, 59.6%); 86 | font-weight: 700; 87 | background-color: #010a01; 88 | color: HSL(114, 96.1%, 59.6%); 89 | } -------------------------------------------------------------------------------- /styles/Newsletter.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | .Newsletter { 4 | margin-top: 15%; 5 | border: 2px solid black; 6 | min-width: 100%; 7 | height: 50vh; 8 | padding: 2% 10%; 9 | display: flex; 10 | flex-direction: column; 11 | align-items: center; 12 | justify-content: center; 13 | border-radius: $round-dem-edges; 14 | } 15 | 16 | .title { 17 | text-align: center; 18 | font-family: $primary-font; 19 | font-weight: 900; 20 | font-size: 2rem; 21 | } 22 | 23 | .subtext { 24 | text-align: center; 25 | margin-bottom: 30px; 26 | } 27 | 28 | .emailErr { 29 | color: red; 30 | font-weight: 500; 31 | } 32 | 33 | .inputContainer { 34 | width: 80%; 35 | 36 | 37 | input { 38 | width: 100%; 39 | padding: 0.5em 1em; 40 | border: 2px solid black; 41 | border-top-left-radius: $round-dem-edges; 42 | border-top-right-radius: $round-dem-edges; 43 | font-weight: 900; 44 | font-family: $primary-font; 45 | } 46 | 47 | 48 | input::placeholder { 49 | font-weight: 100; 50 | } 51 | 52 | button { 53 | width: 100%; 54 | background-color: black; 55 | color: white; 56 | font-weight: 900; 57 | border: 2px solid black; 58 | padding: 0.5em 1em; 59 | border-bottom-left-radius: $round-dem-edges; 60 | border-bottom-right-radius: $round-dem-edges; 61 | } 62 | } -------------------------------------------------------------------------------- /styles/Repoendpoints.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | .repoendpoints { 4 | background: linear-gradient( 5 | 45deg, 6 | $dark-blue 0%, 7 | $primary-blue 20%, 8 | $primary-blue 45%, 9 | $secondary-blue 70%, 10 | $dark-blue 100% 11 | ); 12 | width: 100vw; 13 | height: 100vh; 14 | overflow: hidden; 15 | } 16 | 17 | .hideIt { 18 | display: none; 19 | } 20 | 21 | .inputContainer { 22 | z-index: 999; 23 | display: flex; 24 | flex-direction: column; 25 | justify-content: center; 26 | align-items: center; 27 | background-color: $dark-blue; 28 | height: 500px; 29 | width: 450px; 30 | position: absolute; 31 | top: 50%; 32 | left: 50%; 33 | transform: translate(-50%, -50%); 34 | border-radius: $round-dem-edges; 35 | box-shadow: 0px 2px 5px 1px $secondary-blue; 36 | color: black; 37 | } 38 | 39 | .inputMenu { 40 | background-color: rgba(0, 0, 0, 0.6); 41 | width: 100vw; 42 | height: 100vh; 43 | border-radius: $round-dem-edges; 44 | 45 | input { 46 | border: 1px solid black; 47 | border-radius: $round-dem-edges; 48 | background-color: whitesmoke; 49 | padding: 0.5em 1em; 50 | width: 80%; 51 | } 52 | } 53 | 54 | .linkIt { 55 | margin-top: 25px; 56 | color: white; 57 | border: 1px solid white; 58 | border-radius: $round-dem-edges; 59 | padding: 0.5em 1em; 60 | width: 50%; 61 | } 62 | 63 | .linkIt:hover { 64 | background-color: black; 65 | color: white; 66 | transition: 0.5s ease; 67 | } 68 | 69 | .closeMenu { 70 | position: relative; 71 | top: -180px; 72 | left: -180px; 73 | font-weight: 900; 74 | } 75 | 76 | .command { 77 | color: black; 78 | font-size: 2rem; 79 | font-weight: 900; 80 | text-align: center; 81 | } 82 | 83 | .repoList { 84 | border: 2px solid black; 85 | padding: 1em 3em; 86 | position: absolute; 87 | top: 50%; 88 | left: 50%; 89 | transform: translate(-50%, -50%); 90 | display: flex; 91 | flex-direction: column; 92 | text-align: center; 93 | width: 400px; 94 | height: auto; 95 | max-height: 800px; 96 | border-radius: $round-dem-edges; 97 | overflow-y: auto; 98 | 99 | li { 100 | padding: 0.7em; 101 | font-size: 1.2rem; 102 | font-weight: 900; 103 | } 104 | 105 | li:hover { 106 | background-color: white; 107 | cursor: pointer; 108 | } 109 | } 110 | 111 | .repoClicked { 112 | color: white; 113 | } 114 | -------------------------------------------------------------------------------- /styles/Sidenav.module.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | .Sidenav { 4 | z-index: 999; 5 | color: white; 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | position: absolute; 10 | top: 10px; 11 | left: 10px; 12 | // height: 100vh; 13 | // width: 180px; 14 | // background-image: linear-gradient(180deg, $grad1 0%, $grad2 74%); 15 | // border-right: 1px solid white; 16 | } 17 | 18 | .drawerContent { 19 | background: linear-gradient(180deg, 20 | $primary-blue 0%, 21 | $primary-blue 20%, 22 | $primary-blue 45%, 23 | $secondary-blue 70%, 24 | $dark-blue 100%) !important; 25 | } 26 | 27 | .drawerBody { 28 | color: white; 29 | display: flex; 30 | flex-direction: column; 31 | justify-content: center; 32 | font-size: 1.5rem; 33 | 34 | p { 35 | padding: 1em 0; 36 | } 37 | } 38 | 39 | .burgerIcon { 40 | background-color: $blue-stone !important; 41 | font-size: 1.4rem !important; 42 | } 43 | 44 | .imageContainer { 45 | width: 70%; 46 | } 47 | 48 | .logoPic { 49 | width: 100%; 50 | } 51 | 52 | .logo { 53 | display: flex; 54 | justify-content: center; 55 | align-items: center; 56 | } 57 | 58 | .link { 59 | cursor: pointer; 60 | } 61 | 62 | 63 | 64 | .listLink { 65 | width: 100%; 66 | text-align: center; 67 | } 68 | 69 | .linksContainer { 70 | font-size: 1.2rem; 71 | padding: 0; 72 | display: flex; 73 | flex-direction: column; 74 | justify-content: space-around; 75 | padding: 10em 1em; 76 | align-items: center; 77 | list-style: none; 78 | width: 100%; 79 | height: 100%; 80 | } -------------------------------------------------------------------------------- /styles/Variables.scss: -------------------------------------------------------------------------------- 1 | $primary-font: 'Montserrat', sans-serif; 2 | $round-dem-edges: 25px; 3 | $primary-green: HSL(114, 96.1%, 59.6%); 4 | $primary-black: hsl(225, 6%, 13%); 5 | $blue-stone: #266ef6; 6 | $dark-blue: #3a5985; 7 | $primary-blue: #608fca; 8 | $secondary-blue: #89afe0; 9 | $light-blue: #b2caee; 10 | $gray: #d5deef; 11 | $lavender: #f0f4fb; 12 | 13 | // $dark-bg: rgb(32, 30, 48); 14 | $dark-bg: #0b1337; 15 | 16 | $neon-orange: rgb(255, 125, 125); 17 | $neon-pink: rgb(213, 77, 224); 18 | $neon-blue: rgb(4, 221, 205); 19 | $neon-red: rgb(250, 83, 128); 20 | 21 | $grad1: rgb(221, 77, 204); 22 | $grad2: rgb(186, 84, 245); 23 | -------------------------------------------------------------------------------- /styles/chartie.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 2rem; 3 | } 4 | 5 | .bodyCH { 6 | background: #15232d; 7 | color: rgb(255, 255, 255); 8 | font-family: Montserrat, Arial, sans-serif; 9 | /* width: 100wv; */ 10 | /* height: 100vh; */ 11 | width: 200px; 12 | height: 200px; 13 | } 14 | 15 | .chartContainer { 16 | padding: 2rem; 17 | border-radius: 25px; 18 | /* background: #73ad21; */ 19 | width: 200px; 20 | height: 200px; 21 | color: white; 22 | } 23 | 24 | .containerGauge { 25 | display: flex; 26 | flex-direction: row; 27 | } 28 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 4 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 5 | } 6 | 7 | a { 8 | color: inherit; 9 | text-decoration: none; 10 | } 11 | 12 | 13 | 14 | * { 15 | margin: 0; 16 | padding: 0; 17 | box-sizing: border-box; 18 | } -------------------------------------------------------------------------------- /styles/lineChart.css: -------------------------------------------------------------------------------- 1 | .AChart { 2 | width: 700px; 3 | height: 700px; 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true 21 | }, 22 | "include": [ 23 | "next-env.d.ts", 24 | "**/*.ts", 25 | "**/*.tsx", 26 | "pages/login.tsx", 27 | "pages/login.tsx", 28 | "__test__/pages/home.test.js", 29 | "lib/dbConnect.js" 30 | ], 31 | "exclude": [ 32 | "node_modules" 33 | ] 34 | } -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | // components 2 | // AboutLH, ControlPanel, ControlPanelDemo, employeeID, EndpointsList, Hero, LHGauge, LineChart, LoadingSpinner, MainLineChartRE, Nav, Sidenav, UsingApp, UsingMetrics, wrightDetails, wrightDetailsDemo 3 | 4 | // pages 5 | // _app, dashboard, demo, docs, Index, Login, manager, repoEndpoints, signup 6 | 7 | // backend 8 | // all, createUser, githubLogin, lighthouse, lighthouseDemo, login, [user], [getUser], github, loginModel, dbConnect, parseCookies, passport-github-auth 9 | 10 | export interface LHData { 11 | performance: number; 12 | accessibility: number; 13 | bestPractices: number; 14 | seo: number; 15 | performanceMetrics: {}; 16 | accessibilityMetrics?: {}; 17 | bestPracticesMetrics?: {}; 18 | seoMetrics?: {}; 19 | }; 20 | 21 | export interface LHOptions { 22 | logLevel: string; 23 | output: string; 24 | onlyCategories: string[]; 25 | port: number; 26 | }; 27 | 28 | export interface MainLCOptions { 29 | borderWidth: number; 30 | responsive: boolean; 31 | maintainAspectRatio: boolean; 32 | chart: { 33 | width: string; 34 | height: string; 35 | }; 36 | plugins: { 37 | legend: { 38 | position: string; 39 | }; 40 | title: { 41 | text: string; 42 | }; 43 | }; 44 | scales: { 45 | y: { 46 | type: string; 47 | min: number; 48 | max: number; 49 | }; 50 | }; 51 | }; 52 | 53 | export interface LHGaugeOptions { 54 | responsive: boolean; 55 | maintainAspectRatio: boolean; 56 | plugins: { 57 | legend: { 58 | display: boolean; 59 | }; 60 | title: { 61 | display: boolean; 62 | text: string; 63 | font: { 64 | size: number; 65 | }; 66 | color: string; 67 | padding: { 68 | top: number; 69 | bottom:number; 70 | }; 71 | }; 72 | }; 73 | }; 74 | 75 | export interface LCDatasets { 76 | label: string; 77 | data: number | number[]; 78 | borderColor?: string; 79 | backgroundColor: string | string[]; 80 | pointHoverBackgroundColor?: string; 81 | pointHoverBorderWidth?: number; 82 | pointHoverRadius?: number; 83 | fill?: boolean; 84 | tension?: number; 85 | borderAlign?: string; 86 | }; 87 | 88 | export interface ChartData { 89 | labels: String[]; 90 | datasets: LCDatasets[] 91 | }; 92 | 93 | export interface ChartieDatasets { 94 | label?: String; 95 | data: Number[]; 96 | backgroundColor: String[]; 97 | borderAlign?: String; 98 | }; 99 | 100 | export interface ChartieData { 101 | datasets: ChartieDatasets[]; 102 | labels: String[]; 103 | }; 104 | 105 | export interface ChartieOptions { 106 | plugins: { 107 | legend: { 108 | display: Boolean; 109 | }; 110 | }; 111 | }; 112 | 113 | export interface CreateUser { 114 | username: string; 115 | password: string; 116 | endpoints: any; 117 | }; 118 | 119 | export interface MongoUser { 120 | _id?: number; 121 | username: string; 122 | password: string; 123 | endpoints?: any; 124 | github?: any; 125 | }; 126 | 127 | export interface DBOptions { 128 | useNewUrlParser: boolean; 129 | useUnifiedTopology: boolean; 130 | }; --------------------------------------------------------------------------------