├── .gitignore ├── Aionic-dev.yml ├── Aionic-prod.yml ├── IMG ├── demo1.gif ├── demo2.gif ├── git_repo.png ├── github_Oauth_app.png ├── img3.png └── img4.png ├── LICENSE ├── README.md ├── client ├── Dockerfile ├── Dockerfile.dev ├── index.html ├── package.json ├── postcss.config.cjs ├── src │ ├── App.tsx │ ├── assets │ │ ├── argo-icon-black.png │ │ ├── github-mark.png │ │ ├── logo-sm.png │ │ ├── logo-white.png │ │ └── logo.jpeg │ ├── components │ │ ├── AppsHub.tsx │ │ ├── AppsList.tsx │ │ ├── Login.tsx │ │ ├── ManifestDetails.tsx │ │ ├── ManifestHub.tsx │ │ ├── ManifestList.tsx │ │ ├── Navbar.tsx │ │ ├── Protected.tsx │ │ └── TokenInput.tsx │ ├── main.tsx │ ├── styles.css │ └── vite-env.d.ts ├── tailwind.config.cjs ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── nginx ├── Dockerfile.dev └── default.conf ├── package.json └── server ├── Dockerfile ├── Dockerfile.dev ├── package.json ├── src ├── config │ └── MongoDb.ts ├── controllers │ ├── argoController.ts │ ├── authController.ts │ ├── autoUpdate.ts │ ├── dbController.ts │ └── userController.ts ├── keys.ts ├── passport │ └── passport.ts ├── routes │ └── apiRouter.ts ├── server.ts └── types.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | /client/node_modules 4 | /client/package-lock.json 5 | /client/.DS_Store 6 | /server/.DS_Store 7 | /server/node_modules 8 | /server/package-lock.json 9 | /server/AppConfig.json 10 | /server/dist 11 | /serverTS -------------------------------------------------------------------------------- /Aionic-dev.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | web: 4 | build: 5 | context: ./client 6 | dockerfile: Dockerfile.dev 7 | volumes: 8 | - /app/node_modules 9 | - ./client:/app 10 | command: npm run dev 11 | api: 12 | build: 13 | context: ./server 14 | dockerfile: Dockerfile.dev 15 | environment: 16 | - GITHUB_ID=[GITHUB CLIENT ID Goes here] 17 | - GITHUB_SECRET=[GITHUB CLIENT Secret Goes here] 18 | - GITHUB_CALLBACK_URL=[GITHUB CALLBACK URL]/server/auth/github/callback 19 | - url=[ARGO CD URL] 20 | - api_key=[ARGOCD APIKEY] 21 | - mongodb_uri=[MONGODB URI] 22 | nginx: 23 | depends_on: 24 | - web 25 | - api 26 | build: 27 | context: ./nginx 28 | dockerfile: Dockerfile.dev 29 | ports: 30 | - "3000:80" 31 | -------------------------------------------------------------------------------- /Aionic-prod.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web: 4 | image: aionicos/client-dev 5 | volumes: 6 | - /app/node_modules 7 | - ./client:/app 8 | command: npm run dev 9 | api: 10 | image: aionicos/api-dev 11 | environment: 12 | - GITHUB_ID= 13 | - GITHUB_SECRET= 14 | - GITHUB_CALLBACK_URL= 15 | - url=h 16 | - api_key= 17 | - mongodb_uri= 18 | 19 | command: npm run dev 20 | volumes: 21 | - /app/node_modules 22 | - ./server/dist:/app/dist 23 | - ./server/AppConfig.json:/app/App.Config.json 24 | nginx: 25 | depends_on: 26 | - web 27 | - api 28 | image: aionicos/nginx 29 | ports: 30 | - "2500:80" 31 | 32 | -------------------------------------------------------------------------------- /IMG/demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/IMG/demo1.gif -------------------------------------------------------------------------------- /IMG/demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/IMG/demo2.gif -------------------------------------------------------------------------------- /IMG/git_repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/IMG/git_repo.png -------------------------------------------------------------------------------- /IMG/github_Oauth_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/IMG/github_Oauth_app.png -------------------------------------------------------------------------------- /IMG/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/IMG/img3.png -------------------------------------------------------------------------------- /IMG/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/IMG/img4.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 geodudeorg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # [:rocket: Aionic :rocket:](https://www.aionic.app/) · ![Github](https://img.shields.io/github/repo-size/oslabs-beta/Aionic) ![GitHub](https://img.shields.io/github/license/oslabs-beta/Aionic) ![GitHub](https://img.shields.io/badge/PRs-welcome-orange) 3 | ![Aionic Logo](https://www.aionic.app/_next/image?url=%2FAionic-Logo.png&w=1920&q=75) 4 | ## Aionic is a historical registry and rollback tool for your Argo CD managed apps 5 |
6 | 7 | 8 | ## Before setup 9 | **NOTE:** Aionic requires you to setup your own MongoDB database and OAuth through your Github account, but Aionic ***WILL*** manage your database and OAuth user Github authenication. 10 | 11 | These are what you'll need before we begin: 12 | * MongoDB URI 13 | * ArgoCD Token 14 | * An admin ArgoCD token that has access to all apps, so Aionic can update and store all applications and kubernetes manifests in real time 15 | * A personal ArgoCD token for each user to gain access to specific apps based on their privileges. 16 | * ArgoCD URL 17 | * For example: **https://example.com** 18 | * Github Client ID and Secret 19 | 20 | *For more information on how to setup up MongoDB, click [here](https://www.mongodb.com/docs/manual/tutorial/getting-started/)* 21 | 22 | *For more information on how to setup up an OAuth app on your account, click [here](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)* or follow the steps below. 23 | 24 | ## How to setup 25 | 26 | ### Pre-Requisite 27 |
28 | Clone the repo to your local machine 29 |
30 | 31 |
32 | 33 | ### SET UP YOUR ENVIRONMENT VARIABLES IN Aionic-dev.yml 34 | 35 | >> The Aionic-dev.yml can be found in root directory of repo 36 | - Get Client_ID/Secret and calback url from Github 37 | - Go to github setting 38 | - Click on github Developer setting on the bottom of the list. 39 | - create new OAuth App 40 | 41 |
42 | - callback URL must be forwarded to [http://localhost:3000]/server/auth/callback 43 |
44 | 45 |
46 | 47 | - copy Client_ID and paste it onto Aionic-dev.yml as GITHUB_ID 48 | - copy newly generated client secret paste it onto GITHUB_SECRET 49 | - copy callback url from previous step paste it onto GITHUB_CALLBACK_URL 50 | - post your ArgoCD url and api_key to the enviroment variable "url" and api_key under api image in Aionic-dev.yml 51 | ***to access of localhost of host make sure to use host.docker.interal instead of localhost*** 52 | - get your mongoDB uri and paste it to the enviroment variable "mongodb_uri" under api image in Aionic-dev.yml 53 | 54 | 55 | Make sure that nginx url is same as github callback url. 56 | Since the nginx is only point of entry 57 | ### After you are done with steps above 58 | run these lines of command to build and start your docker-container 59 | 60 | npm run Aionic-build-dev 61 | this command will build Aionic container images 62 | 63 | npm run Aionic-up-dev 64 | this will start Aionic container 65 | 66 | To Stop the container ctrl + C 67 | 68 | npm run Aionic-down-dev 69 | 70 | this will completely stop the container 71 | 72 |
73 | 74 | ## DEMO 75 | 76 | ### Aionic Authentication 77 | 78 |
79 | 80 | ### Aionic Work flow 81 | 82 |
83 | 84 | ## Authors 85 | 86 | | **Nathan Lui** | **Jian Cheng Lu** | **Timothy Kwon** | **Ari Bengiyat** | 87 | | :---: | :---: | :---: | :---: | 88 | | [](https://www.linkedin.com/in/nmlui/) [](https://github.com/nathanmlui) | [](https://www.linkedin.com/in/jlu1932/) [](https://github.com/jiannluu) | [](https://www.linkedin.com/in/timothy-m-kwon/) [](https://github.com/tk-0311) | [](https://www.linkedin.com/in/ari-bengiyat-4b68821a9/) [](https://github.com/aribengiyat/)| 89 | 90 | ## How to contribute 91 | Encountering a bug or wanting features that are missing with our app? Please let us know by opening an issue in our Github repository on how we can improve! 92 | 93 | If you want to contribute directly, please submit a pull request following the guidelines listed [here](https://docs.github.com/en/get-started/quickstart/contributing-to-projects?tool=webui). 94 | 95 | ## Show us your support! 96 | If you liked our app, please show us your support by giving this repo a big :star: 97 | 98 | ## License 99 | Aionic is released under the [MIT License](https://github.com/oslabs-beta/Aionic/blob/dev/LICENSE) 100 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine as builder 2 | WORKDIR '/app' 3 | COPY ./package.json ./ 4 | RUN npm install 5 | RUN npm run build 6 | COPY . . 7 | 8 | 9 | FROM nginx 10 | COPY --from=builder /app/dist /usr/nginx/html 11 | 12 | CMD ["npm", "run", "dev"] 13 | # this production mode does not work as intended major code refactoring is required to properly function -------------------------------------------------------------------------------- /client/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | WORKDIR '/app' 4 | 5 | COPY ./package.json ./ 6 | RUN npm install 7 | 8 | COPY . . 9 | 10 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Aionic 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aionic", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "highlight.js": "^11.7.0", 13 | "js-yaml": "^4.1.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-router": "^6.8.1", 17 | "react-router-dom": "^6.8.1" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^18.0.27", 21 | "@types/react-dom": "^18.0.10", 22 | "@vitejs/plugin-react": "^3.1.0", 23 | "autoprefixer": "^10.4.13", 24 | "postcss": "^8.4.21", 25 | "tailwindcss": "^3.2.7", 26 | "typescript": "^4.9.3", 27 | "vite": "^4.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router'; 2 | import Login from './components/Login'; 3 | import logo from './assets/logo-sm.png' 4 | 5 | //successful auth will return an object with this property 6 | interface User { 7 | user: string; 8 | } 9 | 10 | function App() { 11 | //isLoggedIn changes after successful GitHub auth 12 | 13 | const navigate = useNavigate(); 14 | 15 | return ( 16 |
17 | 21 | Login Using GitHub 22 | 23 |
24 | ); 25 | } 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /client/src/assets/argo-icon-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/client/src/assets/argo-icon-black.png -------------------------------------------------------------------------------- /client/src/assets/github-mark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/client/src/assets/github-mark.png -------------------------------------------------------------------------------- /client/src/assets/logo-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/client/src/assets/logo-sm.png -------------------------------------------------------------------------------- /client/src/assets/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/client/src/assets/logo-white.png -------------------------------------------------------------------------------- /client/src/assets/logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Aionic/428d4f9e2ec469d0054bc08edcfcc539141cb093/client/src/assets/logo.jpeg -------------------------------------------------------------------------------- /client/src/components/AppsHub.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from 'react'; 2 | import AppsList from './AppsList'; 3 | import TokenInput from './TokenInput'; 4 | import { GitUserContext } from './Protected'; 5 | 6 | function AppsHub() { 7 | const [git, setGit] = useState(false); 8 | const [argo, setArgo] = useState(false); 9 | const [url, setUrl] = useState(false); 10 | const gitUser = useContext(GitUserContext); 11 | 12 | //check if token and git auth is on serverside 13 | useEffect(() => { 14 | fetch( 15 | '/server/api/userApiKey?' + 16 | new URLSearchParams({ 17 | user: gitUser, 18 | }) 19 | ) 20 | .then((data: Response) => data.json()) 21 | .then((data: []) => { 22 | console.log('got api key,', data); 23 | if (Array.isArray(data.argoTokens)) { 24 | setArgo(true); 25 | } else return; 26 | }) 27 | .catch((err) => console.log(err)); 28 | 29 | fetch( 30 | '/server/api/gitToken?' + 31 | new URLSearchParams({ 32 | user: gitUser, 33 | }) 34 | ) 35 | .then((data: Response) => data.json()) 36 | .then((data: boolean) => { 37 | console.log('git data is: ', data); 38 | if (data.githubToken !== 'no token') setGit(true); 39 | }) 40 | .catch((err) => console.log(err)); 41 | }, []); 42 | 43 | if (argo && git) { 44 | return ( 45 |
46 | 47 |
48 | ); 49 | } else { 50 | return ( 51 |
52 | 58 |
59 | ); 60 | } 61 | } 62 | 63 | export default AppsHub; 64 | -------------------------------------------------------------------------------- /client/src/components/AppsList.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router'; 2 | import { MouseEvent, useContext, useEffect, useRef, useState } from 'react'; 3 | import { GitUserContext } from './Protected'; 4 | 5 | function AppsList() { 6 | interface dbObj { 7 | name: string; 8 | uid: string; 9 | } 10 | 11 | interface Data { 12 | [key: string]: dbObj; 13 | } 14 | 15 | const [appList, setAppList] = useState({}); 16 | const [apps, setApps] = useState([]); 17 | const appListRef = useRef({}); 18 | let navigate = useNavigate(); 19 | const gitUser = useContext(GitUserContext); 20 | 21 | const handleClick = ( 22 | e: MouseEvent 23 | ) => { 24 | e.preventDefault(); 25 | let appName = e.target.parentNode.childNodes[0].innerText; 26 | navigate('/home/manifests', { state: { query: appListRef.curr[appName] } }); 27 | }; 28 | 29 | //grab the app lists and display 30 | useEffect((): void => { 31 | const appsArr: any = []; 32 | 33 | //add username here not in parent 34 | fetch( 35 | '/server/api/apps?' + 36 | new URLSearchParams({ 37 | user: gitUser, 38 | }) 39 | ) 40 | .then((data: Response) => data.json()) 41 | .then((data) => { 42 | //they are objects with two elements, name and uid 43 | const stateObj: Data = {}; 44 | for (const app of data) { 45 | stateObj[app.name] = app; 46 | stateObj[app.name].repo = app.source.repoURL; 47 | appsArr.push( 48 |
49 |

