├── Client ├── .env ├── src │ ├── global.d.ts │ ├── app.html │ ├── routes │ │ ├── __layout.svelte │ │ ├── logs.svelte │ │ ├── index.svelte │ │ ├── settings.svelte │ │ └── graphs.svelte │ ├── lib │ │ ├── graphs │ │ │ ├── LogLine.svelte │ │ │ ├── card.svelte │ │ │ ├── FusionVertical.svelte │ │ │ ├── BarVerticalOriginal.svelte │ │ │ ├── StackedBarHorizontal.svelte │ │ │ ├── StackedBarHorizontalStatus.svelte │ │ │ ├── StackedBarVerticalOriginal.svelte │ │ │ ├── Histogram.svelte │ │ │ └── Table.svelte │ │ ├── navbar.svelte │ │ ├── store.ts │ │ ├── store │ │ │ └── types.ts │ │ └── storeProcessing.ts │ └── electron.cjs ├── .vscode │ ├── extensions.json │ └── settings.json ├── globals.d.ts ├── static │ ├── icon.png │ ├── favicon.ico │ ├── favicon.png │ ├── logo50px.png │ ├── pteroFull.png │ ├── background.jpg │ ├── logoFull30px.png │ ├── logoFull50px.png │ ├── sveltekit-electron.svg │ └── sveltekit-electron1.svg ├── .prettierrc ├── .gitignore ├── jsconfig.json ├── babel.config.js ├── build.config.json ├── jest.config.js ├── global.css ├── .eslintrc.cjs ├── svelte.config.js ├── tsconfig.json ├── __tests │ └── test.js ├── README.md └── package.json ├── .gitignore ├── Ptero ├── images │ ├── soluntion.png │ └── pteroHighRes.png ├── mod.ts ├── types │ └── types.ts ├── Dockerfile ├── .env ├── models │ ├── redisClient.ts │ ├── dinosaurs.ts │ ├── users.ts │ └── APILogModel.ts ├── routers │ ├── routers.ts │ ├── userRouter.ts │ └── apiLogRouter.ts ├── dockerfile-compose.yml ├── controllers │ ├── users.ts │ ├── apiKey.ts │ ├── apiLog.ts │ └── dinosaurs.ts ├── __tests │ ├── superoak.test.ts │ └── redis.test.ts ├── utils │ ├── dataLogging.ts │ ├── middlewares.ts │ └── redis.ts ├── main.ts ├── deps.ts └── README.md ├── .vscode └── settings.json ├── README.md └── .ebextensions └── .travis.yml /Client/.env: -------------------------------------------------------------------------------- 1 | MONGODB_URI= 2 | MONGODB_DB= -------------------------------------------------------------------------------- /Client/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /Client/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /Client/globals.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /Client/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Client/static/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Client/node_modules/ 2 | /Client/public/build/ 3 | /Client/dist/ 4 | 5 | .DS_Store 6 | .env -------------------------------------------------------------------------------- /Client/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Client/static/favicon.ico -------------------------------------------------------------------------------- /Client/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Client/static/favicon.png -------------------------------------------------------------------------------- /Client/static/logo50px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Client/static/logo50px.png -------------------------------------------------------------------------------- /Client/static/pteroFull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Client/static/pteroFull.png -------------------------------------------------------------------------------- /Ptero/images/soluntion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Ptero/images/soluntion.png -------------------------------------------------------------------------------- /Client/static/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Client/static/background.jpg -------------------------------------------------------------------------------- /Client/static/logoFull30px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Client/static/logoFull30px.png -------------------------------------------------------------------------------- /Client/static/logoFull50px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Client/static/logoFull50px.png -------------------------------------------------------------------------------- /Ptero/images/pteroHighRes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Ptero/HEAD/Ptero/images/pteroHighRes.png -------------------------------------------------------------------------------- /Client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.suggest.imports.hosts": { 4 | "https://deno.land": false 5 | } 6 | } -------------------------------------------------------------------------------- /Ptero/mod.ts: -------------------------------------------------------------------------------- 1 | import { checkApiKey } from "./deps.ts"; 2 | import { caching, checkUser, } from "./deps.ts"; 3 | import { logData } from "./deps.ts"; 4 | -------------------------------------------------------------------------------- /Ptero/types/types.ts: -------------------------------------------------------------------------------- 1 | export interface Dinosaur { 2 | id: string; 3 | name: string; 4 | era: string; 5 | area: string; 6 | diet: string; 7 | } -------------------------------------------------------------------------------- /Client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[svelte]": { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "svelte.svelte-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Client/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | types/env.d.ts 3 | yarn-error.log 4 | pnpm-lock.yaml 5 | node_modules/ 6 | .svelte-kit/ 7 | .DS_Store 8 | .svelte/ 9 | *.local 10 | build/ 11 | dist/ -------------------------------------------------------------------------------- /Client/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "/~/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "build", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /Ptero/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM denoland/deno:1.15.3 2 | 3 | EXPOSE 9000 4 | 5 | WORKDIR /app 6 | 7 | USER deno 8 | 9 | ADD . . 10 | 11 | RUN deno cache main.ts 12 | 13 | CMD ["run", "--allow-all", "--unstable", "main.ts"] -------------------------------------------------------------------------------- /Client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | } -------------------------------------------------------------------------------- /Client/build.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "Ptero", 3 | "productName": "Ptero", 4 | "directories": { 5 | "output": "dist" 6 | }, 7 | "files": [ 8 | "src/electron.cjs", 9 | { 10 | "from": "build", 11 | "to": "" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /Client/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.svelte$': 'svelte-jester', 4 | '^.+\\.js$': 'babel-jest', 5 | }, 6 | moduleFileExtensions: ['js', 'svelte'], 7 | "setupFilesAfterEnv": ["@testing-library/jest-dom/extend-expect"], 8 | } 9 | -------------------------------------------------------------------------------- /Ptero/.env: -------------------------------------------------------------------------------- 1 | DB_NAME_LOG=apilogs 2 | DB_HOST_URL_LOG=mongodb+srv://pterots:aZxmaine!302@cluster0.tm2cs.mongodb.net/apilogs?authMechanism=SCRAM-SHA-1 3 | DB_NAME_USER=TestDB 4 | DB_HOST_URL=mongodb+srv://pterots:aZxmaine!302@cluster0.tm2cs.mongodb.net/TestDB?authMechanism=SCRAM-SHA-1 -------------------------------------------------------------------------------- /Ptero/models/redisClient.ts: -------------------------------------------------------------------------------- 1 | import { redisConnect } from "../deps.ts"; 2 | 3 | // connecting to redis server locally 4 | const redisClient = await redisConnect({ 5 | hostname: "127.0.0.1", 6 | // hostname: "server_redis_database", 7 | port: 6379, 8 | }); 9 | 10 | // logs 'PONG' when connected 11 | console.log(await redisClient.ping()); 12 | 13 | export { redisClient }; 14 | -------------------------------------------------------------------------------- /Ptero/routers/routers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Router, checkApiKey, caching, checkUser, Context 3 | } from '../deps.ts'; 4 | 5 | const pteroRouter = new Router(); 6 | 7 | pteroRouter.use("/", async (ctx: Context, next: any) => { 8 | await checkUser(ctx, checkApiKey); 9 | if (ctx.response.status === 401) return; 10 | await next(); 11 | }); 12 | 13 | export default pteroRouter; 14 | -------------------------------------------------------------------------------- /Client/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | %svelte.head% 12 | 13 | 14 |
%svelte.body%
15 | 16 | 17 | -------------------------------------------------------------------------------- /Ptero/routers/userRouter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Router, 3 | getLogs, 4 | getUser, 5 | getUsers, 6 | Context 7 | } from '../deps.ts' 8 | 9 | const userRouter = new Router(); 10 | 11 | // retrieving all users 12 | userRouter.get("/", async (ctx:Context, next:any) => { 13 | await getUsers(ctx) 14 | }); 15 | 16 | // retrieve one user by api key 17 | userRouter.get("/:api_key", async (ctx:Context, next:any) => { 18 | await getUser(ctx) 19 | }); 20 | 21 | export default userRouter; -------------------------------------------------------------------------------- /Ptero/routers/apiLogRouter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getLogs, 3 | Router, 4 | Context, 5 | getOneLog 6 | } from '../deps.ts'; 7 | 8 | const apiLogRouter = new Router(); 9 | 10 | // retrieving all the logs 11 | apiLogRouter.get("/", async (ctx:Context, next:any) => { 12 | await getLogs(ctx, next) 13 | }); 14 | 15 | // retrieve one of the logs by id 16 | apiLogRouter.get("/:id", async (ctx:Context, next:any) => { 17 | await getOneLog(ctx, next) 18 | }); 19 | 20 | export default apiLogRouter; -------------------------------------------------------------------------------- /Client/global.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --header-height: 5em; 3 | --bg-color: white; 4 | --base: 10px; 5 | --shadow-color: grey; 6 | --shadow-text: lightgrey; 7 | --background-color: lightblue; 8 | --main-color: #BDDA75; 9 | --accent-color: orangered; 10 | } 11 | 12 | html { 13 | height: 100%; 14 | width: 100%; 15 | } 16 | #svelte { 17 | height: 100%; 18 | width: 100%; 19 | } 20 | body { 21 | margin: 0; 22 | color: white; 23 | background-color: #101010; 24 | height: 100%; 25 | width: 100%; 26 | font-family: 'Montserrat', sans-serif; 27 | } 28 | -------------------------------------------------------------------------------- /Client/src/routes/__layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 8 | 9 |
10 | 11 | 28 | -------------------------------------------------------------------------------- /Ptero/models/dinosaurs.ts: -------------------------------------------------------------------------------- 1 | import { Dinosaur } from "../deps.ts"; 2 | 3 | export const Dinosaurs: Array = [ 4 | { 5 | id: "1", 6 | name: "Achillobator", 7 | era: "Late Cretaceous", 8 | area: "Mongolia", 9 | diet: "carnivorous", 10 | }, 11 | { 12 | id: "2", 13 | name: "Agilisaurus", 14 | era: "Late Jurassic", 15 | area: "China", 16 | diet: "herbivorous", 17 | }, 18 | { 19 | id: "3", 20 | name: "Melanorosaurus", 21 | era: "Late Triassic", 22 | area: "South Africa", 23 | diet: "omnivorous", 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /Client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2019 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /Ptero/models/users.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient, config } from '../deps.ts'; 2 | 3 | const { DB_HOST_URL, DB_NAME_USER } = config(); 4 | 5 | const dbHostUrl: any = DB_HOST_URL; 6 | const dbName: any = DB_NAME_USER; 7 | 8 | // connecting to mongoDB listed in .env file 9 | const client = new MongoClient(); 10 | await client.connect(dbHostUrl); 11 | const db = client.database(dbName); 12 | 13 | interface UserSchema { 14 | username: string; 15 | api_key: string; 16 | date_created: Date; 17 | usage: { count: number }; 18 | } 19 | 20 | const Users = db.collection("users"); 21 | 22 | export { db, Users }; 23 | -------------------------------------------------------------------------------- /Ptero/dockerfile-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | server_redis_database: 6 | image: redis 7 | container_name: server_redis 8 | # env_file: 9 | # - ../.env_development 10 | volumes: 11 | - ./redis_data:/data 12 | command: 'redis-server' 13 | restart: always 14 | expose: 15 | - "6379" 16 | ports: 17 | - "6379:6379" 18 | 19 | server_api: 20 | container_name: server_deno 21 | image: pterots/test 22 | volumes: 23 | - "./:/app" 24 | command: "deno test --allow-all --unstable __tests/redis.test.ts" 25 | depends_on: 26 | - "server_redis_database" 27 | ports: 28 | - "9000:9000" 29 | -------------------------------------------------------------------------------- /Ptero/models/APILogModel.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MongoClient, 3 | Bson, 4 | config 5 | } from '../deps.ts'; 6 | 7 | const { DB_HOST_URL_LOG, DB_NAME_LOG } = config() 8 | 9 | // host url and database name is pulled from .env file 10 | const dbHostUrl: any = DB_HOST_URL_LOG; 11 | const dbName: any = DB_NAME_LOG; 12 | 13 | // connecting to the mongoDB 14 | const client = new MongoClient(); 15 | await client.connect(dbHostUrl); 16 | const db = client.database(dbName); 17 | 18 | // Log schema 19 | interface LogSchema { 20 | _id: { $oid: string }, 21 | method: string, 22 | route: string, 23 | timeAccessed: Date, 24 | status: string, 25 | responseTime: string, 26 | APIKey: string, 27 | ipAddress: string, 28 | fromCache: boolean, 29 | } 30 | 31 | const APILog = db.collection("logs") 32 | 33 | export { APILog }; -------------------------------------------------------------------------------- /Client/src/lib/graphs/LogLine.svelte: -------------------------------------------------------------------------------- 1 | 52 | -------------------------------------------------------------------------------- /Client/src/lib/graphs/card.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 |

{Object.keys(cardValue)[0]}

14 | {cardValue[Object.keys(cardValue)[0]]} 15 |
16 | 17 | 30 | -------------------------------------------------------------------------------- /Client/svelte.config.js: -------------------------------------------------------------------------------- 1 | import preprocess from 'svelte-preprocess'; 2 | import adapter from '@sveltejs/adapter-node'; 3 | // import pkg from './package.json'; 4 | 5 | // /** @type {import('@sveltejs/kit').Config} */ 6 | // const config = { 7 | // // Consult https://github.com/sveltejs/svelte-preprocess 8 | // // for more information about preprocessors 9 | // preprocess: preprocess(), 10 | 11 | // kit: { 12 | // // hydrate the
element in src/app.html 13 | // target: '#svelte', 14 | 15 | // // vite: { 16 | // // ssr: { 17 | // // // external: Object.keys(pkg.Dependencies || {}) 18 | // // } 19 | // // } 20 | // } 21 | // }; 22 | 23 | export default { 24 | kit: { 25 | target: '#svelte', 26 | adapter: adapter({ 27 | // default options are shown 28 | out: 'build', 29 | precompress: false, 30 | env: { 31 | host: 'HOST', 32 | port: 'PORT' 33 | } 34 | }) 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Ptero/controllers/users.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Users, 3 | Context 4 | } from '../deps.ts' 5 | 6 | // get all the users 7 | export const getUsers = async (ctx: Context) => { 8 | try { 9 | const data: any = await Users.find({}, { noCursorTimeout: false }).toArray(); 10 | ctx.response.body = { 11 | status: true, 12 | data: data 13 | }; 14 | ctx.response.status = 200; 15 | 16 | } 17 | catch (err) { 18 | ctx.response.body = { status: false, data: null }; 19 | ctx.response.status = 404; 20 | console.log(err); 21 | } 22 | }; 23 | 24 | // get one user by api key 25 | export const getUser = async (ctx: any) => { 26 | 27 | const apiKey = ctx.params.api_key; 28 | const data: any = await Users.findOne({ api_key: apiKey }, { noCursorTimeout: false }) 29 | try{ 30 | if (data.api_key === apiKey) { 31 | ctx.response.body = { 32 | status: true, 33 | data: data, 34 | } 35 | ctx.response.status = 200; 36 | } 37 | } 38 | catch { 39 | ctx.response.body = { status: false, data: null }; 40 | ctx.response.status = 500; 41 | } 42 | }; -------------------------------------------------------------------------------- /Ptero/controllers/apiKey.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Users, 3 | Context 4 | } from '../deps.ts'; 5 | 6 | // checking if the api key exists in the database 7 | export const checkApiKey = async (ctx: Context) => { 8 | // check if request header contains API key 9 | checkIfApiKey(ctx) 10 | if (ctx.response.status === 401) return; 11 | let apiKey: any | string = ctx.request.headers.get('api_key'); 12 | 13 | const selectedUser: any | undefined = await Users.findOne({ api_key: apiKey }, { noCursorTimeout: false }); 14 | 15 | if (selectedUser) { 16 | ctx.response.status = 202; 17 | ctx.response.body = { 18 | success: true, 19 | data: selectedUser, 20 | }; 21 | } else { 22 | ctx.response.status = 401; 23 | ctx.response.body = { 24 | success: false, 25 | msg: "incorrect api key", 26 | }; 27 | } 28 | }; 29 | 30 | // checking if user provides an api key 31 | export const checkIfApiKey = async (ctx: Context) => { 32 | if(ctx.request.headers.has('api_key')) { 33 | ctx.response.status = 200; 34 | } 35 | else { 36 | ctx.response.body = { msg: "API key is required."} 37 | ctx.response.status = 401; 38 | } 39 | }; -------------------------------------------------------------------------------- /Ptero/__tests/superoak.test.ts: -------------------------------------------------------------------------------- 1 | import { Application, getLogs, Router, superoak } from "../deps.ts"; 2 | 3 | const router = new Router(); 4 | router.get("/", (ctx: any) => { 5 | ctx.response.body = "Hello Deno!"; 6 | }); 7 | 8 | router.get("/ptero", (ctx: any) => { 9 | ctx.response.body = "Hello Ptero!"; 10 | }); 11 | 12 | router.get("/log", async (ctx: any, next: any) => { 13 | await getLogs(ctx, next); 14 | }); 15 | 16 | const app = new Application(); 17 | 18 | app.use(router.routes()); 19 | app.use(router.allowedMethods()); 20 | 21 | // Send GET request 22 | Deno.test("it should support the Oak framework", async () => { 23 | const request = await superoak(app); 24 | await request.get("/").expect("Hello Deno!"); 25 | }); 26 | 27 | Deno.test("it should get hello ptero at /ptero", async () => { 28 | const request = await superoak(app); 29 | await request.get("/ptero").expect("Hello Ptero!"); 30 | }); 31 | 32 | // to get logs 33 | Deno.test("Log controller should produce json data", async () => { 34 | const request = await superoak(app); 35 | await request.get("/log").expect(200).expect( 36 | "Content-Type", 37 | "application/json; charset=utf-8", 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /Client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es2020", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "types": ["vite/client", "node"], 8 | "typeRoots": ["node_modules/@types"], 9 | "lib": ["ESNext"], 10 | // "target": "es2018", 11 | /** 12 | svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript 13 | to enforce using \`import type\` instead of \`import\` for Types. 14 | */ 15 | "importsNotUsedAsValues": "error", 16 | "isolatedModules": true, 17 | /** 18 | To have warnings/errors of the Svelte compiler at the correct position, 19 | enable source maps by default. 20 | */ 21 | "sourceMap": true, 22 | "esModuleInterop": true, 23 | "skipLibCheck": true, 24 | "forceConsistentCasingInFileNames": true, 25 | "baseUrl": ".", 26 | "allowJs": true, 27 | "checkJs": false, 28 | "paths": { 29 | "$app/*": [".svelte/dev/runtime/app/*", ".svelte/build/runtime/app/*"], 30 | "$lib/*": ["src/lib/*"], 31 | "@components": ["src/lib/components"], 32 | "@lib": ["src/lib"], 33 | "@utils": ["src/lib/utils"], 34 | } 35 | }, 36 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte", "src/electron.js"] 37 | } 38 | -------------------------------------------------------------------------------- /Client/src/lib/navbar.svelte: -------------------------------------------------------------------------------- 1 | 7 | 16 | 17 | 18 | 19 | logo 20 | (isOpen = !isOpen)} /> 21 | 22 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Client/src/lib/graphs/FusionVertical.svelte: -------------------------------------------------------------------------------- 1 | 63 | 64 |
65 | 66 |
67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Ptero!! 2 | There is currently no end-to-end solution to accelerate and monitor Deno built APIs. Ptero is a Deno built middleware application that allows API owners to get user information from their API as well as adding a Redis caching system to the API. 3 | 4 | There are two parts to this application Ptero, the actual Deno module that one would load into their project and PteroView desktop app to view the logged statistics. 5 | 6 | ## PteroView 7 | PteroView is a Electron application that will allow the API host to to manage all the logs to the server and visualize their metrics in variety of ways. For more information about the metrics, please refer to the document in (PteroView)[Client/README.md] 8 | 9 | ### To Get Started: 10 | - [ ] cd Client 11 | - [ ] npm install 12 | 13 | 14 | ## Ptero - Server Side 15 | Ptero allows data 16 | 17 | ### To Get Started: 18 | #### If you have not already installed or updated Deno: 19 | - [ ] Install deno: https://github.com/denoland/deno_install 20 | - [ ] Update if not sure of version: type deno upgrade in your terminal. 21 | 22 | ## Contributors 23 | - [Quentin Rousset](https://github.com/qrousset/) 24 | - [Rachel Weller](https://github.com/wellerr3/) 25 | - [Brian Vazquez](https://github.com/brianvazquez9) 26 | - [David Rhee](https://github.com/rheed14) 27 | 28 | ## Other Documents 29 | - PteroView - [README.md](Client/README.md) 30 | - Ptero (server-side) - [README.md](Ptero/README.md) 31 | - For more documentation - [Ptero](https://deno.land/x/ptero) in Deno.land 32 | -------------------------------------------------------------------------------- /Client/__tests/test.js: -------------------------------------------------------------------------------- 1 | import Counter from './Counter.svelte' 2 | import { render, fireEvent } from '@testing-library/svelte' 3 | 4 | it('it works', async () => { 5 | const { getByText, getByTestId } = render(Counter) 6 | 7 | const increment = getByText('increment') 8 | const decrement = getByText('decrement') 9 | const counter = getByTestId('counter-value') 10 | 11 | await fireEvent.click(increment) 12 | await fireEvent.click(increment) 13 | await fireEvent.click(increment) 14 | await fireEvent.click(decrement) 15 | 16 | expect(counter.textContent).toBe('2') 17 | 18 | // with jest-dom 19 | expect(counter).toHaveTextContent('2') 20 | }) 21 | 22 | 23 | // test that routes work on the header 24 | 25 | // testing for settings page 26 | //connection settings test 27 | // test for refresh button 28 | if('refresh button refreshes the logs page', async() => { 29 | const button = ; 30 | const { getByText, getByTestId } = render(button) 31 | 32 | // functional stuff? 33 | const refresh = getByText('refresh') 34 | const counter = getByTestId('refresh-counter') 35 | 36 | await fireEvent.click(button); 37 | await fireEvent.click(button); 38 | //expect content type of fetch request to be json-blah 39 | expect(counter) 40 | expect(refresh).toHaveTextContent('2') 41 | }) 42 | 43 | 44 | 45 | 46 | 47 | // test for the "BackEnd Address" 48 | // test for "API key" 49 | //redis settings test 50 | // time to leave update tests -------------------------------------------------------------------------------- /Ptero/utils/dataLogging.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Keeping track of the users and what information has been accessed is important in managing the RESTful API. 3 | These functinos will allow the API host to visualize all the requests in the server by storing the data into their cloud MongoDB. 4 | If additional information is desired from these logs, simply add variables that you want to monitor and retrieve the appropriate 5 | information from the request and response. 6 | */ 7 | 8 | import { addLog } from "../deps.ts"; 9 | 10 | export const logData = async (ctx: any, next: any) => { 11 | // Time Logger 12 | const start = Date.now(); 13 | await next(); 14 | const ms = Date.now() - start; 15 | ctx.response.headers.set("X-Response-Time", `${ms}ms`); 16 | const rt = ctx.response.headers.get("X-Response-Time"); 17 | 18 | // logs in the terminal (method - url - response time) 19 | console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`); 20 | 21 | const ipAddress = ctx.request.ip; 22 | const method = ctx.request.method; 23 | const route = ctx.request.url.pathname; 24 | const status = ctx.response.status; 25 | const fromCache = ctx.request.fromCache; 26 | 27 | // if api key is provided, it is retrieved from the request header, if not api key is equal to null. 28 | let APIKey; 29 | if (ctx.request.headers.has('api_key')) APIKey = ctx.request.headers.get('api_key'); 30 | else APIKey = null; 31 | 32 | const data = { 33 | ipAddress, 34 | method, 35 | APIKey, 36 | route, 37 | status, 38 | rt, 39 | fromCache, 40 | }; 41 | 42 | // add log to the database 43 | await addLog(data); 44 | }; 45 | -------------------------------------------------------------------------------- /Ptero/utils/middlewares.ts: -------------------------------------------------------------------------------- 1 | /* 2 | These functions will allow the users information and data provided in the route to be stored in 3 | Redis cache. Before caching, these functions will check to see if the information already 4 | exists in the cache. If the data is in the cache, information will be pulled from cache and skip the process 5 | of requesting to the route. Retrieving information from the cache will accerate the whole process of acquiring the data. 6 | You will be able to see the response time difference between when data is accessed from the cache and when they is pulled from the actual route. 7 | */ 8 | 9 | import { 10 | redisCheck, redisCheckUser, redisSet, redisSetUser, Context 11 | } from '../deps.ts'; 12 | 13 | // checking if data is in Redis cache via redisCheck funciton and if not storing data in to Redis cache via redisCheck function 14 | export const caching = async (ctx: any, func: any) => { 15 | const method: string = ctx.request.method; 16 | const reqURL: string = ctx.request.url.pathname; 17 | 18 | if (await redisCheck(ctx, func) === true) { 19 | ctx.request.fromCache = true; 20 | } else { 21 | ctx.request.fromCache = false; 22 | await redisSet(ctx, 3000); 23 | } 24 | }; 25 | 26 | // checking if user is in Redis cache via redisCheckUser funciton and if not storing data in to Redis cache via redisCheckUser function 27 | export const checkUser = async (ctx: Context, func: any) => { 28 | if (await redisCheckUser(ctx) === false) { 29 | await func(ctx); 30 | if (ctx.response.status === 202) await redisSetUser(ctx, 3000); 31 | else console.log("incorect API key"); 32 | } 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /Ptero/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Application, Router, logData, oakCors, Context, pteroRouter, apiLogRouter, userRouter 3 | } from './deps.ts'; 4 | // import { Application, Router, RouterContext } from "https://deno.land/x/oak/mod.ts"; 5 | // import { logData } from "./utils/dataLogging.ts"; 6 | // import { oakCors } from "https://deno.land/x/cors/mod.ts"; 7 | // import { Context } from "https://deno.land/x/oak@v9.0.1/context.ts" 8 | // import pteroRouter from "./routers/routers.ts"; 9 | // import apiLogRouter from "./routers/apiLogRouter.ts"; 10 | // import userRouter from "./routers/userRouter.ts"; 11 | const env = Deno.env.toObject(); 12 | const PORT = env.PORT || 9000; 13 | const HOST = env.HOST || "localhost"; 14 | 15 | const app = new Application(); 16 | const router = new Router(); 17 | 18 | app.use( 19 | oakCors({ 20 | origin: "http://localhost:3000", 21 | }), 22 | ); 23 | 24 | // move this under the 'logData' if you want to log the route to '/log' 25 | app.use(apiLogRouter.prefix("/log").routes()); 26 | app.use(userRouter.prefix("/users").routes()); 27 | 28 | // Logging of the methods, routes, and response time 29 | app.use(async (ctx, next) => { 30 | await logData(ctx, next); 31 | }); 32 | 33 | // routes to "/api" 34 | app.use(pteroRouter.prefix("/api").routes()); 35 | 36 | // default methods require to use different routes in Denoo 37 | app.use(router.routes()); 38 | app.use(router.allowedMethods()); 39 | 40 | // global error handling 41 | router.get("/(.*)", async (ctx: any) => { 42 | ctx.response.status = 404; 43 | ctx.response.body = "404 | Page not Found"; 44 | }); 45 | 46 | // listening to localhost:PORT 47 | console.log(`Server running on port ${PORT}`); 48 | await app.listen(`${HOST}:${PORT}`); 49 | -------------------------------------------------------------------------------- /Ptero/controllers/apiLog.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Bson, 3 | Context, 4 | APILog 5 | } from '../deps.ts'; 6 | 7 | // retrieve all the logs from the database 8 | export const getLogs = async (ctx: Context, next: any) => { 9 | try { 10 | const data: any = await APILog.find({}, { noCursorTimeout: false }).toArray(); 11 | ctx.response.body = { 12 | status: true, 13 | data: data 14 | }; 15 | ctx.response.status = 200; 16 | } 17 | catch (err) { 18 | console.log("catch getLog"); 19 | ctx.response.body = { status: false, data: null }; 20 | ctx.response.status = 500; 21 | console.log(err); 22 | } 23 | }; 24 | 25 | // get one log by id 26 | export const getOneLog = async (ctx: any, next: any) => { 27 | try { 28 | const id = ctx.params.id; 29 | const data: any = await APILog.findOne({ _id: new Bson.ObjectId(id) }, { noCursorTimeout: false }); 30 | 31 | ctx.response.body = { 32 | status: true, 33 | data: data, 34 | } 35 | ctx.response.status = 200; 36 | 37 | } 38 | catch (err) { 39 | ctx.response.body = { status: false, data: null }; 40 | ctx.response.status = 500; 41 | console.log(err); 42 | } 43 | }; 44 | 45 | // add new log to database 46 | export const addLog = async (ctx: any) => { 47 | try { 48 | let { method, route, status, APIKey, ipAddress, rt, fromCache } = await ctx; 49 | if (fromCache === undefined) fromCache = false; 50 | 51 | await APILog.insertOne({ 52 | method: method, 53 | route: route, 54 | timeAccessed: new Date(), 55 | status: status, 56 | responseTime: rt, 57 | APIKey: APIKey, 58 | ipAddress: ipAddress, 59 | fromCache: fromCache, 60 | }); 61 | } 62 | catch (err) { 63 | console.log(err); 64 | }; 65 | }; -------------------------------------------------------------------------------- /Ptero/__tests/redis.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | delay, 4 | redisConnect, 5 | redisSet, 6 | redisSetUser, 7 | } from "../deps.ts"; 8 | 9 | // start redis connection 10 | const redisClient = await redisConnect({ 11 | hostname: "server_redis_database", 12 | port: 6379, 13 | }); 14 | 15 | // test if pingging redis gets back pong 16 | Deno.test("Check if redis database is connected", async () => { 17 | const ping = await redisClient.ping(); 18 | assertEquals(ping, "PONG"); 19 | }); 20 | 21 | // test if can add object to redis 22 | Deno.test("Adds data to the redis cache", async () => { 23 | const ctx: any = { 24 | request: { url: { pathname: "key1" } }, 25 | response: { body: "redis tester" }, 26 | }; 27 | await redisSet(ctx, 2); 28 | 29 | let data = await redisClient.get("key1"); 30 | 31 | Deno.test("Data goes in Cache", async () => { 32 | assertEquals(data, `"${ctx.response.body}"`); 33 | }); 34 | 35 | Deno.test("data is delayed and deleted", async () => { 36 | const delayedPromise = delay(2000); 37 | const result = await delayedPromise; 38 | data = await redisClient.get("key1"); 39 | assertEquals(data, {}); 40 | }); 41 | }); 42 | 43 | Deno.test("Adds user to the redis cache", async () => { 44 | let newHeaders = new Headers({ api_key: "9999" }); 45 | const ctx = { 46 | request: { headers: newHeaders }, 47 | response: { body: "user tester" }, 48 | }; 49 | await redisSetUser(ctx, 2); 50 | 51 | let data = await redisClient.get("9999"); 52 | 53 | Deno.test("Data goes in Cache", async () => { 54 | assertEquals(data, `"${ctx.response.body}"`); 55 | }); 56 | 57 | Deno.test("User is delayed and deleted after time", async () => { 58 | const delayedPromise = delay(2000); 59 | const result = await delayedPromise; 60 | data = await redisClient.get("9999"); 61 | assertEquals(data, {}); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /Ptero/deps.ts: -------------------------------------------------------------------------------- 1 | //from deno.land 2 | import { connect as redisConnect } from "https://deno.land/x/redis/mod.ts"; 3 | import { assertEquals } from "https://deno.land/std@0.110.0/testing/asserts.ts"; 4 | import { delay } from "https://deno.land/std/async/mod.ts"; 5 | import { Application, Router } from "https://deno.land/x/oak@v9.0.1/mod.ts"; 6 | import { superoak } from "https://x.nest.land/superoak@4.4.0/mod.ts"; 7 | import { MongoClient, Bson } from "https://deno.land/x/mongo@v0.27.0/mod.ts"; 8 | import { config } from "https://deno.land/x/dotenv/mod.ts"; 9 | import { Context } from "https://deno.land/x/oak@v9.0.1/context.ts" 10 | import { oakCors } from "https://deno.land/x/cors/mod.ts"; 11 | 12 | //from within Ptero 13 | import { redisSet, redisSetUser, redisCheck, redisCheckUser } from "./utils/redis.ts"; 14 | import apiLogRouter from "./routers/apiLogRouter.ts"; 15 | import { getLogs, getOneLog, addLog } from "./controllers/apiLog.ts"; 16 | import { checkApiKey } from "./controllers/apiKey.ts"; 17 | import { caching, checkUser } from "./utils/middlewares.ts"; 18 | import { getUser, getUsers } from "./controllers/users.ts" 19 | import { APILog } from "./models/APILogModel.ts"; 20 | import { db, Users } from './models/users.ts'; 21 | import { redisClient } from "./models/redisClient.ts"; 22 | import { logData } from "./utils/dataLogging.ts"; 23 | import pteroRouter from "./routers/routers.ts"; 24 | import userRouter from "./routers/userRouter.ts"; 25 | 26 | export { 27 | redisConnect, 28 | assertEquals, 29 | redisSet, 30 | redisSetUser, 31 | delay, 32 | Application, 33 | Router, 34 | superoak, 35 | apiLogRouter, 36 | getLogs, 37 | MongoClient, 38 | Bson, 39 | config, 40 | Context, 41 | getOneLog, 42 | APILog, 43 | db, 44 | Users, 45 | checkApiKey, 46 | caching, 47 | checkUser, 48 | getUser, 49 | getUsers, 50 | addLog, 51 | redisCheck, 52 | redisCheckUser, 53 | redisClient, 54 | logData, 55 | oakCors, 56 | pteroRouter, 57 | userRouter 58 | } -------------------------------------------------------------------------------- /Client/src/routes/logs.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | Ptero - Logs 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {#each $Logs as log} 23 | 24 |
25 | {#if log.status >= 400 && log.status < 500} 26 |
27 | {:else if log.status >= 500} 28 | 29 | {:else} 30 | 31 | {/if} 32 | 33 | {#if log.method === 'GET'} 34 | 35 | {:else if log.method === 'PUT' || log.method === 'POST'} 36 | 37 | {:else} 38 | 39 | {/if} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {/each} 50 | 51 |
StatusMethodRouteDateResponse TimeAPI KeyCached
{log.status}{log.status}{log.status}{log.method}{log.method}{log.method}{log.route}{new Date(log.time).toUTCString()}{log.respTime}{log.key}{log.cached}
52 |
53 | 54 | 78 | -------------------------------------------------------------------------------- /.ebextensions/.travis.yml: -------------------------------------------------------------------------------- 1 | language: default 2 | os: linux 3 | services: 4 | - redis-server 5 | # can change to redis and the database and whatever else if needed 6 | branches: 7 | only: 8 | - main 9 | # can also set to testing in dev if needed 10 | # installs deno: 11 | before_install: 12 | - pwd 13 | # can change curl cmd to another install? 14 | - curl -fsSL https://deno.land/x/install/install.sh | sh 15 | - ls -l $HOME/.deno 16 | - export DENO_INSTALL="$HOME/.deno" 17 | - export PATH="$DENO_INSTALL/bin:$PATH" 18 | - deno run https://deno.land/std/examples/welcome.ts 19 | # this will run the deno hello world home page to ensure it's working 20 | # insert tests from ptero!: 21 | script: 22 | - cd ./simplecalc/test/ 23 | - sh run_test.sh 24 | - cd ../../fortune_cookies/test 25 | - sh run_test.sh 26 | 27 | 28 | # matrix: 29 | # include: 30 | # - os: osx 31 | # osx_image: xcode10.2 32 | # language: node_js 33 | # node_js: "10" 34 | # env: 35 | # - ELECTRON_CACHE=$HOME/.cache/electron 36 | # - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder 37 | 38 | # - os: linux 39 | # services: docker 40 | # language: generic 41 | 42 | # cache: 43 | # directories: 44 | # - node_modules 45 | # - $HOME/.cache/electron 46 | # - $HOME/.cache/electron-builder 47 | 48 | # script: 49 | # - | 50 | # if [ "$TRAVIS_OS_NAME" == "linux" ]; then 51 | # docker run --rm \ 52 | # --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') \ 53 | # -v ${PWD}:/project \ 54 | # -v ~/.cache/electron:/root/.cache/electron \ 55 | # -v ~/.cache/electron-builder:/root/.cache/electron-builder \ 56 | # electronuserland/builder:wine \ 57 | # /bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn release --linux --win" 58 | # else 59 | # yarn release 60 | # fi 61 | # before_cache: 62 | # - rm -rf $HOME/.cache/electron-builder/wine 63 | 64 | # branches: 65 | # except: 66 | # - "/^v\\d+\\.\\d+\\.\\d+$/" 67 | -------------------------------------------------------------------------------- /Client/src/lib/store.ts: -------------------------------------------------------------------------------- 1 | import { get, writable } from "svelte/store"; 2 | import { 3 | avgRspTimeCvsNC, 4 | reqPerEndpointAndMethodFn, 5 | reqPerStatusAndMethodProcess, 6 | RouteHistoryProcess, 7 | routePerDay, 8 | totalsPerStatus, 9 | } from "./storeProcessing.ts"; 10 | 11 | //Bulk of logs from the server 12 | const Logs = writable([]); 13 | 14 | //Processed logs for the graphs 15 | const ReqPerEndpointAndMethod = writable([]); 16 | const ReqPerStatusAndMethod = writable([]); 17 | const TotalsStatus = writable([]); 18 | const CachedvsNotCached = writable({}); 19 | const DailyData = writable({}); 20 | const DayRouteTotal = writable([]); 21 | const RouteHistory = writable([]); 22 | 23 | //Settings related store 24 | const Settings = writable({ 25 | serverAddress: "http://localhost:9000/log", 26 | refreshRate: "10", 27 | svelteAPIKey: "pwdawdjioawjdioq131", 28 | redisTTL: "100", 29 | }); 30 | 31 | //Function to retrieve the logs from the server 32 | const fetchLogs = async () => { 33 | let tempLogs = []; 34 | const url = get(Settings).serverAddress; 35 | const res = await fetch(url); 36 | const data = await res.json(); 37 | const loadedLogs = data.data.map((data, index: number) => { 38 | return { 39 | id: index + 1, 40 | time: data.timeAccessed, 41 | method: data.method, 42 | route: data.route, 43 | status: data.status, 44 | respTime: data.responseTime, 45 | key: data.APIKey, 46 | ip: data.ipAddress, 47 | cached: data.fromCache, 48 | }; 49 | }); 50 | tempLogs = await loadedLogs; 51 | await Logs.set(tempLogs.reverse()); 52 | 53 | totalsPerStatus(tempLogs, TotalsStatus); 54 | routePerDay(tempLogs, DailyData, DayRouteTotal); 55 | reqPerEndpointAndMethodFn(tempLogs, ReqPerEndpointAndMethod); 56 | reqPerStatusAndMethodProcess( 57 | tempLogs, 58 | ReqPerStatusAndMethod, 59 | ); 60 | avgRspTimeCvsNC(tempLogs, CachedvsNotCached); 61 | RouteHistoryProcess(tempLogs, RouteHistory); 62 | }; 63 | 64 | fetchLogs(); 65 | 66 | export { 67 | fetchLogs, 68 | CachedvsNotCached, 69 | DailyData, 70 | DayRouteTotal, 71 | Logs, 72 | ReqPerEndpointAndMethod, 73 | ReqPerStatusAndMethod, 74 | RouteHistory, 75 | Settings, 76 | TotalsStatus, 77 | }; 78 | -------------------------------------------------------------------------------- /Client/src/lib/store/types.ts: -------------------------------------------------------------------------------- 1 | interface Logs { 2 | id: number; 3 | method: string; 4 | route: string; 5 | time: string; 6 | status: number; 7 | respTime: string; 8 | key: string; 9 | ip: string; 10 | cached: boolean; 11 | } 12 | 13 | interface LogsResponse { 14 | _id: { $oid: string }, 15 | method: string, 16 | route: string, 17 | timeAccessed: Date, 18 | status: number, 19 | responseTime: string, 20 | APIKey: string, 21 | ipAddress: string, 22 | fromCache: boolean, 23 | } 24 | 25 | interface CachedvsNotCached { 26 | cached: number, 27 | notCached: number, 28 | } 29 | 30 | interface DailyData { 31 | [index: string]: { 32 | date?: string, 33 | totals: number, 34 | id?: string, 35 | [route: string]: any, 36 | } 37 | } 38 | 39 | interface DayRouteTotal { 40 | date: number, 41 | route: string, 42 | total: number, 43 | id: number, 44 | } 45 | 46 | 47 | // interface DayRouteTotal extends Array {} 48 | 49 | interface interfaceReqPerEndpointAndMethod { 50 | [index: string]: { 51 | route: string, 52 | GET: number, 53 | POST: number, 54 | PUT: number, 55 | DELETE: number, 56 | id: number, 57 | tot: number, 58 | } 59 | } 60 | interface interfaceReqPerStatus { 61 | [index: string]: { 62 | status: number, 63 | GET: number, 64 | POST: number, 65 | PUT: number, 66 | DELETE: number, 67 | id: number, 68 | tot: number, 69 | } 70 | } 71 | 72 | interface RouteDaily { 73 | [route: string]: { 74 | [strDate: string]: number 75 | } 76 | } 77 | 78 | interface TempDate { 79 | x: number, 80 | y: number 81 | } 82 | 83 | interface TotalsPerStatus { 84 | [status: string]: number, 85 | } 86 | 87 | interface ReqPerStatusAndMethod {} 88 | interface RouteHistory {} 89 | interface Settings {} 90 | interface TotalsStatus {} 91 | 92 | export type{ 93 | DayRouteTotal, 94 | Logs, 95 | LogsResponse, 96 | CachedvsNotCached, 97 | DailyData, 98 | interfaceReqPerEndpointAndMethod, 99 | ReqPerStatusAndMethod, 100 | RouteHistory, 101 | RouteDaily, 102 | Settings, 103 | TempDate, 104 | TotalsStatus, 105 | interfaceReqPerStatus, 106 | TotalsPerStatus, 107 | } -------------------------------------------------------------------------------- /Client/src/lib/graphs/BarVerticalOriginal.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 | 20 | 21 |
{totals[10 - value]['route']}
22 |
23 | 24 | 25 | {value} 26 | 27 | 28 | 29 | {#each stacks as stack, i} 30 | {#each stack.values as d} 31 | 32 |
33 | 34 | {/each} 35 | {/each} 36 | 37 |
38 | 39 | 96 | -------------------------------------------------------------------------------- /Client/src/lib/graphs/StackedBarHorizontal.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 | 21 | 22 |
{totals[10 - value]['route']}
23 |
24 | 25 | 26 |
27 | {value} 28 | 29 | {#each stacks as stack, i} 30 | {#each stack.values as d} 31 | 32 |
33 | 34 | {/each} 35 | {/each} 36 | 37 |
38 | 39 | 96 | -------------------------------------------------------------------------------- /Client/src/lib/graphs/StackedBarHorizontalStatus.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 | 21 | 22 |
{totals[6 - value]['status']}
23 |
24 | 25 | 26 |
27 | {value} 28 | 29 | {#each stacks as stack, i} 30 | {#each stack.values as d} 31 | 32 |
33 | 34 | {/each} 35 | {/each} 36 | 37 |
38 | 39 | 96 | -------------------------------------------------------------------------------- /Client/README.md: -------------------------------------------------------------------------------- 1 | # PteroView 2 | 3 | ## Getting Started: 4 | - Fork and clone this repo. 5 | - Then use the following commands to run the application: 6 | 7 | | Commands | | 8 | | ------- | ------------------------------------------- | 9 | | Install | npm install | 10 | | Develop | npm run dev | 11 | | Develop | npm run dev:svelte | 12 | | Build | npm run build | 13 | 14 | 15 | To get started on PteroView make sure the server that hosts your log data is running, then simply install and open the PteroView app. There you will be greated by the opening page, go into settings and paste in the URI to your log stream. At this point you should be able to edit certain settings in the setting page including refresh rate as well as a manual refresh button. 16 | 17 | From here you can see the graphs page as well as the home page to see a variaty of graphs and statistics about your logs. The logs themselves can be find under the log tab. 18 | 19 | ## Metrics 20 | Following model is all the information that is collected with the Ptero Deno module. 21 | ```js 22 | interface LogSchema { 23 | _id: { $oid: string }, 24 | method: string, 25 | route: string, 26 | timeAccessed: Date, 27 | status: string, 28 | responseTime: string, 29 | APIKey: string, 30 | ipAddress: string, 31 | fromCache: boolean, 32 | } 33 | ``` 34 | ### Requests per day over the last month 35 | - It is a vertical line graph that shows how many requests were made to the API depending on the day of the month. 36 | 37 | ![image ReqPerDayOverMonth](./static/images/ReqPerDayOverMonth.png) 38 | 39 | ### Avg cached time against non-cached 40 | - Simple GUI that shows the average time of the data retrieved from cache verses the average time of the data pulled from the server. 41 | 42 | ![image avgCached](./static/images/avgCached.png) 43 | 44 | ### Methods per status 45 | - It is a horizontal line graph that shows what type of methods executed with different status codes differentiated by color. 46 | 47 | ![image ReqPerDayOverMonth](./static/images/methodPerStatus.png) 48 | 49 | ### Requests per endpoint and method 50 | - It is a horizontal line graph that shows what type of methods were requested to the different endpoint of the API's. 51 | 52 | ![image ReqPerDayOverMonth](./static/images/reqPerEndpoint.png) 53 | 54 | ### More to Come! 55 | - The open source nature of our project means that new visualizations can be added using the data already collected from the Ptero module. Including user metrics, cached statistics, and usage over time! 56 | -------------------------------------------------------------------------------- /Client/src/lib/graphs/StackedBarVerticalOriginal.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 |
30 | 31 | 32 |
{value}
33 |
34 | 35 | 36 | {value} 37 | 38 | 39 | 40 | {#each stacks as stack, i} 41 | {#each stack.values as d} 42 | 43 |
44 | 45 | {/each} 46 | {/each} 47 | 48 |
49 | 50 | 101 | -------------------------------------------------------------------------------- /Client/src/electron.cjs: -------------------------------------------------------------------------------- 1 | const windowStateManager = require('electron-window-state'); 2 | const contextMenu = require('electron-context-menu'); 3 | const { app, BrowserWindow } = require('electron'); 4 | const serve = require('electron-serve'); 5 | const { fork } = require('child_process'); 6 | 7 | try { 8 | require('electron-reloader')(module); 9 | } catch (e) { 10 | console.error(e); 11 | } 12 | 13 | const serveURL = serve({ directory: '.' }); 14 | const port = process.env.PORT || 3000; 15 | // = 3000; 16 | const dev = !app.isPackaged; 17 | let mainWindow; 18 | 19 | function createWindow() { 20 | let windowState = windowStateManager({ 21 | defaultWidth: 1920, 22 | defaultHeight: 1080 23 | }); 24 | 25 | const mainWindow = new BrowserWindow({ 26 | backgroundColor: 'whitesmoke', 27 | titleBarStyle: 'hidden', 28 | autoHideMenuBar: false, //it was true 29 | trafficLightPosition: { 30 | x: 17, 31 | y: 32 32 | }, 33 | minHeight: 450, 34 | minWidth: 500, 35 | webPreferences: { 36 | enableRemoteModule: true, 37 | contextIsolation: true, // it was true 38 | nodeIntegration: true, 39 | spellcheck: false, 40 | devTools: true 41 | }, 42 | x: windowState.x, 43 | y: windowState.y, 44 | width: windowState.width, 45 | height: windowState.height 46 | }); 47 | 48 | windowState.manage(mainWindow); 49 | 50 | mainWindow.once('ready-to-show', () => { 51 | mainWindow.show(); 52 | mainWindow.focus(); 53 | }); 54 | 55 | mainWindow.on('close', () => { 56 | windowState.saveState(mainWindow); 57 | }); 58 | 59 | return mainWindow; 60 | } 61 | 62 | contextMenu({ 63 | showLookUpSelection: false, 64 | showSearchWithGoogle: true, 65 | showCopyImage: false, 66 | prepend: (defaultActions, params, browserWindow) => [ 67 | { 68 | label: 'Make App 💻' 69 | } 70 | ] 71 | }); 72 | 73 | function loadVite(port) { 74 | mainWindow.loadURL(`http://localhost:${port}`).catch((e) => { 75 | console.log('Error loading URL, retrying', e); 76 | setTimeout(() => { 77 | loadVite(port); 78 | }, 200); 79 | }); 80 | } 81 | 82 | function createMainWindow() { 83 | mainWindow = createWindow(); 84 | mainWindow.once('close', () => { 85 | mainWindow = null; 86 | }); 87 | 88 | if (dev) loadVite(port); 89 | else { 90 | serveURL(mainWindow); 91 | ssr = fork(`${__dirname}/index.js`); 92 | } 93 | } 94 | 95 | app.on('ready', createMainWindow); 96 | app.on('activate', () => { 97 | if (!mainWindow) { 98 | createMainWindow(); 99 | } 100 | }); 101 | app.on('ready', function () { 102 | mainWindow = new BrowserWindow({ width: 800, height: 600 }); 103 | mainWindow.webContents.openDevTools(); 104 | }); 105 | 106 | app.on('window-all-closed', () => { 107 | if (process.platform !== 'darwin') app.quit(); 108 | }); 109 | -------------------------------------------------------------------------------- /Ptero/utils/redis.ts: -------------------------------------------------------------------------------- 1 | import { Context, redisClient } from "../deps.ts"; 2 | // import { redisClient } from "../models/redisClient.ts"; 3 | // import { Context } from "https://deno.land/x/oak@v9.0.1/context.ts" 4 | 5 | /* 6 | This is constant variable that determines the expiration time of the data stored in cache (time-to-live). 7 | Redis takes time-to-live in seconds, hence time-to-live of 300 will be equivalent to 5 mintues. 8 | - ex) 86400 seconds = 24 hours, 43200 seconds = 12 hours, 3600 seconds = 1 hour, etc. 9 | 10 | When the route that already exists in the cache is requested, the expiration time will be renewed. 11 | */ 12 | 13 | const expireTime = 43200; 14 | 15 | // check if data is in the redis cache 16 | const redisCheck = async (ctx: Context, func: any) => { 17 | const url = ctx.request.url.pathname; 18 | let cached = await redisClient.get(url); 19 | 20 | if (cached) { 21 | ctx.response.body = JSON.parse(cached); 22 | 23 | // setting new expiration time when requested again 24 | await redisClient.expire(`${url}`, expireTime); 25 | return true; 26 | } else { 27 | await func(ctx); 28 | return false; 29 | } 30 | }; 31 | 32 | // check if user exists in the redis cache 33 | const redisCheckUser = async (ctx: any) => { 34 | let key: string; 35 | if (ctx.request.headers.has("api_key")) { 36 | key = ctx.request.headers.get("api_key"); 37 | } else key = ""; 38 | 39 | let cached = await redisClient.get(key); 40 | if (cached) { 41 | ctx.response.body = JSON.parse(cached); 42 | 43 | // setting new expiration time when requested again if same data exists 44 | await redisClient.expire(`${key}`, expireTime); 45 | return true; 46 | } else { 47 | return false; 48 | } 49 | }; 50 | 51 | // storing requested data in the cache with expiration time 52 | const redisSet = async (ctx: Context, time: number) => { 53 | const url = ctx.request.url.pathname; 54 | const resp = await ctx.response.body; 55 | const respJSON = JSON.stringify(resp); 56 | 57 | // set time-to-live for the data stored in cache 58 | await redisClient.set(url, respJSON, { ex: time }); 59 | }; 60 | 61 | // storing requested user data in the cache with expiration time 62 | // expiration time is provided upon invoking this function and does not depend on the global constant variable provided in the top of this file. 63 | const redisSetUser = async (ctx: any, time: number) => { 64 | let key: string; 65 | if (ctx.request.headers.has("api_key")) { 66 | key = ctx.request.headers.get("api_key"); 67 | } else key = ""; 68 | 69 | const resp = await ctx.response.body; 70 | const respJSON = JSON.stringify(resp); 71 | 72 | // set time-to-live for the user stored in cache 73 | await redisClient.set(key, respJSON, { ex: time }); 74 | ctx.response.status = 200; 75 | }; 76 | 77 | export { redisCheck, redisCheckUser, redisSet, redisSetUser }; 78 | -------------------------------------------------------------------------------- /Client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.1", 4 | "main": "src/electron.cjs", 5 | "description": "electron", 6 | "author": "ptero", 7 | "scripts": { 8 | "dev": "NODE_ENV=dev npm run dev:all", 9 | "el": "electron .", 10 | "dev:all": "concurrently -n=svelte,electron -c='#ff3e00',blue \"npm run dev:svelte\" \"npm run dev:electron\"", 11 | "dev:svelte": "svelte-kit dev", 12 | "dev:electron": "electron src/electron.cjs", 13 | "build": "cross-env NODE_ENV=production npm run build:svelte && npm run build:electron", 14 | "build:svelte": "svelte-kit build", 15 | "build:electron": "electron-builder -mwl --config build.config.json", 16 | "start": "svelte-kit start" 17 | }, 18 | "devDependencies": { 19 | "@sveltejs/adapter-node": "^1.0.0-next.55", 20 | "@babel/core": "^7.15.8", 21 | "@babel/preset-env": "^7.15.8", 22 | "@sveltejs/kit": "next", 23 | "@testing-library/jest-dom": "^5.14.1", 24 | "@testing-library/svelte": "^3.0.3", 25 | "@typescript-eslint/eslint-plugin": "^4.31.1", 26 | "@typescript-eslint/parser": "^4.31.1", 27 | "babel-jest": "^27.3.1", 28 | "d3": "^7.1.1", 29 | "eslint": "^7.32.0", 30 | "eslint-config-prettier": "^8.3.0", 31 | "eslint-plugin-svelte3": "^3.2.1", 32 | "fs": "^0.0.1-security", 33 | "jest": "^27.3.1", 34 | "mongoose": "^6.0.10", 35 | "prettier": "^2.4.1", 36 | "prettier-plugin-svelte": "^2.4.0", 37 | "svelte": "^3.42.6", 38 | "svelte-check": "^2.2.6", 39 | "svelte-jester": "^2.1.5", 40 | "svelte-preprocess": "^4.9.4", 41 | "sveltestrap": "^5.6.3", 42 | "tslib": "^2.3.1", 43 | "typescript": "^4.4.3", 44 | "@sveltejs/pancake": "^0.0.18", 45 | "bootstrap": "^5.1.3", 46 | "electron": "^12.0.9", 47 | "electron-builder": "^22.10.5", 48 | "pancake": "^3.4.1", 49 | "svelte-kit": "^1.0.0", 50 | "@sveltejs/adapter-static": "^1.0.0-next.11" 51 | }, 52 | "type": "module", 53 | "engines": { 54 | "node": ">= 14.17.4" 55 | }, 56 | "dependencies": { 57 | "@sveltejs/pancake": "^0.0.18", 58 | "dotenv": "^10.0.0", 59 | "fs": "^0.0.1-security", 60 | "fusioncharts": "^3.18.0", 61 | "svelte-fusioncharts": "^1.0.0", 62 | "@sveltejs/adapter-static": "^1.0.0-next.11", 63 | "@sveltejs/kit": "next", 64 | "@types/electron-window-state": "^2.0.34", 65 | "@typescript-eslint/eslint-plugin": "^4.24.0", 66 | "@typescript-eslint/parser": "^4.24.0", 67 | "concurrently": "^6.1.0", 68 | "cross-env": "^7.0.3", 69 | "eslint": "^7.26.0", 70 | "eslint-plugin-svelte": "^1.1.2", 71 | "npm-run-all": "^4.1.5", 72 | "prettier": "^2.3.0", 73 | "prettier-plugin-svelte": "^2.3.0", 74 | "sass": "^1.33.0", 75 | "svelte": "^3.44.0", 76 | "svelte-check": "^1.5.4", 77 | "svelte-preprocess": "^4.7.3", 78 | "typescript": "^4.2.4", 79 | "vite": "^2.3.3", 80 | "electron-connect": "^0.6.3", 81 | "electron-packager": "^15.2.0", 82 | "electron-reloader": "^1.2.1", 83 | "electron-updater": "^4.3.9", 84 | "electron-context-menu": "^2.5.0", 85 | "electron-serve": "^1.1.0", 86 | "electron-window-state": "^5.0.3" 87 | } 88 | } -------------------------------------------------------------------------------- /Client/src/routes/index.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | Ptero - Welcome to Ptero 11 |
12 |
13 |

Requests per day over the last month

14 | 15 |
16 |
17 | {#each $TotalsStatus as status} 18 |
19 | 20 |
21 | {/each} 22 |
23 |
24 |

Requests per endpoint and method

25 | {#if $ReqPerEndpointAndMethod} 26 | 31 | {/if} 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {#each $Logs as log} 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {/each} 54 | 55 |
MethodRouteResponse TimeStatusCached
{log.method}{log.route}{log.respTime}{log.status}{log.cached}
56 |
57 |
58 |
59 | 60 | 117 | -------------------------------------------------------------------------------- /Client/src/routes/settings.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | Ptero - Settings 8 |
9 | 10 | 16 |

Server

17 |
18 |
19 | 20 | 27 | {$Settings.serverAddress}

28 |
29 |
30 | 31 | 38 | {$Settings.svelteAPIKey}

39 |
40 |
41 | 42 | 49 | {$Settings.refreshRate}

50 | 51 |
52 |
53 |
54 | 59 |

Caching Time to Leave

60 |
61 |
62 | 63 | 70 | {$Settings.redisTTL} 71 |

72 |
73 |
74 |
75 |
76 |
77 |
78 | 79 | 119 | -------------------------------------------------------------------------------- /Client/src/lib/graphs/Histogram.svelte: -------------------------------------------------------------------------------- 1 | 144 | -------------------------------------------------------------------------------- /Client/src/routes/graphs.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 | Ptero - Graphs 21 | 29 |
30 |
31 |

Requests per day over the last month

32 | 33 |
34 | 35 |
36 |

Avg cached time against non-cached

37 |
38 |
39 | {$CachedvsNotCached.cached}s 40 |
41 |
/
42 |
43 | {$CachedvsNotCached.notCached}s 44 |
45 |
46 |
47 |
48 |

Methods per status

49 | 54 |
55 |
56 |

Requests per endpoint and method

57 |
58 | {#if $ReqPerEndpointAndMethod} 59 | 64 | {/if} 65 |
66 |
67 |
68 | 69 | 76 |
77 |
78 |
79 |
80 | 81 | 122 | -------------------------------------------------------------------------------- /Ptero/controllers/dinosaurs.ts: -------------------------------------------------------------------------------- 1 | import { 2 | v4, Dinosaur, Dinosaurs 3 | } from '../deps.ts'; 4 | 5 | // get all the dinosaurs 6 | const getDinosaurs = async ({ response }: { response: any }) => { 7 | if(response.status === 200) { 8 | response.body = { 9 | success: true, 10 | data: Dinosaurs, 11 | }; 12 | } 13 | else { 14 | response.body = { 15 | success: false, 16 | msg: "Wrong API key", 17 | }; 18 | } 19 | } 20 | 21 | // get one dinosaur by id 22 | const getDinosaur = async ( 23 | { params, response }: { params: { id: string }; response: any }, 24 | ) => { 25 | const selectedDino: Dinosaur | undefined = Dinosaurs.find((dino) => 26 | dino.id === params.id 27 | ); 28 | if (selectedDino) { 29 | response.status = 200; 30 | response.body = { 31 | success: true, 32 | data: selectedDino, 33 | }; 34 | 35 | } else { 36 | response.status = 404; 37 | response.body = { 38 | success: false, 39 | msg: "Dinosaur Not Found", 40 | }; 41 | } 42 | }; 43 | 44 | // add a dinosaur 45 | const addDinosaur = async ( 46 | { request, response }: { request: any; response: any }, 47 | ) => { 48 | if (!request.hasBody) { 49 | response.status = 400; 50 | response.body = { 51 | success: false, 52 | msg: "No data", 53 | }; 54 | } else { 55 | const { value: dinosaurBody } = await request.body(); 56 | const dinosaur: Dinosaur = dinosaurBody; 57 | dinosaur.id = v4.generate(); 58 | Dinosaurs.push(dinosaur); 59 | response.status = 201; 60 | response.body = { 61 | success: true, 62 | data: dinosaur, 63 | }; 64 | } 65 | }; 66 | 67 | // delete existing dinosaur 68 | const deleteDinosaur = ( { params, response }: { params: { id: string }; response: any } ) => { 69 | const filteredDinosaurs: Array = Dinosaurs.filter( 70 | (dinosaur: Dinosaur) => (dinosaur.id !== params.id), 71 | ); 72 | if (filteredDinosaurs.length === Dinosaurs.length) { 73 | response.status = 404; 74 | response.body = { 75 | success: false, 76 | msg: "Not found", 77 | }; 78 | } else { 79 | Dinosaurs.splice(0, Dinosaurs.length); 80 | Dinosaurs.push(...filteredDinosaurs); 81 | response.status = 200; 82 | response.body = { 83 | success: true, 84 | msg: `Dinosaur with id ${params.id} has been deleted`, 85 | }; 86 | } 87 | }; 88 | 89 | // update existing dinosaur 90 | const updateDinosaur = async ( 91 | { params, request, response }: { 92 | params: { id: string }; 93 | request: any; 94 | response: any; 95 | }, 96 | ) => { 97 | const requestedDinosaur: Dinosaur | undefined = Dinosaurs.find( 98 | (dinosaur: Dinosaur) => dinosaur.id === params.id, 99 | ); 100 | if (requestedDinosaur) { 101 | const { value: updatedDinosaurBody } = await request.body(); 102 | const updatedDinosaurs: Array = Dinosaurs.map( 103 | (dinosaur: Dinosaur) => { 104 | if (dinosaur.id === params.id) { 105 | return { 106 | ...dinosaur, 107 | ...updatedDinosaurBody, 108 | }; 109 | } else { 110 | return dinosaur; 111 | } 112 | }, 113 | ); 114 | 115 | Dinosaurs.splice(0, Dinosaurs.length); 116 | Dinosaurs.push(...updatedDinosaurs); 117 | response.status = 200; 118 | response.body = { 119 | success: true, 120 | msg: `Dinosaur id ${params.id} updated`, 121 | }; 122 | } else { 123 | response.status = 404; 124 | response.body = { 125 | success: false, 126 | msg: `Not Found`, 127 | }; 128 | } 129 | }; 130 | 131 | export { 132 | addDinosaur, 133 | deleteDinosaur, 134 | getDinosaur, 135 | getDinosaurs, 136 | updateDinosaur, 137 | }; 138 | -------------------------------------------------------------------------------- /Ptero/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Ptero! 2 | 3 | A middleware framework for Deno native RESTful APIs to add redis caching and log data for later retrieval and use. This README focuses on the mechanics of the Ptero framework and intended for those familiar with JavaScript frameworks as well as a decent understanding of Deno. 4 | 5 | ## Getting Started: 6 | 7 | ### Application, middleware, and context 8 | The Ptero application coordinates the data logging and redis caching for one's Deno based API. The deno module would have to be loaded into the top of all of the API owners files that will be using the module as outlined below. 9 | ```js 10 | import { caching, userCaching, logData } from "https://deno.land/x/ptero"; 11 | ``` 12 | ## Data Logging 13 | Every request to the server will be logged in designated cloud database. Then the web or Electron application of Ptero will retrieve log data from the database and allow visualization of the data with a user-friendly interface. In order to enable data logging, add the following lines of code in your main server file. For more information see [PteroView](../Client/README.md). 14 | 15 | This code along with an import statement for it should be put into the main server file or equivalent. 16 | ```js 17 | // Data Logging 18 | app.use(async (ctx, next) => { 19 | await logData(ctx, next); 20 | }); 21 | ``` 22 | 23 | ## Redis Caching 24 | In order to power the caching capabilities of Ptero, the use of Redis is required. Redis caching allows acceleration of the data processed in the server. The information requested to the server for the first time will be stored in the Redis Cache with the expiration time (aka **_time-to-live_**). When the same data is requested within the expiration time, the information will be pulled from the cache. This process is potentially faster than the process of retrieving information from the server. The default set-up of our app requires a user to have an API key. If the host decides not to require an API key for the users, simply remove the _checkApiKey_ function from the below code. 25 | 26 | ### To Set-up Redis: 27 | - Make sure you have Redis installed in your machine. 28 | - To install Redis, check out the [Redis Quick Start Guide](https://redis.io/topics/quickstart). 29 | - Run a Redis instance: 30 | - For Mac Users: 31 | - type redis-cli in your terminal. 32 | - For WSL/Windows Users: 33 | - type: sudo service redis-server start in your terminal. 34 | - then type: redis-cli in your terminal. 35 | - keys * gets all of the keys stored in Redis cache. 36 | - get (key) gets the value associated with the key. 37 | 38 | ### Prerequisite 39 | ```js 40 | // Example Routing 41 | const testRouter = new Router(); 42 | ``` 43 | This code along with an import statement for it should be put into the router that would route the user to the api key checking. 44 | ```js 45 | // If the server requires an api key 46 | testRouter.use("/", async (ctx: Context, next: any) => { 47 | await checkUser(ctx, checkApiKey); 48 | if (ctx.response.status === 401) return; 49 | await next(); 50 | }); 51 | ``` 52 | This code along with an import statement for it should be put into the router that would route the user to the data. 53 | ```js 54 | // Example Get Method 55 | testRouter.get("/endpoint", async (ctx: Context, next: any) => { 56 | await caching(ctx, getController); 57 | }); 58 | ``` 59 | 60 | ## Testing 61 | We have build our test suites using a Deno third party module: [SuperOak](https://deno.land/x/superoak@4.4.0). SuperOak was used for testing HTTP in Deno's Oak web framework. Deno also has built-in testing using __*Deno.test*__, which was used in our application to test Redis caching of the server. 62 | 63 | ## Dependencies 64 | - [Oak](https://deno.land/x/oak@v9.0.1) 65 | - [Redis](https://deno.land/x/redis@v0.25.0) 66 | 67 | 68 | -------------------------------------------------------------------------------- /Client/src/lib/graphs/Table.svelte: -------------------------------------------------------------------------------- 1 | 107 | -------------------------------------------------------------------------------- /Client/static/sveltekit-electron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PTERO 23 | 24 | PTERO 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 54 | 55 | 56 | 58 | 60 | 68 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Client/static/sveltekit-electron1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PTERO 23 | 24 | PTERO 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 54 | 55 | 56 | 58 | 60 | 68 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Client/src/lib/storeProcessing.ts: -------------------------------------------------------------------------------- 1 | /* This file is used to store the different functions supporting the computation 2 | * of the difference pieces of state used in the graphs */ 3 | import type { 4 | Logs, 5 | DailyData, 6 | interfaceReqPerEndpointAndMethod, 7 | interfaceReqPerStatus, 8 | DayRouteTotal, 9 | RouteDaily, 10 | TempDate, 11 | TotalsPerStatus, 12 | } from "./store/types.ts"; 13 | 14 | const totalsPerStatus = async (tempLogs: Logs[], TotalsStatus) => { 15 | const totalsPerStatusObj: TotalsPerStatus = {}; 16 | const totalsPerStatusArr: TotalsPerStatus[] = []; 17 | 18 | await tempLogs.forEach((el: Logs) => { 19 | if (!totalsPerStatusObj[el.status]) totalsPerStatusObj[el.status] = 0; 20 | totalsPerStatusObj[el.status] += 1; 21 | }); 22 | for (const status in totalsPerStatusObj) { 23 | const temp: any = {}; 24 | temp[status] = totalsPerStatusObj[status]; 25 | totalsPerStatusArr.push(temp); 26 | } 27 | TotalsStatus.set(totalsPerStatusArr); 28 | return; 29 | }; 30 | 31 | const reqPerEndpointAndMethodFn = async (tempLogs: Logs[], ReqPerEndpointAndMethod: interfaceReqPerEndpointAndMethod) => { 32 | const routeObj: interfaceReqPerEndpointAndMethod = {}; 33 | const tempIndexBars = []; 34 | 35 | tempLogs.forEach((el: Logs, index: number) => { 36 | if (!routeObj[el.route]) { 37 | routeObj[el.route] = { 38 | route: el.route, 39 | GET: 0, 40 | POST: 0, 41 | PUT: 0, 42 | DELETE:0, 43 | id: index + 1, 44 | tot: 0 45 | }; 46 | } 47 | if (el.method === "GET") routeObj[el.route]["GET"] += 1; 48 | else if (el.method === "POST") routeObj[el.route]["POST"] += 1; 49 | else if (el.method === "PUT") routeObj[el.route]["PUT"] += 1; 50 | else if (el.method === "DELETE") routeObj[el.route]["DELETE"] += 1; 51 | routeObj[el.route]["tot"] += 1; 52 | }); 53 | 54 | for (const route in routeObj) { 55 | tempIndexBars.push(routeObj[route]); 56 | } 57 | 58 | await tempIndexBars.sort((a, b) => { 59 | return b["tot"] - a["tot"]; 60 | }); 61 | await tempIndexBars.forEach((element, index) => { 62 | element.id = index + 1; 63 | }); 64 | await ReqPerEndpointAndMethod.set(tempIndexBars); 65 | return; 66 | }; 67 | 68 | const reqPerStatusAndMethodProcess = async ( 69 | tempLogs: Logs[], 70 | ReqPerStatusAndMethod: interfaceReqPerStatus, 71 | ) => { 72 | const statusObj: interfaceReqPerStatus = {}; 73 | const tempStatusObj = []; 74 | 75 | tempLogs.forEach((el, index: number) => { 76 | // const test = {}; 77 | // test["status"] = el.status; 78 | if (!statusObj[el.status]) { 79 | statusObj[el.status] = { 80 | status: el.status, 81 | GET: 0, 82 | POST: 0, 83 | PUT: 0, 84 | DELETE:0, 85 | id: index + 1, 86 | tot: 0 87 | }; 88 | } 89 | if (el.method === "GET") statusObj[el.status]["GET"] += 1; 90 | else if (el.method === "POST") statusObj[el.status]["POST"] += 1; 91 | else if (el.method === "PUT") statusObj[el.status]["PUT"] += 1; 92 | else if (el.method === "DELETE") statusObj[el.status]["DELETE"] += 1; 93 | statusObj[el.status]["tot"] += 1; 94 | }); 95 | 96 | for (const route in statusObj) { 97 | tempStatusObj.push(statusObj[route]); 98 | } 99 | 100 | await tempStatusObj.sort((a, b) => { 101 | return b["tot"] - a["tot"]; 102 | }); 103 | await tempStatusObj.forEach((element, index) => { 104 | element.id = index + 1; 105 | }); 106 | await ReqPerStatusAndMethod.set(tempStatusObj); 107 | 108 | return; 109 | }; 110 | 111 | const avgRspTimeCvsNC = async (tempLogs: Logs[], CachedvsNotCached: any) => { 112 | const avg = { cached: 0, notCached: 0 }; 113 | const cached = { time: 0, num: 0 }; 114 | const notCached = { time: 0, num: 0 }; 115 | 116 | tempLogs.forEach((logs) => { 117 | if (logs.cached === true) { 118 | cached.time = cached.time + parseInt(logs.respTime, 10); 119 | cached.num += 1; 120 | } else { 121 | notCached.time += parseInt(logs.respTime, 10); 122 | notCached.num += 1; 123 | } 124 | }); 125 | avg.cached = Math.floor(cached.time / cached.num); 126 | avg.notCached = Math.floor(notCached.time / notCached.num); 127 | CachedvsNotCached.set(avg); 128 | return; 129 | }; 130 | 131 | const routePerDay = async (tempLogs: Logs[], DailyData: DailyData, DayRouteTotal: DayRouteTotal) => { 132 | const tempDailyData: DailyData = {}; 133 | await tempLogs.forEach((log: any) => { 134 | const route = log.route; 135 | const date = log.time; 136 | const year = date.substring(0, 4); 137 | const month = date.substring(5, 7); 138 | const day = date.substring(8, 10); 139 | // const strDate = `${year}/${month}/${day}`; 140 | const strDate = day; 141 | 142 | if (!tempDailyData[strDate]) { 143 | tempDailyData[strDate] = {totals: 0}; 144 | // tempDailyData[strDate]["totals"] = 0; 145 | } 146 | tempDailyData[strDate]["totals"] += 1; 147 | if (!tempDailyData[strDate][route]) { 148 | tempDailyData[strDate][route] = { 149 | "total": 0, 150 | "status": {}, 151 | "cached": { "cached": 0, "notCached": 0 }, 152 | }; 153 | } 154 | tempDailyData[strDate][route]["total"] += 1; 155 | if (log.cached === true) { 156 | tempDailyData[strDate][route]["cached"]["cached"] += 1; 157 | } else tempDailyData[strDate][route]["cached"]["notCached"] += 1; 158 | 159 | if (!tempDailyData[strDate][route]["status"][log.status]) { 160 | tempDailyData[strDate][route]["status"][log.status] = 0; 161 | } 162 | tempDailyData[strDate][route]["status"][log.status] += 1; 163 | }); 164 | 165 | DailyData.set(tempDailyData); 166 | 167 | const tempDayRouteTotal = []; 168 | let id = 0; 169 | for (const key in tempDailyData) { 170 | for (const prop in tempDailyData[key]) { 171 | if (prop !== "totals") { 172 | const currentObj: DayRouteTotal = { 173 | date: parseInt(key), 174 | route: prop, 175 | total: tempDailyData[key][prop]["total"], 176 | id: id, 177 | }; 178 | tempDayRouteTotal.push(currentObj); 179 | id++; 180 | } 181 | } 182 | } 183 | DayRouteTotal.set(tempDayRouteTotal); 184 | return; 185 | }; 186 | 187 | const RouteHistoryProcess = async (tempLogs: Logs[], RouteHistory: any) => { 188 | const tempRouteDaily: RouteDaily = {} 189 | await tempLogs.forEach((log: Logs) => { 190 | const route: string = log.route; 191 | const date: string = log.time; 192 | const year = date.substring(0, 4); 193 | const month = date.substring(5, 7); 194 | const day = date.substring(8, 10); 195 | const strDate = `${day}`; 196 | 197 | if (!tempRouteDaily[route]) tempRouteDaily[route] = {}; 198 | if (!tempRouteDaily[route][strDate]) tempRouteDaily[route][strDate] = 0; 199 | tempRouteDaily[route][strDate]++; 200 | }); 201 | 202 | const allDays: string[] = []; 203 | for (const key in tempRouteDaily) { 204 | for (const day in tempRouteDaily[key]) { 205 | // console.log(day); 206 | if (!allDays.includes(day)) allDays.push(day); 207 | } 208 | } 209 | 210 | const DailyCallsPerRoute = []; 211 | for (const key in tempRouteDaily) { 212 | const currentObj = { name: "", data: [] }; 213 | currentObj.name = key; 214 | for (const day in allDays) { 215 | if (tempRouteDaily[key][allDays[day]]) { 216 | const tempDate: TempDate = { 217 | x: parseFloat(allDays[day]), 218 | y: tempRouteDaily[key][allDays[day]] 219 | }; 220 | currentObj.data.push(tempDate); 221 | } 222 | } 223 | DailyCallsPerRoute.push(currentObj); 224 | } 225 | RouteHistory.set(DailyCallsPerRoute); 226 | // console.log(DailyCallsPerRoute); 227 | return; 228 | }; 229 | 230 | export { 231 | avgRspTimeCvsNC, 232 | reqPerEndpointAndMethodFn, 233 | reqPerStatusAndMethodProcess, 234 | RouteHistoryProcess, 235 | routePerDay, 236 | totalsPerStatus, 237 | }; 238 | --------------------------------------------------------------------------------