├── .env.example ├── .eslintrc.cjs ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── pre-push ├── CONTRIBUTING.md ├── Dockerfile.grafana ├── README.md ├── T3_README.md ├── __tests__ └── jest │ └── routes.test.ts ├── cypress.config.ts ├── cypress ├── fixtures │ └── example.json ├── integration │ ├── components │ │ ├── GraphCard.cy.tsx │ │ ├── HamburgerMenu.cy.tsx │ │ ├── Header.cy.tsx │ │ ├── InputQuery.cy.tsx │ │ ├── LoadingBar.cy.tsx │ │ ├── Popup.cy.tsx │ │ ├── modal │ │ │ ├── DBCredentials.cy.tsx │ │ │ ├── DBModal.cy.tsx │ │ │ ├── DBSelection.cy.tsx │ │ │ ├── GrafanaCredentials.cy.tsx │ │ │ └── ModalFormInput.cy.tsx │ │ └── queryLog │ │ │ ├── QueryLog.cy.tsx │ │ │ └── QueryLogItem.cy.tsx │ ├── containers │ │ ├── DashboardContainer.cy.tsx │ │ ├── MainContainer.cy.tsx │ │ ├── QueryContainer.cy.tsx │ │ └── SideBarContainer.cy.tsx │ └── pages │ │ ├── _appMyApp.cy.tsx │ │ ├── aboutAbout.cy.tsx │ │ ├── faqFAQ.cy.tsx │ │ └── homepageHomepage.cy.tsx ├── support │ ├── commands.ts │ ├── component-index.html │ └── component.ts └── tsconfig.json ├── docker-compose.yml ├── grafana-build.sh ├── grafana.sh ├── grafana └── grafana.ini ├── jest.config.ts ├── landing ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── prettier.config.cjs ├── public │ └── favicon.ico ├── src │ ├── components │ │ ├── About.tsx │ │ ├── FAQ.tsx │ │ ├── Features.tsx │ │ ├── Footer.tsx │ │ ├── HamburgerMenu.tsx │ │ ├── Header.tsx │ │ ├── Hero.tsx │ │ └── Team.tsx │ ├── hooks │ │ └── getWindowDimensions.tsx │ └── pages │ │ └── index.tsx ├── styles │ └── landingstyles.css ├── tailwind.config.ts └── tsconfig.json ├── next.config.mjs ├── nodemon.json ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── prettier.config.cjs ├── prisma ├── schema.prisma └── seed.ts ├── public ├── assets │ ├── Demo_connectDB.gif │ ├── Demo_queryInput.gif │ ├── Zoom Background.jpg │ ├── backgroundContainer.png │ ├── github-mark-white-.png │ ├── linkedin-icon-update.png │ ├── logo-128.png │ ├── logo-32.png │ ├── logo-64.png │ ├── logo-full-background-color.png │ ├── logo-full-bg.png │ └── logo-full-no-bg.png └── favicon.ico ├── src ├── components │ ├── AuthShowcase.tsx │ ├── Button.tsx │ ├── DBConnect.tsx │ ├── GraphCard.tsx │ ├── HamburgerMenu.tsx │ ├── Header.tsx │ ├── InputQuery.tsx │ ├── LoadingBar.tsx │ ├── Popup.tsx │ ├── modal │ │ ├── DBCredentials.tsx │ │ ├── DBModal.tsx │ │ ├── DBSelection.tsx │ │ ├── GrafanaCredentials.tsx │ │ └── ModalFormInput.tsx │ └── queryLog │ │ ├── EditButton.tsx │ │ ├── QueryLog.tsx │ │ └── QueryLogItem.tsx ├── containers │ ├── DashboardContainer.tsx │ ├── MainContainer.tsx │ ├── QueryContainer.tsx │ └── SideBarContainer.tsx ├── env.mjs ├── pages │ ├── [id].tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── about.tsx │ ├── api │ │ ├── auth │ │ │ └── [...nextauth].ts │ │ ├── restricted.tsx │ │ └── trpc │ │ │ └── [trpc].ts │ ├── contact.tsx │ ├── faq.tsx │ ├── homepage.tsx │ └── index.tsx ├── server │ ├── .env.example │ ├── auth.ts │ ├── controllers │ │ ├── connectionController.ts │ │ ├── dashBoardHelper.ts │ │ ├── grafanaController.ts │ │ └── pgQueryHelper.ts │ ├── db.ts │ ├── package-lock.json │ ├── package.json │ ├── routers │ │ └── apiRouter.ts │ └── server.ts ├── styles │ └── globals.css ├── types │ └── types.ts └── utils │ └── .gitkeep ├── tailwind.config.ts ├── tsconfig.build.json └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # Since the ".env" file is gitignored, you can use the ".env.example" file to 2 | # build a new ".env" file when you clone the repo. Keep this file up-to-date 3 | # when you add new variables to `.env`. 4 | 5 | # This file will be committed to version control, so make sure not to have any 6 | # secrets in it. If you are cloning this repo, create a copy of this file named 7 | # ".env" and populate it with your secrets. 8 | 9 | ################################################## 10 | # When adding additional environment variables, # 11 | # the schema in "/src/env.mjs" # 12 | # should be updated accordingly. # 13 | ################################################## 14 | 15 | NODE_ENV="development" # development | test | production 16 | 17 | # Prisma 18 | # https://www.prisma.io/docs/reference/database-reference/connection-urls#env 19 | # Prisma 20 | # https://www.prisma.io/docs/reference/database-reference/connection-urls#env 21 | DATABASE_URL_PRISMA='' 22 | DATABASE_URL_NODE='' 23 | 24 | # Next Auth 25 | # You can generate a new secret on the command line with: 26 | # openssl rand -base64 32 27 | # https://next-auth.js.org/configuration/options#secret 28 | # Not providing any secret or NEXTAUTH_SECRET will throw an error in production. 29 | # Leaving this empty in development won't throw an error, but may result in JWT decryption errors. 30 | NEXTAUTH_SECRET="" 31 | NEXTAUTH_URL="http://localhost:3000" 32 | 33 | # Next Auth Github Provider 34 | GITHUB_ID="" 35 | GITHUB_SECRET="" -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const path = require("path"); 3 | 4 | /** @type {import("eslint").Linter.Config} */ 5 | const config = { 6 | overrides: [ 7 | { 8 | extends: [ 9 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 10 | ], 11 | files: ["*.ts", "*.tsx"], 12 | parserOptions: { 13 | project: path.join(__dirname, "tsconfig.json"), 14 | }, 15 | }, 16 | ], 17 | parser: "@typescript-eslint/parser", 18 | parserOptions: { 19 | project: path.join(__dirname, "tsconfig.json"), 20 | }, 21 | plugins: ["@typescript-eslint"], 22 | extends: ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"], 23 | rules: { 24 | "@typescript-eslint/consistent-type-imports": [ 25 | "warn", 26 | { 27 | prefer: "type-imports", 28 | fixStyle: "inline-type-imports", 29 | }, 30 | ], 31 | "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], 32 | }, 33 | }; 34 | 35 | module.exports = config; 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | */node_modules 6 | **/node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # database 14 | /prisma/db.sqlite 15 | /prisma/db.sqlite-journal 16 | 17 | # next.js 18 | /.next/ 19 | /out/ 20 | next-env.d.ts 21 | 22 | # production 23 | /build 24 | 25 | # misc 26 | .DS_Store 27 | *.pem 28 | 29 | # debug 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | .pnpm-debug.log* 34 | 35 | # local env files 36 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables 37 | .env 38 | .env*.local 39 | 40 | # vercel 41 | .vercel 42 | 43 | # typescript 44 | *.tsbuildinfo 45 | 46 | # binaries 47 | grafana-agent* 48 | *config.y*ml 49 | 50 | 51 | landingbuild/ -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | # npx --no --commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | # Exit if in production 5 | # More specifically, most Continuous Integration Servers set a CI environment variable. 6 | # The following detects if this script is running in a CI, early exit 7 | [ -n "$CI" ] && exit 0 8 | 9 | # Grab current name of branch 10 | LOCAL_BRANCH_NAME="$(git rev-parse --abbrev-ref HEAD)" 11 | 12 | # Branch name regex match 13 | VALID_BRANCH_REGEX='^((bug|docs|fix|feat|merge|test|wip)\/[a-zA-Z0-9\-]+)$' 14 | 15 | ERROR_MSG="\nERROR: There is something wrong with your branch name.\nBranch names must adhere to this contract:\n\n$VALID_BRANCH_REGEX\n\nDue to this conflict, your commit has been rejected. Please rename your branch to a valid name and try again." 16 | 17 | FIX_MSG="You can rename your current working branch with:\ngit branch --move \$OLD_NAME \$NEW_NAME\ngit push --set-upstream \$REMOTE \$NEW_NAME\ngit branch -a\n\nAnd then remove the old branch on remote:\ngit push \$REMOTE --delete \$OLD_NAME\n" 18 | 19 | # Rejects commit if current branch name does not match regex pattern 20 | echo 'Linting branch name...'; 21 | if [[ ! $LOCAL_BRANCH_NAME =~ $VALID_BRANCH_REGEX ]]; then 22 | # Print error 23 | echo "$ERROR_MSG\n\n" 24 | # Optional: Error fix suggestion 25 | echo "$FIX_MSG" 26 | # Abort commit 27 | exit 1 28 | fi 29 | echo 'OK' 30 | 31 | echo 'Formatting staged files...'; 32 | # Grab all files in staging 33 | FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') 34 | 35 | # If staging is empty, abort commit 36 | [ -z "$FILES" ] && echo 'No staged files, aborting...' && exit 0 37 | 38 | # Format all staged files with Prettier 39 | echo "$FILES" | xargs ./node_modules/.bin/prettier --ignore-unknown --write 40 | 41 | echo 'Adding formatted files back to staging...'; 42 | 43 | echo "$FILES" | xargs git add 44 | 45 | echo 'Pre-flight complete.\nCommitting...' 46 | 47 | exit 0 48 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | # Prevents force-pushing to main or dev 5 | BRANCH=$(git rev-parse --abbrev-ref HEAD) 6 | PUSH_COMMAND=$(ps -ocommand= -p $PPID) 7 | PROTECTED_BRANCHES="^(main|dev|release\/*|patch\/*)" 8 | FORCE_PUSH="force|delete|-f" 9 | if [[ "$BRANCH" =~ $PROTECTED_BRANCHES && "$PUSH_COMMAND" =~ $FORCE_PUSH ]]; then 10 | echo "WARN: Prevented force-push to protected branch \"$BRANCH\" by pre-push hook" 11 | exit 1 12 | fi 13 | 14 | # Tests must pass before pushing 15 | # npm test 16 | 17 | exit 0 18 | 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | The QueryIQ team really appreciates contributions from the community. If you're interested in contributing, please see below: 4 | 5 | ## How Can I Contribute? 6 | - If you have any suggestions to improve our project, please fork this repository, create a pull request and describe what your contribution or issue is. 7 | 8 | ## Spin up the dev server 9 | 1. Pull the repo down locally 10 | 2. Ensure `./grafana.sh` is executable (`chmod +x ./grafana.sh` if it isn't) 11 | 3. `docker-compose up & npm run serve:dev` 12 | 4. Default credentials for your dockerized grafana and postgres server: 13 | - Grafana: 14 | ``` 15 | User: admin 16 | Pass: admin 17 | Port: 3000 18 | ``` 19 | - Postgres: 20 | ``` 21 | DB Name: pgdev 22 | User: admin 23 | Pass: postgres 24 | URL: localhost:5432 25 | Server: localhost:5432 26 | ``` 27 | 28 | 29 | 30 | ## Iteration Roadmap 31 | 32 | [] Reimplement data-vis service with home-rolled solution, migrate away from Grafana. 33 | [] Add functionality to connect to more than one database at a time. 34 | [] Add functionality to connect to other types of databases such as MySQL, SQLite. 35 | [] Implement plug-and-play containerization that requires less config. 36 | [] Implement solution that automatically searches for active databases on local machine `nmap -sV localhost -p- | grep ${DB_VENDER}`, or `netstat -an | grep docker` or `ps waux | grep docker`. 37 | [] Integrate Prometheus to allow for time series databases, alerting, and more customization on query and visualization 38 | [WIP] Integrate Open AI as means of suggesting optimized queries, to replace less performant queries 39 | [x] Containerize Grafana, possibly Prometheus, with Docker to enhance overall consistency across environments and operational efficiency 40 | [] Present modal interpretations of performance metrics that succinctly describe the issues and provide actionable recommendations for resolution 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Dockerfile.grafana: -------------------------------------------------------------------------------- 1 | FROM grafana/grafana-enterprise:latest 2 | 3 | COPY ./grafana/grafana.ini /etc/grafana/grafana.ini 4 | 5 | EXPOSE 3000 6 | 7 | CMD ["grafana-server", "--config=/etc/grafana/grafana.ini", "--homepath=/usr/share/grafana", "--packaging=docker", "--pidfile=/var/run/grafana/grafana-server.pid", "cfg:default.paths.data=/var/lib/grafana", "cfg:default.paths.logs=/var/log/grafana", "cfg:default.paths.plugins=/var/lib/grafana/plugins"] 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | Logo 9 | 10 |
11 | 12 |
13 |
14 | 15 | 16 |
17 | 18 | ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) 19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 | 27 | ![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) 28 | ![Next.js](https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white) 29 | ![Express.js](https://img.shields.io/badge/express.js-%23404d59.svg?style=for-the-badge&logo=express&logoColor=%2361DAFB) 30 | ![Node.js](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) 31 | ![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white) 32 | ![Grafana](https://img.shields.io/badge/grafana-%23F46800.svg?style=for-the-badge&logo=grafana&logoColor=white) 33 | ![Jest](https://img.shields.io/badge/-jest-%23C21325?style=for-the-badge&logo=jest&logoColor=white) 34 | ![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB) 35 | ![React Query](https://img.shields.io/badge/React_Query-FF4154?style=for-the-badge&logo=React_Query&logoColor=white) 36 | ![NextAuth](https://img.shields.io/badge/NextAuth-20ACF5?style=for-the-badge) 37 | ![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white) 38 | ![MySQL](https://img.shields.io/badge/MySQL-005C84?style=for-the-badge&logo=mysql&logoColor=white) 39 | ![TailwindCSS](https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white) 40 | ![Primsa](https://img.shields.io/badge/Prisma-3982CE?style=for-the-badge&logo=Prisma&logoColor=white) 41 | ![Cypress](https://img.shields.io/badge/-cypress-%23E5E5E5?style=for-the-badge&logo=cypress&logoColor=058a5e) 42 | ![Shell](https://img.shields.io/badge/Shell_Script-121011?style=for-the-badge&logo=gnu-bash&logoColor=white) 43 | ![Prettier](https://img.shields.io/badge/prettier-1A2C34?style=for-the-badge&logo=prettier&logoColor=F7BA3E) 44 | 45 | 46 |
47 | 48 |
49 | 50 | # Table of Contents 51 | 52 | 53 |
    54 |
  1. 55 | About
  2. 56 |
  3. Key Features
  4. 57 |
  5. Installation
  6. 58 |
  7. Contributing
  8. 59 |
  9. Authors
  10. 60 |
61 | 62 | 63 | 64 |
65 | 66 | # About 67 | QueryIQ is a developer-friendly application designed to transform the process of analyzing and optimizing PostgreSQL databases. With its features, QueryIQ enables developers to gain valuable insights by creating data visualization dashboards based on database performance and query metrics. 68 | 69 | Visit our website: query-iq.com! 70 | 71 | ## Demo 72 | 73 |
74 | Connecting Query IQ to user database to receive health & performance metrics: 75 |
76 | Logo 77 |
78 |
79 | User inputting an arbitrary query to receive query execution stats: 80 |
81 | Logo 82 |
83 | 84 |
85 | 86 | # Key Features: 87 | 88 | ### ➮ PostgresQL Support 89 | 90 | Easily manage your postgresQL connection, health, and performance metrics 91 | 92 | ### ➮ Grafana Integration 93 | 94 | Query IQ simplifies managing your Grafana instance by creating data sources, customizing dashboards, and imbedding graphs within the application. Users also have the option to remove their data sources and dashboards as needed. 95 | 96 | ### ➮ Overall metrics on database health including: 97 | 98 | - Queries with the Longest Running Queries 99 | - Queries with the Highest Average Execution Time 100 | - Queries with the Highest Memory Usage 101 | - Row Counts per Table 102 | - Index Scans by Table 103 | - Total of Table Size and Index Size 104 | - Cache-hit Ratio 105 | - All databases connected to that server by size 106 | - Open Connections 107 | 108 | ### ➮ Overall metrics on multiple arbitrary query inputs including: 109 | 110 | - Query plan by aggregated with actual time, rows, and width 111 | - Sequence scan with actual time, rows, and width 112 | - Planning time 113 | - Execution time 114 | 115 | ### ➮ Secured authorization through Google Oauth with required login 116 | Users are required to login with Google Oauth for authorization prior to using the application. 117 | 118 | ### ➮ Privacy and Security 119 | 120 | Privacy and security within QueryIQ is maintained through running in individual local server along with Grafana's local instance with authorization required. QueryIQ does not store any user data, most importantly including database connection information, usernames, and passwords. Data is maintained within Grafana's local instance with authorization required and access restricted by the client as needed. 121 | 122 | # Installation 123 | 124 | ## Prerequisites 125 | 126 | ➮ Go to the following link to download the latest version (10.0.1) of Grafana: https://grafana.com/docs/grafana/latest/setup-grafana/installation/ 127 | 128 | ➮ Go to your grafana.ini configurations file and ensure you have the following configurations. Refer to the the link if you're having trouble locating the grafana.ini: https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/ 129 | 130 | allow_embedding = true 131 | auth.anonymous 132 | enabled = true 133 | org_name = <> 134 | org_role = Viewer 135 | 136 | ➮ Once you have Grafana installed, run the following command to start your Grafana local instance and ensure you've logged in successfully in the Grafana server 137 | 138 | - On OSX, `brew services restart grafana`, or by running the Grafana binary 139 | 140 | ➮ For the PostgreSQL database you connect to, ensure pg_stat_statements is enabled. Refer to the link for further details: https://virtual-dba.com/blog/postgresql-performance-enabling-pg-stat-statements/ 141 | 142 | ## Install Query IQ 143 | 144 | 1. Clone this repo 145 | 2. `cd` into project directory 146 | 3. Run `npm install` 147 | 4. Run `npm run npm:fullInstall` to install packages nested within the project 148 | 5. Run `npm run serve:dev` to spin up the Next.js and Express server layers 149 | 6. Start Grafana (please see [Prerequisites](#Prerequisites)) 150 | 151 | # How to Contribute 152 | 153 | - Please read [CONTRIBUTING.md](#) for details on how to contribute. 154 | 155 | 156 | # Authors 157 | 158 | | Developed By | Github | LinkedIn | 159 | | :-: | :-: |:-: | 160 | |Connor Dillon |[![Github](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/connoro7) |[![LinkedIn](https://img.shields.io/badge/LinkedIn-%230077B5.svg?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/connor-dillon/)| 161 | | Khaile Tran |[![Github](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/khailetran) | [![LinkedIn](https://img.shields.io/badge/LinkedIn-%230077B5.svg?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/khailetran/)| 162 | |Johanna Cameron|[![Github](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/jojecameron)|[![LinkedIn](https://img.shields.io/badge/LinkedIn-%230077B5.svg?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/johanna-cameron/) | 163 | |Dean Biscocho|[![Github](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/deanbiscocho)|[![LinkedIn](https://img.shields.io/badge/LinkedIn-%230077B5.svg?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/deanbiscocho/)| 164 | |Alan Beck |[![Github](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/KAlanBeck)| [![LinkedIn](https://img.shields.io/badge/LinkedIn-%230077B5.svg?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/k-alan-beck/) | 165 | 166 | ## Show Your Support 167 | 168 | Please ⭐️ this project if you enjoy our tool, thank you so much! 169 | -------------------------------------------------------------------------------- /T3_README.md: -------------------------------------------------------------------------------- 1 | # Create T3 App 2 | 3 | This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. 4 | 5 | ## What's next? How do I make an app with this? 6 | 7 | We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary. 8 | 9 | If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help. 10 | 11 | - [Next.js](https://nextjs.org) 12 | - [NextAuth.js](https://next-auth.js.org) 13 | - [Prisma](https://prisma.io) 14 | - [Tailwind CSS](https://tailwindcss.com) 15 | - [tRPC](https://trpc.io) 16 | 17 | ## Learn More 18 | 19 | To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: 20 | 21 | - [Documentation](https://create.t3.gg/) 22 | - [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials 23 | 24 | You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome! 25 | 26 | ## How do I deploy this? 27 | 28 | Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. 29 | -------------------------------------------------------------------------------- /__tests__/jest/routes.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import request from "supertest"; 4 | import assert from "assert"; 5 | import { exec, ChildProcess } from 'child_process'; 6 | 7 | const server = "http://localhost:3001"; 8 | let serverProcess: ChildProcess; 9 | 10 | beforeAll(async () => { 11 | console.log('Starting the server...'); 12 | const startServer = 'npm run serve:dev'; 13 | serverProcess = exec(startServer); 14 | await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for the server to start (adjust the delay as needed) 15 | }); 16 | 17 | afterAll(() => { 18 | console.log('Stopping the server...'); 19 | serverProcess.kill(); // Terminate the server process (you may need to customize this logic to gracefully stop your server) 20 | }); 21 | 22 | describe("Datasource and Dashboard creation", () => { 23 | describe("POST", () => { 24 | it("responds with 200 status and application/json content type", async () => { 25 | await request(server) 26 | .post("/api/connect") 27 | .expect("Content-Type", /application\/json/) 28 | .expect(200); 29 | }); 30 | }); 31 | }); 32 | 33 | describe("Error catch route", () => { 34 | it("should respond with a 404 error and 'page not found'", async () => { 35 | const response = await request(server).get("/wrongaddress"); 36 | expect(response.status).toBe(404); 37 | expect(response.text).toBe("Page not found"); 38 | }); 39 | }); -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | component: { 5 | devServer: { 6 | framework: "next", 7 | bundler: "webpack", 8 | }, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/integration/components/GraphCard.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import GraphCard from '../../../src/components/GraphCard' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/HamburgerMenu.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HamburgerMenu from './HamburgerMenu' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/Header.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Header from './Header' 3 | 4 | describe('
', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount(
) 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/InputQuery.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import InputQuery from './InputQuery' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/LoadingBar.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LoadingBar from './LoadingBar' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/Popup.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Popup from './Popup' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/modal/DBCredentials.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DBCredentials from '../../../../src/components/modal/DBCredentials' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/modal/DBModal.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DBModal from './DBModal' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/modal/DBSelection.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DBSelection from './DBSelection' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/modal/GrafanaCredentials.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import GrafanaCredentials from './GrafanaCredentials' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/modal/ModalFormInput.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ModalFormInput from './ModalFormInput' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/queryLog/QueryLog.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import QueryLog from '../../../../src/components/queryLog/QueryLog' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/components/queryLog/QueryLogItem.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import QueryLogItem from './QueryLogItem' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/containers/DashboardContainer.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DashboardContainer from '../../../src/containers/DashboardContainer' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/containers/MainContainer.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MainContainer from './MainContainer' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/containers/QueryContainer.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import QueryContainer from './QueryContainer' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/containers/SideBarContainer.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SideBarContainer from './SideBarContainer' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/pages/_appMyApp.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MyApp from '../../../src/pages/_app' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/pages/aboutAbout.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import About from './about' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/pages/faqFAQ.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FAQ from './faq' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/integration/pages/homepageHomepage.cy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Homepage from './homepage' 3 | 4 | describe('', () => { 5 | it('renders', () => { 6 | // see: https://on.cypress.io/mounting-react 7 | cy.mount() 8 | }) 9 | }) -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************** 3 | // This example commands.ts shows you how to 4 | // create various custom commands and overwrite 5 | // existing commands. 6 | // 7 | // For more comprehensive examples of custom 8 | // commands please read more here: 9 | // https://on.cypress.io/custom-commands 10 | // *********************************************** 11 | // 12 | // 13 | // -- This is a parent command -- 14 | // Cypress.Commands.add('login', (email, password) => { ... }) 15 | // 16 | // 17 | // -- This is a child command -- 18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 19 | // 20 | // 21 | // -- This is a dual command -- 22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 23 | // 24 | // 25 | // -- This will overwrite an existing command -- 26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 27 | // 28 | // declare global { 29 | // namespace Cypress { 30 | // interface Chainable { 31 | // login(email: string, password: string): Chainable 32 | // drag(subject: string, options?: Partial): Chainable 33 | // dismiss(subject: string, options?: Partial): Chainable 34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable 35 | // } 36 | // } 37 | // } -------------------------------------------------------------------------------- /cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 |
10 | 11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /cypress/support/component.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | import { mount } from 'cypress/react18' 23 | 24 | // Augment the Cypress namespace to include type definitions for 25 | // your custom command. 26 | // Alternatively, can be defined in cypress/support/component.d.ts 27 | // with a at the top of your spec. 28 | declare global { 29 | namespace Cypress { 30 | interface Chainable { 31 | mount: typeof mount 32 | } 33 | } 34 | } 35 | 36 | Cypress.Commands.add('mount', mount) 37 | 38 | // Example use: 39 | // cy.mount() -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "isolatedModules": false, 6 | "types": ["cypress"] 7 | }, 8 | "include": ["**/*.ts","**.spec.ts"] 9 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | grafana: 5 | image: grafana/grafana-enterprise:latest 6 | ports: 7 | - '3000:3000' 8 | db: 9 | image: postgres:latest 10 | ports: 11 | - '5432:5432' 12 | environment: 13 | POSTGRES_USER: admin 14 | POSTGRES_PASSWORD: postgres 15 | POSTGRES_DB: pgdev 16 | -------------------------------------------------------------------------------- /grafana-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Color vars 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | YELLOW='\033[0;33m' 7 | COLOR_OFF='\033[0m' 8 | 9 | # Ensure OS is macOS 10 | if [[ "$OSTYPE" != "darwin"* ]]; then 11 | echo "This script is only for macOS!"; 12 | exit 1; 13 | fi 14 | 15 | # Ensure Intel chip 16 | if [[ "$HOSTTYPE" != "x86_64" ]]; then 17 | echo "This script is only for Intel chips!"; 18 | exit 1; 19 | fi 20 | 21 | # Install Grafana Agent from binary if doesn't exist 22 | if [ ! -f ./grafana-agent-darwin-amd64 ]; then 23 | curl -O -L "https://github.com/grafana/agent/releases/latest/download/grafana-agent-darwin-amd64.zip"; 24 | unzip "grafana-agent-darwin-amd64.zip"; 25 | chmod a+x "grafana-agent-darwin-amd64"; 26 | fi 27 | 28 | # Ensure .env file exists 29 | if [ ! -f ./.env ]; then 30 | echo "Please create a .env file with the following variables: GRAFANA_CLOUD_MACOS_AMD64_BINARY_API_KEY, GRAFANA_GCLOUD_HOSTED_METRICS_URL, GRAFANA_GCLOUD_HOSTED_METRICS_ID, GRAFANA_GCLOUD_HOSTED_LOGS_URL, GRAFANA_GCLOUD_HOSTED_LOGS_ID"; 31 | exit 1; 32 | fi 33 | 34 | # Set environment variables 35 | ARCH="amd64" 36 | GCLOUD_HOSTED_METRICS_URL="$(grep GRAFANA_GCLOUD_HOSTED_METRICS_URL .env | cut -d '=' -f2-)" 37 | GCLOUD_HOSTED_METRICS_ID="$(grep GRAFANA_GCLOUD_HOSTED_METRICS_ID .env | cut -d '=' -f2-)" 38 | GCLOUD_SCRAPE_INTERVAL="60s" 39 | GCLOUD_HOSTED_LOGS_URL="$(grep GRAFANA_GCLOUD_HOSTED_LOGS_URL .env | cut -d '=' -f2-)" 40 | GCLOUD_HOSTED_LOGS_ID="$(grep GRAFANA_GCLOUD_HOSTED_LOGS_ID .env | cut -d '=' -f2-)" 41 | GCLOUD_RW_API_KEY="$(grep GRAFANA_CLOUD_MACOS_AMD64_BINARY_API_KEY .env | cut -d '=' -f2-)==" 42 | 43 | if [ -z "$GCLOUD_HOSTED_METRICS_URL" ] || [ -z "$GCLOUD_HOSTED_METRICS_ID" ] || [ -z "$GCLOUD_SCRAPE_INTERVAL" ] || [ -z "$GCLOUD_HOSTED_LOGS_URL" ] || [ -z "$GCLOUD_HOSTED_LOGS_ID" ] || [ -z "$GCLOUD_RW_API_KEY" ]; then 44 | echo "${RED}Please ensure all environment variables are set in .env file${COLOR_OFF}"; 45 | exit 1; 46 | else 47 | echo "${GREEN}Environment variables correctly set in .env file, following warning is a race condition that is actually resolved.${COLOR_OFF}"; 48 | /bin/sh -c "$(curl -fsSL https://storage.googleapis.com/cloud-onboarding/agent/scripts/grafanacloud-install-darwin.sh)" 49 | 50 | fi 51 | 52 | # Fetch config file from Grafana Cloud 53 | 54 | 55 | # Move config file to root dir 56 | # if ./grafana-config.yml does not exist, move from /usr/local/etc/grafana-agent/config.yaml 57 | if [ ! -f ./grafana-config.yml ]; then 58 | mv /usr/local/etc/grafana-agent/config.yml ./grafana-config.yml 59 | fi 60 | 61 | # Run the Agent if not currently running, otherwise kill and restart 62 | if lsof -Pi :12345 -sTCP:LISTEN -t >/dev/null ; then 63 | kill -9 $(lsof -t -i:12345) && ./grafana-agent-darwin-amd64 --config.file=./grafana-config.yml 64 | else 65 | ./grafana-agent-darwin-amd64 --config.file=./grafana-config.yml 66 | fi 67 | -------------------------------------------------------------------------------- /grafana.sh: -------------------------------------------------------------------------------- 1 | docker run -d -p 3000:3000 --name=grafana \ 2 | --volume grafana-storage:/var/lib/grafana \ 3 | -e GF_SECURITY_ADMIN_USER=admin \ 4 | -e GF_SECURITY_ADMIN_PASSWORD=password \ 5 | grafana/grafana-enterprise 6 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/__tests__/**/*.test.ts'], 5 | verbose: true, 6 | forceExit: true, 7 | clearMocks: true 8 | }; 9 | -------------------------------------------------------------------------------- /landing/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import("next").NextConfig} */ 2 | const config = { 3 | reactStrictMode: true, 4 | 5 | /** 6 | * If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config out. 7 | * 8 | * @see https://github.com/vercel/next.js/issues/41980 9 | */ 10 | i18n: { 11 | locales: ["en"], 12 | defaultLocale: "en", 13 | }, 14 | images: { 15 | remotePatterns: [ 16 | { 17 | protocol: 'https', 18 | hostname: 'github.com' 19 | }, 20 | { 21 | protocol: 'https', 22 | hostname: '**.githubusercontent.com' 23 | }, 24 | { 25 | protocol: 'https', 26 | hostname: '**.discordapp.com' 27 | }, 28 | ], 29 | }, 30 | distDir: 'landingbuild', 31 | }; 32 | export default config; -------------------------------------------------------------------------------- /landing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "landing", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "landing:build": "next build", 8 | "landing:dev": "next dev -p 8888", 9 | "landing:lint": "next lint", 10 | "landing:start": "next start -p 8888" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "flowbite": "^1.7.0", 17 | "flowbite-react": "^0.5.0", 18 | "next": "^13.4.12", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-icons": "^4.10.1" 22 | }, 23 | "devDependencies": { 24 | "postcss": "^8.4.27", 25 | "prettier": "^2.8.8", 26 | "prettier-plugin-tailwind": "^2.2.12" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /landing/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | 8 | module.exports = config; 9 | -------------------------------------------------------------------------------- /landing/prettier.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | const config = { 3 | plugins: [require.resolve("prettier-plugin-tailwindcss")], 4 | semi: true, 5 | singleQuote: true, 6 | }; 7 | 8 | module.exports = config; 9 | -------------------------------------------------------------------------------- /landing/public/favicon.ico: -------------------------------------------------------------------------------- 1 | ��4�}� -------------------------------------------------------------------------------- /landing/src/components/About.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from 'next/image'; 3 | 4 | const About: React.FC = () => { 5 | return ( 6 | <> 7 |
8 |
9 |

Query IQ is a powerful developer tool that provides holistic insights on your SQL database.

10 | {/*

*/} 11 |
12 |
13 |
14 |
15 | database health 21 |
22 |

Database Health Metrics

23 |

24 | Monitor crucial metrics for your database such as query execution time, memory usage, cache-hit ratio, and more! 25 |

26 |
27 |
28 |
29 |
30 |
31 | query log 37 |
38 |

Customizable Query Log

39 |

40 | Keep tabs on the queries you make to your database, give them custom labels, and easily compare query performance. 41 |

42 |
43 |
44 |
45 |
46 |
47 | query performance image 53 |
54 |

Query Performance

55 |

56 | Receive a granular level analysis of individual query performance by aggregating actual time, rows, and width. 57 |

58 |
59 |
60 |
61 |
62 |
63 | 64 | ); 65 | }; 66 | 67 | export default About; 68 | -------------------------------------------------------------------------------- /landing/src/components/FAQ.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const FAQ = () => { 4 | return ( 5 | <> 6 |
7 |
8 |
9 |

10 | FAQ 11 |

12 |

13 | Below you'll find the answers to some frequently asked questions 14 |

15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | Q. 23 |
24 |
25 | A. 26 |
27 |
28 |
29 |
30 | 31 | What are performance metrics for SQL database queries? 32 | 33 |
34 |
35 | 36 | Performance metrics for SQL database queries are measurements used to assess the efficiency and 37 | effectiveness of query execution. They include metrics such as query execution time, query throughput, 38 | CPU usage, memory usage, disk I/O, and more. 39 | 40 |
41 |
42 |
43 | 44 | 45 |
46 |
47 |
48 | Q. 49 |
50 |
51 | A. 52 |
53 |
54 |
55 |
56 | 57 | Why do performance metrics for database queries matter? 58 | 59 |
60 |
61 | 62 | Performance metrics for database queries matter because 63 | they provide insights into system efficiency, user 64 | experience, scalability, cost optimization, 65 | troubleshooting, optimization opportunities, SLA 66 | compliance, and enable continuous improvement. Monitoring 67 | and analyzing these metrics help maintain a 68 | well-performing database system and ensure that it meets 69 | the needs of users and applications. 70 | 71 |
72 |
73 |
74 | 75 |
76 |
77 |
78 | Q. 79 |
80 |
81 | A. 82 |
83 |
84 |
85 |
86 | 87 | What is query execution time, and how can I optimize it? 88 | 89 |
90 |
91 | 92 | Query execution time refers to the duration it takes for a 93 | query to complete. To optimize query execution time, you 94 | can consider strategies such as indexing the appropriate 95 | columns, rewriting or optimizing the query logic, 96 | denormalizing data for better performance, or tuning the 97 | database configuration. 98 | 99 |
100 |
101 |
102 |
103 | 104 |
105 | 106 |
107 |
108 |
109 | Q. 110 |
111 |
112 | A. 113 |
114 |
115 |
116 |
117 | 118 | What is query throughput, and how can I improve it? 119 | 120 |
121 |
122 | 123 | Query throughput refers to the number of queries that can 124 | be processed within a given timeframe. To improve query 125 | throughput, you can optimize the database schema, utilize 126 | caching mechanisms, distribute data across multiple 127 | servers, parallelize query execution, and implement query 128 | optimization techniques. 129 | 130 |
131 |
132 |
133 | 134 |
135 |
136 |
137 | Q. 138 |
139 |
140 | A. 141 |
142 |
143 |
144 |
145 | 146 | What is query optimization, and how can I optimize my SQL 147 | queries? 148 | 149 |
150 |
151 | 152 | Query optimization involves improving the efficiency of 153 | SQL queries by selecting optimal execution plans. You can 154 | optimize SQL queries by using appropriate indexing, 155 | rewriting complex queries, avoiding unnecessary joins or 156 | subqueries, utilizing query hints, and analyzing and 157 | tuning the database configuration. 158 | 159 |
160 |
161 |
162 | 163 |
164 |
165 |
166 | Q. 167 |
168 |
169 | A. 170 |
171 |
172 |
173 |
174 | 175 | What are some common SQL query performance issues and solutions? 176 | 177 |
178 |
179 | 180 | Common performance issues with SQL queries include slow 181 | query execution, high resource utilization, lack of 182 | indexing, inefficient join operations, and suboptimal 183 | query plans. You can address these issues by optimizing 184 | query logic, adding or modifying indexes, rewriting 185 | queries, and monitoring resource usage. 186 | 187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | 196 | ); 197 | }; 198 | 199 | export default FAQ; 200 | -------------------------------------------------------------------------------- /landing/src/components/Features.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BiLogoPostgresql } from 'react-icons/bi'; 3 | import { SiGrafana } from 'react-icons/si'; 4 | import { GiPadlock } from 'react-icons/gi'; 5 | 6 | 7 | const Features: React.FC = () => { 8 | return ( 9 | <> 10 |
11 |
12 |

Features

13 |
14 |
    15 |
  • 16 |
    17 |
    18 | 19 |
    20 |

    PostgreSQL Support

    21 |
    22 |

    Effortlessly manage your PostgreSQL connection, health, and performance metrics with Query IQ. Simplify monitoring, performance optimization, and gain valuable insights.

    23 |
  • 24 |
  • 25 |
    26 |
    27 | 28 |
    29 |

    Grafana Integration


    30 |
    31 |

    Query IQ simplifies managing your Grafana instance by creating data sources, pre-confgured dashboards, and embedded graphs within the application.

    32 |
  • 33 |
  • 34 |
    35 |
    36 | 37 |
    38 |

    Secure Authorization


    39 |
    40 |

    Seamlessly create an account using your existing GitHub credentials, ensuring a streamlined and secure registration process. 41 | Your data remains protected, and you can confidently access all the features with ease.

    42 |
  • 43 |
44 |
45 |
46 |
47 | 48 | ); 49 | } 50 | 51 | export default Features; 52 | 53 | -------------------------------------------------------------------------------- /landing/src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from 'next/image'; 3 | 4 | const Footer: React.FC = () => { 5 | return ( 6 | 70 | ); 71 | }; 72 | 73 | export default Footer; 74 | -------------------------------------------------------------------------------- /landing/src/components/HamburgerMenu.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { GiHamburgerMenu } from 'react-icons/gi'; 3 | import Link from 'next/link'; 4 | 5 | const HamburgerMenu = () => { 6 | const [isOpen, setIsOpen] = useState(false); 7 | 8 | const toggleMenu = () => { 9 | setIsOpen(!isOpen); 10 | }; 11 | 12 | return ( 13 |
14 |
15 | 16 |
17 | {isOpen && ( 18 |
19 | 42 |
43 | )} 44 |
45 | ); 46 | }; 47 | 48 | export default HamburgerMenu; 49 | -------------------------------------------------------------------------------- /landing/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import React, { useState, useRef, useEffect } from 'react'; 3 | 4 | const Header: React.FC = () => { 5 | // const [sticky, setSticky] = useState({ isSticky: false, offset: 0 }); 6 | // const headerRef = useRef(null); 7 | 8 | 9 | // // handle scroll event 10 | // const handleScroll = (elTopOffset, elHeight) => { 11 | // if (window.scrollY > (elTopOffset + elHeight)) { 12 | // setSticky({ isSticky: true, offset: elHeight }); 13 | // } else { 14 | // setSticky({ isSticky: false, offset: 0 }); 15 | // } 16 | // }; 17 | 18 | // // add/remove scroll event listener 19 | // useEffect(() => { 20 | // var header = headerRef.current.getBoundingClientRect(); 21 | // const handleScrollEvent = () => { 22 | // handleScroll(header.top, header.height) 23 | // } 24 | 25 | // window.addEventListener('scroll', handleScrollEvent); 26 | 27 | // return () => { 28 | // window.removeEventListener('scroll', handleScrollEvent); 29 | // }; 30 | // }, []); 31 | 32 | const headerStyle = { 33 | position: 'sticky', 34 | top: 0, 35 | bottom:0, 36 | zIndex: 999, // Optional: To ensure the header appears above other content 37 | background: 'linear-gradient(to bottom, #1F1F1F, transparent)', // Replace 'blue' with your desired background color 38 | padding: '8px', 39 | }; 40 | 41 | return ( 42 |
43 |
46 | 58 |
59 | 82 |
83 |
84 |
85 | 86 | ); 87 | }; 88 | 89 | export default Header; 90 | -------------------------------------------------------------------------------- /landing/src/components/Hero.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Image from 'next/image'; 3 | import useWindowDimensions from '../hooks/getWindowDimensions' 4 | 5 | const Hero = () => { 6 | 7 | // const { height, width } = useWindowDimensions(); 8 | const width = '1000' 9 | return ( 10 | <> 11 |
12 |
13 |
14 |
15 |
16 | Query IQ Logo 24 |
25 |
26 |

Query IQ

27 |
28 | 29 |
30 |
31 |

32 | Your database will thank you! 33 |

34 | Query IQ Application Screenshot 40 |
41 |
42 |
43 |
44 | 45 | ) 46 | } 47 | 48 | export default Hero 49 | -------------------------------------------------------------------------------- /landing/src/components/Team.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from 'next/image'; 3 | 4 | const Team = () => { 5 | const teamMembers = [ 6 | { 7 | image: 'https://github.com/KAlanBeck.png', 8 | name: 'Alan Beck', 9 | linkedin: 'https://www.linkedin.com/in/k-alan-beck/', 10 | github: 'https://github.com/KAlanBeck/', 11 | }, 12 | { 13 | image: 'https://github.com/connoro7.png', 14 | name: 'Connor Dillon', 15 | linkedin: 'https://www.linkedin.com/in/connor-dillon/', 16 | github: 'https://github.com/connoro7/', 17 | }, 18 | { 19 | image: 'https://github.com/deanbiscocho.png', 20 | name: 'Dean Biscocho', 21 | linkedin: 'https://www.linkedin.com/in/deanbiscocho/', 22 | github: 'https://github.com/deanbiscocho/', 23 | }, 24 | { 25 | image: 'https://github.com/jojecameron.png', 26 | name: 'Johanna Cameron', 27 | linkedin: 'https://www.linkedin.com/in/johanna-cameron/', 28 | github: 'https://github.com/jojecameron/', 29 | }, 30 | { 31 | image: 'https://github.com/khailetran.png', 32 | name: 'Khaile Tran', 33 | linkedin: 'https://www.linkedin.com/in/khailetran/', 34 | github: 'https://github.com/khailetran/', 35 | }, 36 | // Add more team members as needed 37 | ]; 38 | return ( 39 | <> 40 |
41 |
42 |

Meet Our Team

43 |
44 | {teamMembers.map((member, index) => ( 45 |
49 |
50 | src} 54 | alt="Profile" 55 | width={55} 56 | height={55} 57 | unoptimized 58 | /> 59 |
60 | 61 |

62 | {member.name} 63 |

64 |
65 |
66 | {' '} 67 | 68 | LinkedIn 75 | 81 | LinkedIn 82 | 83 |
84 |
85 | {' '} 86 | GitHub 93 | 99 | GitHub 100 | 101 |
102 |
103 |
104 | ))} 105 |
106 |
107 |
108 | 109 | ); 110 | }; 111 | 112 | export default Team; 113 | -------------------------------------------------------------------------------- /landing/src/hooks/getWindowDimensions.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { useState, useEffect } from 'react'; 3 | 4 | function getWindowDimensions() { 5 | const { innerWidth: width, innerHeight: height } = window; 6 | 7 | return { 8 | width, 9 | height 10 | }; 11 | } 12 | 13 | export default function useWindowDimensions() { 14 | const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); 15 | 16 | useEffect(() => { 17 | function handleResize() { 18 | setWindowDimensions(getWindowDimensions()); 19 | } 20 | 21 | window.addEventListener('resize', handleResize); 22 | return () => window.removeEventListener('resize', handleResize); 23 | }, []); 24 | 25 | return windowDimensions; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /landing/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from 'next'; 2 | import Image from 'next/image'; 3 | import Head from 'next/head'; 4 | import Hero from '../components/Hero'; 5 | import Header from '../components/Header'; 6 | import About from '../components/About'; 7 | import FAQ from '../components/FAQ'; 8 | import Team from '../components/Team'; 9 | import Footer from '../components/Footer'; 10 | import Features from '../components/Features' 11 | import '../../../src/styles/globals.css'; 12 | // https://cdn.discordapp.com/attachments/1115285712292565056/1126317089712517190/QuIQ_query.gif 13 | 14 | const LandingHome: NextPage = () => { 15 | 16 | 17 | return ( 18 | <> 19 |
20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 | ); 32 | }; 33 | 34 | export default LandingHome; -------------------------------------------------------------------------------- /landing/styles/landingstyles.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | box-sizing: border-box; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | body { 12 | /*background-image: linear-gradient(to bottom, #0f0f0f, #1f1f1f);*/ 13 | background: #1f1f1f; 14 | height: 100dvh; 15 | width: fit-content; 16 | 17 | } 18 | 19 | @layer utilities { 20 | .h-100dvh { 21 | height: 100dvh; 22 | } 23 | .mb-128 { 24 | margin-bottom: 36rem; 25 | } 26 | .outline-red { 27 | outline: 1px solid red; 28 | } 29 | .outline-blue { 30 | outline: 1px solid cyan; 31 | } 32 | .outline-green { 33 | outline: 1px solid chartreuse; 34 | } 35 | .outline-purple { 36 | outline: 1px solid magenta; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /landing/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from 'tailwindcss'; 2 | 3 | const tailwindConfig: Config = { 4 | content: ['./src/**/*.{js,ts,jsx,tsx}'], 5 | theme: { 6 | extend: { 7 | fontFamily: { 8 | 'reem-kufi': ['Reem Kufi', 'sans-serif'], 9 | }, 10 | visibility: ['group-hover'], 11 | }, 12 | }, 13 | variants: { 14 | extend: { 15 | visibility: ['group-hover'], 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | 21 | export default tailwindConfig; 22 | -------------------------------------------------------------------------------- /landing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "incremental": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve" 20 | }, 21 | "include": [ 22 | "next-env.d.ts", 23 | "**/*.ts", 24 | "**/*.tsx" 25 | ], 26 | "exclude": [ 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful 3 | * for Docker builds. 4 | */ 5 | import("./src/env.mjs"); 6 | 7 | /** @type {import("next").NextConfig} */ 8 | const config = { 9 | reactStrictMode: true, 10 | 11 | /** 12 | * If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config out. 13 | * 14 | * @see https://github.com/vercel/next.js/issues/41980 15 | */ 16 | i18n: { 17 | locales: ["en"], 18 | defaultLocale: "en", 19 | }, 20 | tsconfigPath: "tsconfig.json", 21 | images: { 22 | remotePatterns: [ 23 | { 24 | protocol: 'https', 25 | hostname: 'github.com', 26 | 27 | }, 28 | ], 29 | }, 30 | }; 31 | export default config; 32 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts,json", 4 | "ignore": [ 5 | "src/**/*.spec.ts", 6 | "src/**/*.test.ts", 7 | "node_modules", 8 | "dist", 9 | "build", 10 | "src/**/*.d.ts", 11 | "src/**/*.map", 12 | "src/**/*.js.map", 13 | "src/**/*.spec.js", 14 | "src/**/*.test.js" 15 | ], 16 | "exec": "ts-node --transpile-only ./src/server/server.ts" 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-t3-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "npm:fullInstall": "npm install --prefix ./src/server && npm run prisma:generate", 7 | "next:build": "next build", 8 | "next:dev": "next dev -p 3333", 9 | "next:lint": "next lint", 10 | "next:start": "next start -p 3333", 11 | "express": "ts-node --transpile-only ./src/server/server.ts", 12 | "express:watch": "nodemon", 13 | "prisma:generate": "prisma generate", 14 | "prisma:push": "npx prisma db push", 15 | "lint": "next lint", 16 | "start": "next start", 17 | "prepare": "husky install && bash -c 'chmod ug+x .husky/*'", 18 | "prisma:studio": "npx prisma studio", 19 | "serve:dev": "concurrently \"npm run next:dev\" \"npm run express:watch\"", 20 | "cypress": "cypress open" 21 | }, 22 | "dependencies": { 23 | "@next-auth/prisma-adapter": "^1.0.5", 24 | "@prisma/client": "^4.16.0", 25 | "@t3-oss/env-nextjs": "^0.3.1", 26 | "body-parser": "^1.20.2", 27 | "concurrently": "^8.2.0", 28 | "cors": "^2.8.5", 29 | "express": "^4.18.2", 30 | "helmet": "^7.0.0", 31 | "next": "^13.4.2", 32 | "next-auth": "^4.22.1", 33 | "pg": "^8.11.0", 34 | "react": "18.2.0", 35 | "react-dom": "18.2.0", 36 | "react-icons": "^4.10.1", 37 | "react-query": "^3.39.3", 38 | "uuid": "^9.0.0", 39 | "zod": "^3.21.4" 40 | }, 41 | "devDependencies": { 42 | "@testing-library/cypress": "^9.0.0", 43 | "@testing-library/jest-dom": "^5.16.5", 44 | "@testing-library/react": "^14.0.0", 45 | "@types/cors": "^2.8.13", 46 | "@types/eslint": "^8.37.0", 47 | "@types/express": "^4.17.17", 48 | "@types/jest": "^29.5.2", 49 | "@types/node": "^18.16.18", 50 | "@types/pg": "^8.10.2", 51 | "@types/prettier": "^2.7.2", 52 | "@types/react": "^18.2.6", 53 | "@types/react-dom": "^18.2.4", 54 | "@types/supertest": "^2.0.12", 55 | "@types/testing-library__react": "^10.2.0", 56 | "@types/uuid": "^9.0.2", 57 | "@typescript-eslint/eslint-plugin": "^5.59.6", 58 | "@typescript-eslint/parser": "^5.59.6", 59 | "autoprefixer": "^10.4.14", 60 | "cypress": "^12.16.0", 61 | "eslint": "^8.40.0", 62 | "eslint-config-next": "^13.4.2", 63 | "husky": "^8.0.3", 64 | "eslint-plugin-cypress": "^2.13.3", 65 | "jest": "^29.5.0", 66 | "nodemon": "^2.0.22", 67 | "postcss": "^8.4.21", 68 | "prettier": "^2.8.8", 69 | "prettier-plugin-tailwindcss": "^0.2.8", 70 | "prisma": "^4.16.0", 71 | "supertest": "^6.3.3", 72 | "tailwindcss": "^3.3.0", 73 | "ts-jest": "^29.1.1", 74 | "ts-node": "^10.9.1", 75 | "typescript": "^5.1.3" 76 | }, 77 | "ct3aMetadata": { 78 | "initVersion": "7.13.2" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | 8 | module.exports = config; 9 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | const config = { 3 | plugins: [require.resolve("prettier-plugin-tailwindcss")], 4 | semi: true, 5 | singleQuote: true, 6 | }; 7 | 8 | module.exports = config; 9 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | previewFeatures = ["jsonProtocol"] 7 | } 8 | 9 | datasource db { 10 | provider = "mysql" 11 | // Further reading: 12 | // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema 13 | // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string 14 | url = env("DATABASE_URL_PRISMA") 15 | relationMode = "prisma" 16 | } 17 | 18 | // Necessary for Next auth 19 | model Account { 20 | id String @id @default(cuid()) 21 | userId String 22 | type String 23 | provider String 24 | providerAccountId String 25 | refresh_token String? @db.Text 26 | refresh_token_expires_in Int? 27 | access_token String? @db.Text 28 | expires_at Int? 29 | token_type String? 30 | scope String? 31 | id_token String? @db.Text 32 | session_state String? 33 | user User @relation(fields: [userId], references: [id], onDelete: Cascade, map: "user_fk") 34 | PostgreSQL PostgreSQL[] 35 | MySQL MySQL[] 36 | 37 | @@unique([provider, providerAccountId]) 38 | @@index([userId]) 39 | } 40 | 41 | model Session { 42 | id String @id @default(cuid()) 43 | sessionToken String @unique 44 | userId String 45 | expires DateTime 46 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 47 | 48 | @@index([userId]) 49 | } 50 | 51 | model User { 52 | id String @id @default(cuid()) 53 | name String? 54 | email String? @unique 55 | emailVerified DateTime? 56 | image String? 57 | accounts Account[] 58 | sessions Session[] 59 | PostgreSQL PostgreSQL[] 60 | MySQL MySQL[] 61 | } 62 | 63 | model VerificationToken { 64 | identifier String 65 | token String @unique 66 | expires DateTime 67 | 68 | @@unique([identifier, token]) 69 | } 70 | 71 | model PostgreSQL { 72 | id String @id @default(cuid()) 73 | userId String 74 | user User @relation(fields: [userId], references: [id]) 75 | pg_username String // TODO: store securely 76 | pg_password String // TODO: store securely 77 | Account Account? @relation(fields: [accountId], references: [id]) 78 | accountId String? 79 | 80 | @@index([userId]) 81 | @@index([accountId]) 82 | } 83 | 84 | model MySQL { 85 | id String @id @default(cuid()) 86 | userId String 87 | user User @relation(fields: [userId], references: [id]) 88 | mysql_username String // TODO: store securely 89 | mysql_password String // TODO: store securely 90 | Account Account? @relation(fields: [accountId], references: [id]) 91 | accountId String? 92 | 93 | @@index([userId]) 94 | @@index([accountId]) 95 | } 96 | -------------------------------------------------------------------------------- /prisma/seed.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://create.t3.gg/en/usage/prisma 3 | * 4 | * */ 5 | 6 | import { prisma } from "../src/server/db"; 7 | 8 | async function main() { 9 | const id = "cl9ebqhxk00003b600tymydho"; 10 | await prisma.example.upsert({ 11 | where: { 12 | id, 13 | }, 14 | create: { 15 | id, 16 | }, 17 | update: {}, 18 | }); 19 | } 20 | 21 | main() 22 | .then(async () => { 23 | await prisma.$disconnect(); 24 | }) 25 | .catch(async (e) => { 26 | console.error(e); 27 | await prisma.$disconnect(); 28 | process.exit(1); 29 | }); 30 | -------------------------------------------------------------------------------- /public/assets/Demo_connectDB.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/Demo_connectDB.gif -------------------------------------------------------------------------------- /public/assets/Demo_queryInput.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/Demo_queryInput.gif -------------------------------------------------------------------------------- /public/assets/Zoom Background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/Zoom Background.jpg -------------------------------------------------------------------------------- /public/assets/backgroundContainer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/backgroundContainer.png -------------------------------------------------------------------------------- /public/assets/github-mark-white-.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/github-mark-white-.png -------------------------------------------------------------------------------- /public/assets/linkedin-icon-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/linkedin-icon-update.png -------------------------------------------------------------------------------- /public/assets/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/logo-128.png -------------------------------------------------------------------------------- /public/assets/logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/logo-32.png -------------------------------------------------------------------------------- /public/assets/logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/logo-64.png -------------------------------------------------------------------------------- /public/assets/logo-full-background-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/logo-full-background-color.png -------------------------------------------------------------------------------- /public/assets/logo-full-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/logo-full-bg.png -------------------------------------------------------------------------------- /public/assets/logo-full-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/assets/logo-full-no-bg.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QueryIQ/748c864eea9d810477567d13aa18309b4d95ce63/public/favicon.ico -------------------------------------------------------------------------------- /src/components/AuthShowcase.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { signIn, signOut, useSession } from 'next-auth/react'; 3 | import Button from './Button'; 4 | 5 | const AuthShowcase: React.FC = () => { 6 | const { data: sessionData } = useSession(); 7 | 8 | const profilePicStyle: React.CSSProperties = { 9 | borderRadius: '50%', 10 | }; 11 | 12 | return ( 13 |
14 |

15 | {sessionData && ( 16 |

17 | {/* eslint-disable-next-line @next/next/no-img-element */} 18 | Profile Picture 24 | {sessionData.user?.name} 25 | {/* eslint-disable-next-line @next/next/no-img-element */} 26 |
27 | )} 28 |

29 | {/* {sessionData && ( 30 |
31 |
32 | 35 |
36 |
37 | )} */} 38 | 52 |
53 | ); 54 | }; 55 | 56 | export default AuthShowcase; 57 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React, { HTMLAttributes } from 'react'; 2 | 3 | interface ButtonProps { 4 | btnStyle: (props: T) => T; 5 | } 6 | 7 | const Button: React.FC = (props: ButtonProps, children) => { 8 | const { btnStyle } = props; 9 | 10 | return ( 11 | <> 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default Button; 18 | -------------------------------------------------------------------------------- /src/components/DBConnect.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { DBConnectProps } from '~/types/types'; 3 | import { BsDatabaseAdd } from 'react-icons/bs'; 4 | 5 | const DBConnect: React.FC = ({ 6 | openModal, 7 | connection, 8 | formData, 9 | setFormData, 10 | disconnectDB, 11 | }) => { 12 | const handleConnect = () => { 13 | setFormData({ 14 | graf_name: '', 15 | graf_pass: '', 16 | graf_port: '', 17 | db_name: '', 18 | db_url: '', 19 | db_username: '', 20 | db_server: '', 21 | db_password: '', 22 | }); 23 | openModal(true); 24 | }; 25 | 26 | const handleClick = async () => { 27 | await disconnectDB(); 28 | }; 29 | 30 | return ( 31 |
32 | {!connection ? ( 33 | <> 34 | 45 | 46 | ) : ( 47 | <> 48 |
49 | 50 | Active Connection 51 | 52 |
53 | 54 | DB Name:{' '} 55 | {formData.db_name} 56 | 57 |

58 | 59 | DB Server:{' '} 60 | {formData.db_server} 61 | 62 |

63 | 69 |
70 |
71 | 72 | )} 73 |
74 | ); 75 | }; 76 | 77 | export default DBConnect; 78 | -------------------------------------------------------------------------------- /src/components/GraphCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { GraphCardProps } from '~/types/types'; 3 | 4 | const GraphCard: React.FC = ({ src, key }) => { 5 | return ( 6 |
10 | 11 |
12 | ); 13 | }; 14 | 15 | export default GraphCard; 16 | -------------------------------------------------------------------------------- /src/components/HamburgerMenu.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { GiHamburgerMenu } from 'react-icons/gi'; 3 | import { signOut, signIn, useSession } from 'next-auth/react'; 4 | import Link from 'next/link'; 5 | 6 | const HamburgerMenu = () => { 7 | const { data: sessionData } = useSession(); 8 | const [isOpen, setIsOpen] = useState(false); 9 | 10 | const toggleMenu = () => { 11 | setIsOpen(!isOpen); 12 | }; 13 | 14 | return ( 15 |
16 |
17 | 18 |
19 | {isOpen && ( 20 |
21 |
    22 |
  • 23 | Home 24 |
  • 25 |
  • 26 | About 27 |
  • 28 |
  • 29 | FAQ 30 |
  • 31 |
  • 32 | Contact 33 |
  • 34 |
  • 35 | 36 | Docs 37 | 38 |
  • 39 |
  • { 44 | void signOut({ callbackUrl: window.location.origin }); 45 | } 46 | : () => { 47 | void signIn(); 48 | } 49 | } 50 | > 51 | {sessionData ? 'Logout' : 'Sign in'} 52 |
  • 53 |
54 |
55 | )} 56 |
57 | ); 58 | }; 59 | 60 | export default HamburgerMenu; 61 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Image from 'next/image'; 3 | import HamburgerMenu from '../components/HamburgerMenu'; 4 | import Link from 'next/link'; 5 | 6 | const Header: React.FC = () => { 7 | return ( 8 |
9 |
10 |
11 | 12 | Logo 18 | 19 |
20 |
21 | 22 | Query IQ 23 | 24 |
25 |
26 |
27 | 30 |
31 |
32 | ); 33 | }; 34 | 35 | export default Header; 36 | -------------------------------------------------------------------------------- /src/components/InputQuery.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useState } from 'react'; 3 | import LoadingBar from './LoadingBar'; 4 | import Popup from './Popup'; 5 | import type { 6 | InputQueryProps, 7 | GrafanaUserObject, 8 | QueryLogItemObject, 9 | dbUid, 10 | } from '~/types/types'; 11 | import { useMutation } from 'react-query'; 12 | import { BsKeyboard, BsArrowRightCircleFill } from 'react-icons/bs'; 13 | 14 | const InputQuery: React.FC = ({ 15 | setQueryLog, 16 | setQuery, 17 | query, 18 | setActiveQuery, 19 | setDashboardState, 20 | grafanaUser, 21 | dbUid, 22 | connection, 23 | }) => { 24 | //useState for loading bar 25 | const [loadingProgress, setLoadingProgress] = useState(0); 26 | const [isLoading, setIsLoading] = useState(false); 27 | 28 | const asyncLoadingSim = (): Promise => { 29 | setIsLoading(true); 30 | setLoadingProgress(0); 31 | return new Promise((resolve) => { 32 | setTimeout(() => { 33 | setLoadingProgress(100); 34 | setTimeout(() => { 35 | setIsLoading(false); 36 | resolve(); 37 | }, 900); 38 | }, 100); 39 | }); 40 | }; 41 | 42 | //KT's code for fetching POST for the input query dashboard to Grafana 43 | //use mutation from react query to fetch a post request to send api to create dashboard for input query 44 | 45 | const mutationQuery = useMutation( 46 | async ({ 47 | query, 48 | dbUid, 49 | grafanaUser, 50 | }: { 51 | query: string; 52 | dbUid: dbUid; 53 | grafanaUser: GrafanaUserObject; 54 | }) => { 55 | const apiUrl = 'http://localhost:3001/api/query'; 56 | //deconstruct query for the request response 57 | const response = await fetch(apiUrl, { 58 | method: 'POST', 59 | headers: { 60 | 'Content-Type': 'application/json', 61 | }, 62 | body: JSON.stringify({ 63 | query: query, 64 | GrafanaCredentials: { 65 | graf_name: grafanaUser.graf_name, 66 | graf_port: grafanaUser.graf_port, 67 | graf_pass: grafanaUser.graf_pass, 68 | }, 69 | datasourceUID: dbUid.datasourceUid, 70 | }), 71 | }); 72 | // If response is less than 200 or greater than 300 73 | // Basically, if response is NOT 200-299 74 | if (response.status <= 199 && response.status >= 300) { 75 | throw new Error('Failed to connect'); // Handle error 76 | } 77 | return response.json(); 78 | } 79 | ); 80 | 81 | const handleGoClick = async ( 82 | e: React.FormEvent 83 | ): Promise => { 84 | e.preventDefault(); 85 | try { 86 | const response = (await mutationQuery.mutateAsync({ 87 | query, 88 | dbUid, 89 | grafanaUser, 90 | })) as void | { 91 | slug: string; 92 | uid: string; 93 | status: number; 94 | iFrames: string[]; 95 | }; 96 | await asyncLoadingSim(); 97 | const { iFrames, uid } = response; 98 | const newQuery: QueryLogItemObject = { 99 | query: query, 100 | data: iFrames, 101 | name: '', 102 | dashboardUID: uid, 103 | }; 104 | setQueryLog((prevQueryLog) => [...prevQueryLog, newQuery]); 105 | setQuery(''); 106 | setActiveQuery(newQuery); 107 | setDashboardState('query'); 108 | } catch (error) { 109 | console.error(error); 110 | } 111 | }; 112 | // TO DO: want to move this conditional to the return statement and plug in our loading bar component 113 | //if post request is still loading 114 | if (mutationQuery.isLoading) { 115 | return ; 116 | } 117 | 118 | // //if post request fails to fetch 119 | if (mutationQuery.error) { 120 | return ; 121 | } 122 | 123 | return ( 124 | <> 125 |
126 |
130 |
131 |