{`${app.name}`}

50 | 56 |
57 | ); 58 | } 59 | setAppList(stateObj); 60 | setApps(appsArr); 61 | }) 62 | .catch((err) => console.log('error occured fetching apps: ')); 63 | }, []); 64 | 65 | useEffect(() => { 66 | appListRef.curr = appList; 67 | }, [appList]); 68 | 69 | return ( 70 |
71 |

Select an app to view

72 |
73 | {apps} 74 |
75 |
76 | ); 77 | } 78 | 79 | export default AppsList; 80 | -------------------------------------------------------------------------------- /client/src/components/Login.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router"; 2 | 3 | function Login(props) { 4 | let navigate = useNavigate(); 5 | const handleClick = (e) => { 6 | e.preventDefault(); 7 | return navigate('/home') 8 | } 9 | return ( 10 |
11 |

You need to login!

12 | 13 |
14 | ) 15 | } 16 | 17 | export default Login; -------------------------------------------------------------------------------- /client/src/components/ManifestDetails.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from 'react'; 2 | import { useLocation } from 'react-router'; 3 | import { Octokit } from 'https://cdn.skypack.dev/@octokit/core'; 4 | import { GitUserContext } from './Protected'; 5 | import hljs from 'highlight.js/lib/core'; 6 | import 'highlight.js/styles/default.css'; 7 | import yaml from 'highlight.js/lib/languages/json'; 8 | hljs.registerLanguage('yaml', yaml); 9 | import yaml2 from 'js-yaml'; 10 | function ManifestDetails(props) { 11 | const [mani, setMani] = useState([]); 12 | const [branch, setBranch] = useState(''); 13 | const [token, setToken] = useState(''); 14 | const gitUser = useContext(GitUserContext); 15 | const { state } = useLocation(); 16 | //load each manifest for display 17 | useEffect(() => { 18 | let counter = 0; 19 | getToken(); 20 | const stateArr = []; 21 | // loop over passed in array to find correct manifests 22 | for (const obj of props.details) { 23 | console.log('props.details is: ', props.details); 24 | if (obj.revision === props.sha) { 25 | console.log('obj is: ', obj); 26 | const manifests = JSON.parse(obj.manifest); 27 | for (const manifest of manifests) { 28 | if (counter >= manifests.length) { 29 | setMani(stateArr); 30 | return; 31 | } 32 | const high = hljs.highlight(yaml2.dump(JSON.parse(manifest)), { 33 | language: 'yaml', 34 | }).value; 35 | console.log( 36 | 'typeof manifest is: ', 37 | typeof manifest, 38 | JSON.parse(manifest), 39 | 'of manifests is: ', 40 | typeof manifests, 41 | high 42 | ); 43 | stateArr.push( 44 |
45 |
 46 |                 
 47 |               
48 |
49 | ); 50 | console.log('statearr is: ', stateArr); 51 | setMani(stateArr); 52 | } 53 | } 54 | } 55 | }, []); 56 | //get github token from db 57 | const getToken = () => { 58 | console.log('getting token, gitUser is: ', gitUser); 59 | fetch( 60 | '/server/api/gitToken?' + 61 | new URLSearchParams({ 62 | user: gitUser, 63 | }) 64 | ) 65 | .then((data) => data.json()) 66 | .then((data) => { 67 | console.log('Data recieved from db for token, ', data); 68 | if (data.githubToken) { 69 | setToken(data.githubToken); 70 | } else console.log('token does not exist in DB!'); 71 | }); 72 | }; 73 | //back button 74 | const handleClick = (e) => { 75 | e.preventDefault(); 76 | props.setDetail(false); 77 | }; 78 | //helper function to get repo owner and name 79 | const parseGitLink = (str: String): String[] => { 80 | let temp = str.slice(19).split('/'); 81 | temp[1] = temp[1].slice(0, temp[1].length - 4); 82 | return temp; 83 | }; 84 | //push to git 85 | const handleGit = (e) => { 86 | e.preventDefault(); 87 | const [owner, repo] = parseGitLink(state.query.repo); 88 | console.log(token); 89 | const octokit = new Octokit({ 90 | auth: token, 91 | }); 92 | octokit 93 | .request('PATCH /repos/{owner}/{repo}/git/refs/heads/{ref}', { 94 | owner: owner, 95 | repo: repo, 96 | ref: branch, 97 | sha: props.sha, 98 | force: true, 99 | }) 100 | .then((data) => { 101 | console.log('response from GitHub is: ', data); 102 | }) 103 | .catch((err) => { 104 | console.log('error occured: ', err); 105 | }); 106 | }; 107 | //get more manifests 108 | const moreManifests = () => { 109 | fetch('http://'); 110 | }; 111 | return ( 112 |
113 | 119 |
120 | {/* container for input and git sha */} 121 |
122 |
123 |

