├── .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/) ·   
3 | 
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 |
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 |
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 | handleClick(e)}
53 | >
54 | View
55 |
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 |
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 | handleClick(e)}>Click me to login!
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 |
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 |
handleClick(e)}
116 | >
117 | ⬅️ Back
118 |
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 | handleGit(e)}
139 | >
140 | Push to git
141 |
142 |
143 |
144 |
145 |
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 |
handleClick(e)}
44 | >
45 | View manifests
46 |
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 | handleBack(e)}
72 | >
73 | ⬅️ All apps
74 |
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 |
24 |
25 | handleClick(e)}>
26 | Logout
27 |
28 |
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 |
119 |
120 |
121 | {/*
122 |
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 | }
--------------------------------------------------------------------------------