Manifests for git sha:

124 | 125 | {props.sha} 126 | 127 |
128 | 129 |
130 | setBranch(e.target.value)} 135 | /> 136 | 142 |
143 |
144 | 145 |
146 |
{mani}
147 |
148 |
149 |
150 | ); 151 | } 152 | export default ManifestDetails; 153 | -------------------------------------------------------------------------------- /client/src/components/ManifestHub.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet, useLocation } from 'react-router'; 2 | import ManifestList from './ManifestList'; 3 | 4 | function ManifestHub() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default ManifestHub; 13 | -------------------------------------------------------------------------------- /client/src/components/ManifestList.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | import { useLocation, useNavigate } from 'react-router'; 3 | import ManifestDetails from './ManifestDetails'; 4 | 5 | function ManifestList() { 6 | const navigate = useNavigate(); 7 | 8 | const handleBack = (e) => { 9 | e.preventDefault(); 10 | navigate('/home'); 11 | }; 12 | 13 | //grab all the data from backend 14 | const [mlList, setMlList] = useState([]); 15 | const [detail, setDetail] = useState(false); 16 | const [manifests, setManifests] = useState([]); 17 | const [sha, setSha] = useState(''); 18 | const { state } = useLocation(); 19 | 20 | useEffect(() => { 21 | const stateArr: any = []; 22 | console.log('uid is: ', state.query.uid); 23 | fetch( 24 | '/server/api/manifests?' + 25 | new URLSearchParams({ 26 | uid: state.query.uid, 27 | }) 28 | ) 29 | .then((data: Response) => data.json()) 30 | .then((data: any) => { 31 | console.log('data for this app is: ', data); 32 | for (const el of data) { 33 | stateArr.push( 34 |
35 |
36 |

Git sha:

37 | 38 | {el.revision} 39 | 40 |
41 | 47 |
48 | ); 49 | } 50 | setMlList(stateArr); 51 | setManifests(data); 52 | }); 53 | }, []); 54 | 55 | //handle the click for the see more 56 | 57 | const handleClick = (e) => { 58 | e.preventDefault(); 59 | const gitSha = e.target.parentNode.childNodes[0].childNodes[1].innerText; 60 | setDetail(true); 61 | setSha(gitSha); 62 | }; 63 | 64 | //conditional return for displaying the details 65 | 66 | if (!detail) { 67 | return ( 68 |
69 | 75 | {mlList} 76 |
77 | ); 78 | } else { 79 | return ( 80 | 81 | ); 82 | } 83 | } 84 | 85 | export default ManifestList; 86 | -------------------------------------------------------------------------------- /client/src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router'; 2 | import logoWhite from '../assets/logo-white.png'; 3 | 4 | function Navbar({ setIsLoggedIn, setUsername }) { 5 | const navigate = useNavigate() 6 | const handleClick = (e) => { 7 | e.preventDefault(); 8 | fetch('/server/logout', { 9 | credentials: 'include', 10 | headers: { 11 | 'Content-Type': 'application/json', 12 | }, 13 | }) 14 | .then((data) => data.json()) 15 | .then((data) => { 16 | setIsLoggedIn(false); 17 | setUsername(''); 18 | }); 19 | navigate('/'); 20 | }; 21 | 22 | return ( 23 | 29 | ); 30 | } 31 | 32 | export default Navbar; 33 | -------------------------------------------------------------------------------- /client/src/components/Protected.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router'; 2 | import { createContext, useEffect, useState } from 'react'; 3 | import App from '../App'; 4 | import Navbar from './Navbar'; 5 | import logoWhite from '../assets/logo-white.png'; 6 | 7 | export const GitUserContext = createContext('noUser'); 8 | 9 | function Protected() { 10 | const [isLoggedIn, setIsLoggedIn] = useState(); 11 | const [username, setUsername] = useState(''); 12 | 13 | useEffect(() => { 14 | //fetch the api 15 | fetch('/server/api/checkUser', { 16 | credentials: 'include', 17 | headers: { 18 | 'Content-Type': 'application/json', 19 | }, 20 | }) 21 | .then((data: Response) => data.json()) 22 | .then((data) => { 23 | //if auth succeeds, we get the username back. need to make sure we get false if it fails 24 | if (data != 'failed') { 25 | setIsLoggedIn(true); 26 | setUsername(data.githubId); 27 | } 28 | }); 29 | }); 30 | if (isLoggedIn) { 31 | return ( 32 |
33 | 34 | 35 | 36 | 37 |
38 | ); 39 | } else { 40 | return ( 41 | 42 | 43 | 44 | ); 45 | } 46 | } 47 | 48 | export default Protected; 49 | -------------------------------------------------------------------------------- /client/src/components/TokenInput.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from 'react'; 2 | import { GitUserContext } from './Protected'; 3 | 4 | import gitLogo from '../assets/github-mark.png'; 5 | import argoLogo from '../assets/argo-icon-black.png'; 6 | 7 | interface tokens { 8 | argo: boolean; 9 | git: boolean; 10 | url: boolean; 11 | } 12 | 13 | interface props { 14 | tokens: tokens; 15 | setGit(arg0: boolean): void; 16 | setArgo(arg0: boolean): void; 17 | setUrl(arg0: boolean): void; 18 | } 19 | 20 | function TokenInput(props: props) { 21 | const [argoTokenValue, setArgoTokenValue] = useState(''); 22 | const [argoUrlValue, setArgoUrlValue] = useState(''); 23 | const [gitTokenValue, setGitTokenValue] = useState(''); 24 | const gitUser = useContext(GitUserContext); 25 | 26 | const handleArgoSubmit = (e: React.FormEvent) => { 27 | e.preventDefault(); 28 | 29 | const req = { 30 | api_key: argoTokenValue, 31 | url: argoUrlValue, 32 | githubId: gitUser, 33 | }; 34 | 35 | fetch('server/api/userApiKey', { 36 | method: 'POST', 37 | headers: { 38 | 'Content-Type': 'application/json', 39 | }, 40 | body: JSON.stringify(req), 41 | }) 42 | .then((data: Response) => data.json()) 43 | .then((data) => { 44 | if (data == 'success') { 45 | props.setArgo(true); //need to check what data we will get back 46 | props.setUrl(true); 47 | } 48 | // window.location.reload(); 49 | }); 50 | }; 51 | 52 | //waiting for BE to finish gittoken logic 53 | const handleGitSubmit = (e: React.FormEvent) => { 54 | e.preventDefault(); 55 | 56 | const req = { gitToken: gitTokenValue, githubId: gitUser }; 57 | 58 | fetch('server/api/gitToken', { 59 | method: 'POST', 60 | headers: { 61 | 'Content-Type': 'application/json', 62 | }, 63 | body: JSON.stringify(req), 64 | }) 65 | .then((data: Response) => data.json()) 66 | .then((data) => { 67 | console.log('set git data: ', data); 68 | if (data) { 69 | props.setGit(true); 70 | } 71 | // window.location.reload(); 72 | }); 73 | }; 74 | 75 | return ( 76 |
77 |
78 |
79 | 80 |

Argo

81 |
82 | 83 |
{ 86 | handleArgoSubmit(e); 87 | handleGitSubmit(e); 88 | window.location.reload(); 89 | }} 90 | > 91 | setArgoTokenValue(e.target.value)} 95 | placeholder='Insert Argo Token Here' 96 | > 97 | 98 | setArgoUrlValue(e.target.value)} 102 | placeholder='Insert Argo URL Here' 103 | > 104 | 105 |
106 | 107 |

Github

108 |
109 | setGitTokenValue(e.target.value)} 113 | placeholder='Insert Github Token Here' 114 | > 115 | 118 |
119 |
120 | 121 | {/*
122 |
{}}> 123 | 126 |
127 |
*/} 128 |
129 | ); 130 | } 131 | 132 | export default TokenInput; 133 | -------------------------------------------------------------------------------- /client/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React, { Children } from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import Protected from './components/Protected' 5 | import { 6 | createBrowserRouter, 7 | RouterProvider, 8 | } from "react-router-dom"; 9 | import ManifestHub from './components/ManifestHub'; 10 | import AppsHub from './components/AppsHub' 11 | 12 | const router = createBrowserRouter([ 13 | { 14 | path: '/', 15 | element: 16 | }, 17 | { 18 | path: '/home', 19 | element: , 20 | children: [ 21 | { 22 | path: '/home/', 23 | element: , 24 | children: [ 25 | ] 26 | }, 27 | { 28 | path: '/home/manifests', 29 | element: 30 | } 31 | ] 32 | }, 33 | 34 | ]) 35 | 36 | 37 | 38 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 39 | 40 | ) 41 | -------------------------------------------------------------------------------- /client/src/styles.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /client/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /client/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './index.html', 5 | './src/**/*.{js,ts,jsx,tsx}' 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /client/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /client/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /nginx/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | COPY ./default.conf /etc/nginx/conf.d/default.conf -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | upstream web { 2 | server web:5173; 3 | } 4 | 5 | upstream api { 6 | server api:3000; 7 | } 8 | 9 | 10 | server { 11 | listen 80; 12 | location / { 13 | proxy_set_header X-Real-IP $remote_addr; 14 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 15 | proxy_set_header Host $http_host; 16 | proxy_set_header X-NginX-Proxy true; 17 | 18 | proxy_pass http://web; 19 | proxy_redirect off; 20 | 21 | proxy_http_version 1.1; 22 | proxy_set_header Upgrade $http_upgrade; 23 | proxy_set_header Connection "upgrade"; 24 | } 25 | location /server { 26 | rewrite /server/(.*) /$1 break; 27 | proxy_pass http://api; 28 | } 29 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "Aionic-build-dev": "docker-compose -f Aionic-dev.yml build", 4 | "Aionic-up-dev": "docker-compose -f Aionic-dev.yml up", 5 | "Aionic-down-dev": "docker-compose -f Aionic-dev.yml down" 6 | } 7 | } -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | WORKDIR "/app" 3 | 4 | COPY ./package.json . 5 | 6 | RUN npm install 7 | RUN npm build 8 | 9 | 10 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /server/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | WORKDIR "/app" 4 | 5 | COPY ./package.json . 6 | RUN npm install 7 | 8 | COPY . . 9 | 10 | CMD ["npm", "run", "dev"] -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/server.ts", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "tsc", 9 | "dev": "NODE_TLS_REJECT_UNAUTHORIZED='0' nodemon src/server", 10 | "start": "NODE_TLS_REJECT_UNAUTHORIZED='0' node dist/server", 11 | "docker-build-dev": "docker-compose -f ../Aionic-dev.yml build", 12 | "docker-start": "docker-compose -f ../Aionic-dev.yml up" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "cookie-parser": "^1.4.6", 19 | "cookie-session": "^2.0.0", 20 | "cors": "^2.8.5", 21 | "express": "^4.18.2", 22 | "mongoose": "^6.9.1", 23 | "passport": "^0.5.3", 24 | "passport-github2": "^0.1.12" 25 | }, 26 | "devDependencies": { 27 | "@types/cookie-parser": "^1.4.3", 28 | "@types/cookie-session": "^2.0.44", 29 | "@types/cors": "^2.8.13", 30 | "@types/express": "^4.17.17", 31 | "@types/passport": "^1.0.12", 32 | "@types/passport-github2": "^1.2.5", 33 | "dotenv": "^16.0.3", 34 | "nodemon": "^2.0.20", 35 | "ts-node": "^10.9.1", 36 | "typescript": "^4.9.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/src/config/MongoDb.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from 'mongoose' 2 | import * as T from '../types' 3 | import { config } from '../keys' 4 | 5 | const uri = config.mongodb_uri; 6 | 7 | 8 | mongoose.connect(uri, { 9 | dbName: 'Aionic' 10 | }) 11 | .then(() => console.log('Connected to Mongo DB.')) 12 | .catch(err => console.log(err)); 13 | 14 | // do we need to save repo url to database???? 15 | const AppSchema = new Schema({ 16 | uid: {type: String, required: true, unique: true}, 17 | name: {type: String, required: true}, 18 | source: {type: { 19 | repoURL: String, 20 | path:String, 21 | targetRevision: String 22 | }, required: true}, 23 | date: {type: Date, default: Date.now()}, 24 | head: {type: String, default: null}, 25 | tail: {type: String, default: null} 26 | }) 27 | const UserApiKeys = new Schema({api_key: {type: String, unique: true}, url: {type:String, default: null}}) 28 | 29 | const UserSchema = new Schema({ 30 | githubId: {type: String, required: true, unique: true}, 31 | githubToken: {type: String, default: '', unique:true}, 32 | argo_tokens: {type: [UserApiKeys], default: [], unique:true} 33 | }) 34 | 35 | const NodeSchema = new Schema({ 36 | manifest: {type: String, required: true}, //stringify 37 | revision: {type: String, required: true}, 38 | sourceType: {type: String, required: true}, 39 | prev: {type: String, default: null}, 40 | next: {type: String, default: null} 41 | }) 42 | const ApiKeySchema = new Schema({ 43 | api_key: {type: String, required: true, unique: true}, 44 | url: {type: String, required: true} 45 | }) 46 | 47 | export const ApiKey = mongoose.model("ApiKey",ApiKeySchema) 48 | export const User = mongoose.model("User", UserSchema) 49 | export const App = mongoose.model("App", AppSchema) 50 | export const Node = mongoose.model("Node", NodeSchema) 51 | 52 | 53 | -------------------------------------------------------------------------------- /server/src/controllers/argoController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import { App } from '../config/MongoDb'; 3 | import * as T from '../types'; 4 | 5 | //Grabs all user apps to display for user************************************************************************************************ 6 | //grabs all apps user has access to 7 | export const getAllUserApps = async ( 8 | req: Request, 9 | res: Response, 10 | next: NextFunction 11 | ): Promise => { 12 | try { 13 | let appList: T.AppList[] = []; 14 | for (let i = 0; i < res.locals.argoTokens.length; i++) { 15 | let data: any = await fetch( 16 | `${res.locals.argoTokens[i].url}/api/v1/applications`, 17 | { 18 | method: 'GET', 19 | headers: { 20 | Authorization: `Bearer ${res.locals.argoTokens[i].api_key}`, 21 | } 22 | }) 23 | data = await data.json() 24 | appList = appList.concat(data.items); 25 | 26 | } 27 | console.log(appList) 28 | res.locals.apps = appList.map(app => { 29 | const apps = {} as T.App; 30 | apps.name = app.metadata.name; 31 | apps.uid = app.metadata.uid; 32 | apps.source = app.spec.source 33 | return apps; 34 | }) 35 | console.log(res.locals.apps) 36 | return next(); 37 | } 38 | catch (err) { 39 | console.log(err) 40 | return next({ 41 | log: 'Error while invoking middleware: getAllUserApps', 42 | status: 400, 43 | message: `Error getAllUserApps: ${err}`, 44 | }); 45 | } 46 | }; 47 | 48 | //AutoUpdate functions********************************************************************************************************* 49 | //queries argoCD for all application clusters running 50 | export const updateApp = async ( 51 | req: Request, 52 | res: Response, 53 | next: NextFunction 54 | ): Promise => { 55 | try { 56 | let data: any = await fetch( 57 | `${res.locals.argoToken.url}/api/v1/applications`, 58 | { 59 | method: 'GET', 60 | headers: { 61 | Authorization: `Bearer ${res.locals.argoToken.api_key}`, 62 | }, 63 | } 64 | ); 65 | data = await data.json(); 66 | 67 | if (req === undefined) return data.items; 68 | res.locals.apps = data.items; 69 | return next(); 70 | } catch (err) { 71 | return next({ 72 | log: 'Error while invoking middleware: updateApp', 73 | status: 400, 74 | message: `Error updateApp `, 75 | }); 76 | } 77 | }; 78 | 79 | //detects all new application clusters and adds them to the database 80 | export const updateAppDatabase = async ( 81 | req: Request, 82 | res: Response, 83 | next: NextFunction 84 | ): Promise => { 85 | try { 86 | const appList = []; 87 | for (let i = 0; i < res.locals.apps.length; i++) { 88 | const { name, uid } = res.locals.apps[i].metadata; 89 | let data: T.App[] = await App.find({ name, uid }); 90 | if (data.length < 1) { 91 | await App.create({ name, uid }); 92 | appList.push({ name, uid }); 93 | } 94 | } 95 | if (req === undefined) return appList; 96 | res.locals.appList = appList; 97 | return next(); 98 | } catch (err) { 99 | return next({ 100 | log: 'Error while invoking middleware: updateAppDatabase', 101 | status: 400, 102 | message: `Error updateAppDatabase: ${err}`, 103 | }); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /server/src/controllers/authController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | 3 | export const isLoggedIn = async (req: Request, res: Response, next: NextFunction) => { 4 | if (req.isAuthenticated()) { 5 | return next(); 6 | } else { 7 | res.status(401).send('failed'); 8 | } 9 | } -------------------------------------------------------------------------------- /server/src/controllers/autoUpdate.ts: -------------------------------------------------------------------------------- 1 | import { Node, App } from '../config/MongoDb'; 2 | import {addNode} from './dbController'; 3 | import * as T from '../types' 4 | import { config } from '../keys'; 5 | 6 | export const startAutoUpdate = (): void => { 7 | let init:boolean = true; 8 | setInterval(async () => { 9 | try { 10 | const {url, api_key}:T.ApiKey = config.global 11 | let appList:T.App[] = [] 12 | const data = await fetch(`${url}/api/v1/applications`, { 13 | method:"GET", 14 | headers: { 15 | Authorization: `Bearer ${api_key}`, 16 | }}) 17 | let apps = await data.json(); 18 | appList = apps.items.map( (app: any) => { 19 | const {name, uid} = app.metadata 20 | const {source} = app.spec 21 | return {name, uid, source} 22 | }) 23 | if (init) { 24 | init = false; 25 | await updateAppList(appList) 26 | }else { 27 | appList = (await updateAppList(appList)) as T.App[] 28 | } 29 | if (appList.length > 0) { 30 | for (let app of appList) { 31 | let appDb:T.App = await App.findOne({uid: app.uid}); 32 | const {name, uid, source, head, tail} = appDb; 33 | const checkupdate = new checkManifestUpdate({name, uid, source, head, tail}, url, api_key); 34 | checkupdate.update() 35 | } 36 | } 37 | }catch(err) { 38 | const {url, api_key}:T.ApiKey = config.global 39 | if (url === "undefined" || api_key === "undefined") console.log('configure AppConfig.json location server/config/AppConfig.json') 40 | console.log(err) 41 | } 42 | }, 6000) 43 | } 44 | 45 | async function updateAppList(arr:T.App[]):Promise { 46 | try { 47 | const appList:T.App[] = [] 48 | for (let i:number =0; i < arr.length; i++) { 49 | const {name, uid, source} = arr[i]; 50 | const app = await App.find({uid}); 51 | if (app.length < 1) { 52 | const app:T.App = await App.create({name, uid, source}); 53 | appList.push(app); 54 | } 55 | } 56 | return appList 57 | }catch(err){ 58 | console.log(err) 59 | const error:T.error = { 60 | message: 'src/controllers/autoUpdate error updateAppList function error', 61 | status:500, 62 | log: `error on server side` 63 | } 64 | console.log(error) 65 | return [] 66 | } 67 | } 68 | 69 | class checkManifestUpdate { 70 | apikey:string 71 | url: string 72 | time: number 73 | checkingManifest: boolean 74 | app:T.App 75 | intervalId: NodeJS.Timer 76 | revision: string 77 | constructor(app:T.App, url:string, api_key:string) { 78 | this.apikey = api_key; 79 | this.url = url; 80 | this.checkingManifest = false; 81 | this.app = app; 82 | this.time = 6000; 83 | this.revision; 84 | } 85 | update():string { 86 | if (!this.checkingManifest) { 87 | this.checkingManifest = true; 88 | this.intervalId = setInterval(async ():Promise => { 89 | try{ 90 | console.log('manifest check') 91 | if (this.app.tail !== null) { 92 | if (this.revision === undefined) { 93 | const LastNode:T.Node = await Node.findOne({_id: this.app.tail}) 94 | const {revision} = await LastNode 95 | this.revision = revision 96 | } 97 | } 98 | const response = await fetch(`${this.url}/api/v1/applications/${this.app.name}/manifests`, { 99 | headers: { 100 | Authorization: `Bearer ${this.apikey}` 101 | }}); 102 | const data = await response.json() 103 | // console.log("before error", data) 104 | const {manifests, revision, sourceType} = data 105 | if (this.revision !== revision) { 106 | console.log(manifests) 107 | const mani = await JSON.stringify(manifests) 108 | const newNode:T.Node = await addNode(undefined, { 109 | locals:{ 110 | uid: this.app.uid, 111 | manifest: mani, 112 | revision: revision, 113 | sourceType: sourceType 114 | }},(err)=>{console.log(err)}) as T.Node; 115 | this.revision = newNode.revision 116 | } 117 | }catch(err) { 118 | console.log(err) 119 | const error:T.error = { 120 | message: 'server/controller/autoUpdate checkManifestUpdate.update error', 121 | status: 500, 122 | log: 'serverside error' 123 | } 124 | console.error(error) 125 | } 126 | }, this.time) 127 | } 128 | return'' 129 | } 130 | } -------------------------------------------------------------------------------- /server/src/controllers/dbController.ts: -------------------------------------------------------------------------------- 1 | import {User, App, Node, ApiKey} from '../config/MongoDb'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import { HydratedDocument } from 'mongoose'; 4 | import * as T from '../types' 5 | 6 | // Add Node to linked list of app******************************************************************************** 7 | // pass in the uid of the cluster you want to look at as res.locals.uid 8 | // res.locals = 9 | // { 10 | // uid: String, fetched from db or api this is application's uid 11 | // manifest: (fetched from api and stringify it plz) 12 | // revision: {fetched from api} 13 | // } 14 | export const addNode = async (req: Request | undefined, res: Response | any, next?: NextFunction ): Promise => { 15 | // create and save the node to the app 16 | try { 17 | const {uid, manifest,revision, sourceType} = res.locals 18 | const app:HydratedDocument = await App.findOne({uid: uid}) 19 | const node:HydratedDocument = await Node.create({manifest: manifest, revision:revision, sourceType: sourceType}) 20 | if (app.head === null) { 21 | app.head = node._id 22 | app.tail = node._id 23 | } else { 24 | const prevNode:HydratedDocument = await Node.findOne({_id: app.tail}); 25 | prevNode.next = node._id; 26 | node.prev = app.tail; 27 | app.tail = node._id; 28 | prevNode.save(); 29 | node.save(); 30 | } 31 | app.save() 32 | if (req === undefined) return node 33 | return next() 34 | } catch(err) { 35 | const error:T.error = { 36 | message: `server/middleware/dbController.js dbController.addNode ${typeof err === 'object'? JSON.stringify(err) : err }`, 37 | status:500, 38 | log:'data base error' 39 | } 40 | console.log(err) 41 | return next(error) 42 | } 43 | } 44 | 45 | //Adding admin API Key ************************************************************************************************ 46 | // pass in as res.locals 47 | // { 48 | // api_key: , 49 | // uid: , 50 | // } 51 | ////////////////////////// 52 | export const addGlobalApiKey = async (req: Request, res: Response, next: NextFunction): Promise => { 53 | try { 54 | const { api_key, url } = req.body 55 | const Api_Key:T.ApiKey = await ApiKey.create({ api_key, url }) 56 | return next(); 57 | } 58 | catch(err) { 59 | const error:T.error = { 60 | message: `server/middleware/dbController.js dbController.addKey ${typeof err === 'object'? JSON.stringify(err) : err }`, 61 | status:500, 62 | log:'data base error' 63 | } 64 | return next(error) 65 | } 66 | } 67 | 68 | //finds all the admin api keys 69 | //passes in nothin 70 | // checking gloablapi key 71 | ///////////////////////// 72 | export const getGlobalApiKey = async (req: Request, res: Response, next: NextFunction): Promise => { 73 | try { 74 | let data:T.ApiKey[] = await ApiKey.find({}); 75 | if (data.length < 1) { 76 | console.log('no token'); 77 | if(!next) return undefined; 78 | return res.status(200).json({ 79 | api_key: false, 80 | url: false, 81 | }); 82 | } 83 | else { 84 | if(!next) return data; 85 | else { 86 | res.locals.argoToken = data; 87 | return next(); 88 | } 89 | } 90 | } 91 | catch (err) { 92 | return next({ 93 | log: 'Error while invoking middleware: dbController.checkToken', 94 | status: 400, 95 | message: `Error checkToken: ${err}`, 96 | }); 97 | } 98 | } 99 | 100 | //Manifest ************************************************************************************************ 101 | //grabs the first five most recent manifests for the requested application cluster 102 | //client should pass down app uid in query parameter 103 | ///////////////////// 104 | export const getManifests = async (req: Request, res: Response, next: NextFunction): Promise => { 105 | const { uid } = req.query; 106 | try { 107 | let data:T.App = await App.findOne({ uid }); 108 | const manifests:T.Node[] = []; 109 | let curr = data.tail; 110 | let i =0 111 | while (i < 5 && curr) { 112 | let manifestData:T.Node = await Node.findOne({ _id: curr }); 113 | manifests.push(manifestData); 114 | curr = manifestData.prev; 115 | i++ 116 | } 117 | res.locals.manifests = manifests as T.Node[] 118 | return next(); 119 | } 120 | catch (err){ 121 | console.error(err) 122 | const error:T.error = { 123 | log: 'Error while invoking middleware: dbController/getManifests', 124 | status: 400, 125 | message: `Error getManifests: ${err}`, 126 | } 127 | return next(error); 128 | } 129 | } 130 | 131 | // get next 5 manifests 132 | // data comes as req.query 133 | // { 134 | // _id: string 135 | // } 136 | ////////////////////// 137 | export const getNextManifests = async (req: Request, res: Response, next: NextFunction): Promise => { 138 | const { id } = req.query; 139 | try { 140 | let i = 0 141 | const manifests:T.Node[] = []; 142 | let cur = id; 143 | while (i< 5 && cur){ 144 | const curNode:T.Node = await Node.findOne({_id:cur}); 145 | manifests.push(curNode); 146 | cur = curNode.prev; 147 | i+=1; 148 | } 149 | 150 | res.locals.manifests = manifests as T.Node[]; 151 | return next(); 152 | } 153 | catch (err){ 154 | console.log(err) 155 | return next({ 156 | log: 'Error while invoking middleware: getManifests', 157 | status: 400, 158 | message: `Error getManifests: ${err}`, 159 | }); 160 | } 161 | } -------------------------------------------------------------------------------- /server/src/controllers/userController.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../config/MongoDb'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import * as T from '../types'; 4 | import { HydratedDocument } from 'mongoose'; 5 | //User Git Token************************************************************************************************ 6 | 7 | export const checkUserGitToken = async ( 8 | req: Request, 9 | res: Response, 10 | next: NextFunction 11 | ): Promise => { 12 | const { user } = req.query; 13 | let data: T.User = await User.findOne({ githubId: user }); 14 | const { githubId, githubToken } = data; 15 | if (githubToken === '') { 16 | return res.status(200).json({ githubToken: 'no token' }); 17 | } else { 18 | res.locals.gitToken = { githubId, githubToken }; 19 | return next(); 20 | } 21 | }; 22 | 23 | export const patchUserGitToken = async ( 24 | req: Request, 25 | res: Response, 26 | next: NextFunction 27 | ): Promise => { 28 | try { 29 | console.log(req.body); 30 | const { githubId, gitToken } = req.body; 31 | const user: HydratedDocument = await User.findOneAndUpdate( 32 | { githubId: githubId }, 33 | { githubToken: gitToken }, 34 | { 35 | new: true, 36 | } 37 | ); 38 | res.locals.response = user; 39 | return next(); 40 | } catch (err) { 41 | console.log('this is the fucntion error ', err); 42 | const error = { 43 | log: `server/middlewarte/dbcontroller error`, 44 | status: 500, 45 | message: 'server side error check serverlog', 46 | }; 47 | return next(error); 48 | } 49 | }; 50 | 51 | //************************************************************************************************************** 52 | 53 | //User Argo Token************************************************************************************************ 54 | 55 | export const getUserToken = async (req: Request, res: Response, next: NextFunction): Promise => { 56 | try { 57 | const { user } = req.query; 58 | let data: T.User = await User.findOne({ githubId: user }); 59 | if (data.argo_tokens.length < 1) { 60 | return res.status(400).json({ 61 | api_key: false, 62 | url: false, 63 | }); 64 | } 65 | else { 66 | res.locals.argoTokens = data.argo_tokens 67 | return next(); 68 | } 69 | } 70 | catch (err) { 71 | console.log(err) 72 | return next({ 73 | log: 'Error while invoking middleware: getUserToken', 74 | status: 400, 75 | message: `Error getUserToken: ${err}`, 76 | }); 77 | }; 78 | } 79 | 80 | export const addUserApiKey = async (req: Request, res: Response, next: NextFunction): Promise => { 81 | try { 82 | const {api_key, url, githubId} = req.body 83 | const user = await User.updateOne({githubId: githubId, 'argo_tokens.api_key': {$ne: api_key}}, {$push: {argo_tokens: {api_key: api_key, url:url}}}); 84 | res.locals.response = 'successfully saved'; 85 | res.locals.api_key = {api_key: api_key, } 86 | return next() 87 | } catch(err) { 88 | const error = { 89 | log: `server/middlewarte/dbcontroller error ${typeof err === "object" ? JSON.stringify(err) : err}`, 90 | status: 500, 91 | message:'server side error check serverlog' 92 | }; 93 | return next(error) 94 | }; 95 | }; 96 | 97 | //************************************************************************************************************** 98 | -------------------------------------------------------------------------------- /server/src/keys.ts: -------------------------------------------------------------------------------- 1 | interface config { 2 | global: {url:string, api_key: string} 3 | mongodb_uri: string 4 | } 5 | export const config:config = {global: { 6 | url:process.env.url, 7 | api_key: process.env.api_key 8 | }, 9 | mongodb_uri: process.env.mongodb_uri 10 | } 11 | 12 | interface env { 13 | GithubId: string, 14 | GithubSecret: string, 15 | GithubCbUrl: string 16 | } 17 | 18 | const keys:env = { 19 | GithubId: process.env.GITHUB_ID, 20 | GithubSecret: process.env.GITHUB_SECRET, 21 | GithubCbUrl: process.env.GITHUB_CALLBACK_URL 22 | } 23 | 24 | export default keys; -------------------------------------------------------------------------------- /server/src/passport/passport.ts: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import keys from '../keys'; 3 | import {User} from '../config/MongoDb'; 4 | import { Strategy as GitHubStrategy } from 'passport-github2'; 5 | import * as types from '../types'; 6 | 7 | //Each subsequent request will not contain credentials, but rather the unique cookie that identifies the session. 8 | //In order to support login sessions, Passport will serialize and deserialize user instances to and from the session. 9 | passport.serializeUser((user, done) => { 10 | done(null, user); 11 | }); 12 | passport.deserializeUser((user, done) => { 13 | done(null, user); 14 | }); 15 | 16 | passport.use(new GitHubStrategy({ 17 | clientID: keys.GithubId, 18 | clientSecret: keys.GithubSecret, 19 | callbackURL: keys.GithubCbUrl 20 | }, 21 | (accessToken: string, refreshToken: string, profile: types.GithubProfile, done: (error: any, user? : any, info?: any) => void) => { 22 | const githubId:string = profile.username; 23 | User.findOne({ githubId }, (err: Error | null, user: types.User) => { 24 | //check for errors finding user 25 | if (err) { 26 | return done(err); 27 | } 28 | //no user found, so we save new user to db 29 | if (!user) { 30 | User.create({ githubId }, (err: Error | null, user: types.User) => { 31 | if (err) { 32 | return done(err, user); 33 | } 34 | else { 35 | return done(null, user); 36 | } 37 | }) 38 | } 39 | else { 40 | return done(null, user); 41 | } 42 | }) 43 | } 44 | )); -------------------------------------------------------------------------------- /server/src/routes/apiRouter.ts: -------------------------------------------------------------------------------- 1 | import express, {Request, Response} from 'express' 2 | import * as userController from '../controllers/userController' 3 | import * as dbController from '../controllers/dbController' 4 | import * as authController from '../controllers/authController' 5 | import * as argoController from '../controllers/argoController' 6 | const router = express.Router() 7 | 8 | 9 | /**************** usergitToken route ***************/ 10 | router.get('/gitToken', userController.checkUserGitToken, (req:Request,res: Response)=> { 11 | return res.json(res.locals.gitToken) 12 | }) 13 | // /api/usergithubtoken post route has changed to gitToken post 14 | router.post('/gitToken', userController.patchUserGitToken, (req:Request, res:Response)=> { 15 | return res.json(res.locals.response) 16 | }) 17 | 18 | /**************** userargoToken route ***************/ 19 | router.get('/userApiKey', userController.getUserToken, (req:Request,res:Response)=> { 20 | return res.json(res.locals) 21 | }) 22 | 23 | router.post('/userApiKey', userController.addUserApiKey, (req: Request, res: Response) => { 24 | return res.json(res.locals) 25 | }) 26 | 27 | /**************** globalApiKey route ***************/ 28 | // req.body ={ 29 | // api_key 30 | // url 31 | // } 32 | // this as global apiKey for autoupdate functionality 33 | //make the global apiKey routes admin only in the future 34 | router.post('/globalKey', dbController.addGlobalApiKey, (req: Request, res: Response) =>{ 35 | return res.json('successfully added') 36 | }) 37 | 38 | router.get('/globalKey', dbController.getGlobalApiKey, (req: Request, res: Response) => { 39 | return res.json(res.locals.argoToken) 40 | }) 41 | 42 | /**************** check user auth route ***************/ 43 | router.get('/checkUser', authController.isLoggedIn, (req: Request, res: Response) => { 44 | return res.json(req.user) 45 | }) 46 | 47 | /**************** grab user apps route ***************/ 48 | router.get('/apps', userController.getUserToken, argoController.getAllUserApps, (req: Request, res: Response) => { 49 | return res.json(res.locals.apps) 50 | }) 51 | 52 | /**************** grab manifests for app route ***************/ 53 | 54 | // [ 55 | // { 56 | // _id: 'object_id:asdasdasdasd', 57 | // manifest: {type: String, required: true}, //stringify 58 | // revision: {type: String, required: true}, 59 | // prev: {type: String, default: null}, 60 | // next: {type: String, default: null} 61 | // } 62 | // ] 63 | // this gets the first five of the manifests 64 | router.get('/manifests', dbController.getManifests, (req: Request, res: Response) => { 65 | return res.json(res.locals.manifests) 66 | }) 67 | 68 | // [ 69 | // { 70 | // _id: 'object_id:asdasdasdasd', 71 | // manifest: {type: String, required: true}, //stringify 72 | // revision: {type: String, required: true}, 73 | // prev: {type: String, default: null}, 74 | // next: {type: String, default: null} 75 | // } 76 | // ] 77 | // this gets the next five of the manifests 78 | router.get('/nextManifests', dbController.getNextManifests, (req: Request, res: Response)=>{ 79 | return res.json(res.locals.manifests) 80 | }) 81 | 82 | export default router -------------------------------------------------------------------------------- /server/src/server.ts: -------------------------------------------------------------------------------- 1 | import cookieSession from 'cookie-session' 2 | import cookieParser from 'cookie-parser' 3 | import passport from 'passport' 4 | import express, {Request, Response, NextFunction} from 'express' 5 | import cors from 'cors' 6 | const app = express(); 7 | const PORT = process.env.PORT || 3000; 8 | import './passport/passport' 9 | 10 | import * as authController from './controllers/authController' 11 | import {startAutoUpdate} from './controllers/autoUpdate' 12 | import apiRouter from './routes/apiRouter' 13 | import * as types from './types' 14 | // import keys from './keys' 15 | startAutoUpdate() 16 | app.use(express.json()); 17 | app.use(cookieParser()); 18 | app.use(cors({ credentials: true, origin: '/' })); 19 | 20 | app.use(cookieSession({ 21 | name: "session", 22 | keys: ['key1', 'key2'] 23 | })); 24 | 25 | app.use(passport.initialize()); 26 | app.use(passport.session()); 27 | 28 | app.get('/auth/github/callback', 29 | passport.authenticate('github', {failureRedirect: '/'}), 30 | (req: Request, res: Response)=> { 31 | return res.redirect('/home') 32 | } 33 | ); 34 | 35 | app.get('/auth/github', 36 | passport.authenticate('github', {scope: ['user:email']}) 37 | ); 38 | 39 | //req.logout required callback function so added one err => console.log(err) 40 | app.get('/logout', (req: Request,res: Response)=> { 41 | req.session = null; 42 | req.logout((err)=> {console.log(err)}); 43 | return res.json({logout: true }); 44 | }) 45 | 46 | /*********** did not make authController yet comeback to activate this route ********/ 47 | app.get('/', authController.isLoggedIn, (req:Request,res: Response)=> { 48 | return res.json('success') 49 | }) 50 | 51 | app.use('/api', apiRouter) 52 | 53 | app.use('*', (req: Request,res: Response)=> { 54 | return res.status(404).send({ 55 | message: '404 requested page not found' 56 | }) 57 | }) 58 | 59 | 60 | app.use((err:types.error | any, req: Request, res: Response, next:NextFunction) => { 61 | const error:types.error = { 62 | message: 'you will never know what is wrong with me **hint** server related ', 63 | status: 404, 64 | log: 'if you see this text you did not setup error handling -.-;;' 65 | } 66 | err = Object.assign(error, err) 67 | return res.status(err.status).send(err) 68 | }) 69 | 70 | 71 | app.listen(PORT, ()=>{ 72 | console.log(`listening ${PORT}...`) 73 | }) -------------------------------------------------------------------------------- /server/src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export type error = { 3 | message: string, 4 | status: number, 5 | log: string 6 | } 7 | 8 | export interface ApiKey { 9 | _id?: any, 10 | api_key: string, 11 | url: string, 12 | __v?: number 13 | } 14 | 15 | export interface User { 16 | _id?: any, 17 | githubId: string, 18 | githubToken: string, 19 | argo_tokens: ApiKey[], 20 | __v?: number 21 | } 22 | 23 | export interface App { 24 | _id?: any, 25 | name: string, 26 | uid: string, 27 | source: { 28 | repoURL: string, 29 | path:string, 30 | targetRevision: string 31 | }, 32 | date?: Date, 33 | head?: string, 34 | tail?: string, 35 | __v?: number 36 | } 37 | 38 | export interface Node { 39 | _id?: any 40 | manifest: string, 41 | revision: string, 42 | sourceType:string, 43 | prev?: string, 44 | next?: string 45 | } 46 | 47 | interface GithubPhoto { 48 | value: string 49 | } 50 | 51 | export interface GithubProfile extends GithubPhoto { 52 | id: string, 53 | nodeId: string, 54 | displayName?: string, 55 | username: string, 56 | profileUrl: string, 57 | photos: GithubPhoto[], 58 | provider: string 59 | } 60 | 61 | export interface AppData { 62 | name: string, 63 | uid: string, 64 | } 65 | 66 | export interface AppList { 67 | metadata?: AppData 68 | spec?: any 69 | } -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "outDir": "dist", 10 | "esModuleInterop": true, 11 | "noImplicitAny": true, 12 | "declaration": true 13 | }, 14 | "include": [ 15 | "src/**/*.ts", "src/**/*.json" 16 | ], 17 | "exclude": [ 18 | "src/**/*.spec.ts", 19 | "node_modules" 20 | ] 21 | } --------------------------------------------------------------------------------