├── .eslintrc.json ├── .gitignore ├── Dockerfile ├── KlustrSchema2.sql ├── README.md ├── __tests__ ├── jest.js └── supertest.js ├── build └── bundle.js ├── client ├── assets │ ├── dashboard-icon.svg │ └── transparentohana.png ├── components │ ├── App.jsx │ ├── AppContext.js │ ├── CreateCluster.jsx │ ├── CreateSpace.jsx │ ├── CreateTeam.jsx │ ├── CreateUser.jsx │ ├── DeployComponent.jsx │ ├── DeployPage.jsx │ ├── HomePage.jsx │ ├── LoginPage.jsx │ ├── TeamsDisplay.jsx │ └── UsersDisplay.jsx ├── containers │ ├── ClusterContainer.jsx │ ├── MainContainer.jsx │ ├── NavPane.jsx │ └── SpacesContainer.jsx ├── index.js ├── styles.css └── themes │ ├── CustomThemeProvider.jsx │ ├── Dark.jsx │ ├── Normal.jsx │ ├── Themes.jsx │ └── ToggleTheme.jsx ├── docker-compose.yml ├── index.html ├── package-lock.json ├── package.json ├── server ├── controllers │ ├── clusterController.js │ ├── spacesController.js │ ├── teamsController.js │ ├── userController.js │ └── vClusterController.js ├── db │ └── models.js ├── routers │ ├── clusterRouter.js │ ├── spacesRouter.js │ ├── teamsRouter.js │ ├── userRouter.js │ └── vClusterRouter.js ├── server.js └── start.js ├── terminalCommands.js ├── webpack.config.js └── yamlConfigs ├── deployImage.yaml ├── rbac-creator.yaml ├── spaceConfig.yaml ├── spaceLimitConfig.yaml ├── userAccount.yaml ├── vCluster.yaml ├── viewUserAccount.yaml └── yamlGenerator.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["airbnb", "prettier"], 7 | "parserOptions": { 8 | "ecmaFeatures": { 9 | "jsx": true 10 | }, 11 | "ecmaVersion": 12, 12 | "sourceType": "module" 13 | }, 14 | "plugins": ["react", "prettier"], 15 | "rules": { 16 | "prettier/prettier": ["error", { "singleQuote": true }], 17 | "react/jsx-filename-extension": "off", 18 | "import/extensions": "off" 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | klustr-316321-f31f9767f260.json -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | ## YOU MUST FILL IN THE ENV VALUES FOR THE IMAGE TO PROPERLY BUILD 3 | 4 | ## BEFORE BUILDING THE IMAGE CHANGE ALL NODEMON VALUES IN PACKAGE.JSON SCRIPTS TO NODE 5 | 6 | 7 | # Set arguments 8 | ## Add gcloud service account here 9 | ARG gcloud_account=newadmin@klustr-316321.iam.gserviceaccount.com 10 | 11 | 12 | ## Add gcloud account key file path here 13 | ARG key_path=/secret/klustr-316321-f31f9767f260.json 14 | 15 | ## Add GKE project_id here 16 | ARG project_id=klustr-316321 17 | 18 | 19 | ## Add GKE Cluster zone here 20 | ARG cluster_zone=us-west1-a 21 | 22 | 23 | 24 | # ohana app image 25 | FROM node:latest AS app 26 | WORKDIR / 27 | COPY package*.json ./ 28 | RUN npm install 29 | COPY . . 30 | EXPOSE 8080 31 | ENTRYPOINT ["node", "./server/server.js"] 32 | 33 | ## Installs gcloud and kubectl as well as other products 34 | ###### old gcloud link gcr.io/google.com/cloudsdktool/cloud-sdk:latest 35 | FROM gcr.io/google.com/cloudsdktool/cloud-sdk:slim as gcloud 36 | WORKDIR / 37 | 38 | ## Install debian as OS for container 39 | FROM debian:latest as os 40 | WORKDIR / 41 | COPY --from=app / . 42 | COPY --from=gcloud / . 43 | RUN apt-get update 44 | RUN apt-get install sudo 45 | # RUN apt-get install vim 46 | # authenticate gcloud service account via json token 47 | ENV gcloud_account= 48 | 49 | 50 | ENV key_path= 51 | 52 | 53 | RUN gcloud auth activate-service-account ${gcloud_account} --key-file=${key_path} 54 | 55 | 56 | ENV project_id= 57 | 58 | RUN gcloud config set project ${project_id} 59 | RUN sudo apt-get install kubectl 60 | # generate kubectl config file via gcloud cli (note 'cluster-1' is cluster name to be replaced) 61 | ENV cluster_zone= 62 | 63 | 64 | RUN gcloud container clusters get-credentials cluster-1 --zone=${cluster_zone} 65 | 66 | ## run sequential commands to install helm 67 | RUN curl https://baltocdn.com/helm/signing.asc | sudo apt-key add - 68 | RUN sudo apt-get install apt-transport-https --yes 69 | RUN echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list 70 | RUN sudo apt-get update 71 | RUN sudo apt-get install helm 72 | 73 | # install vCluster 74 | RUN curl -s -L "https://github.com/loft-sh/vcluster/releases/latest" | sed -nE 's!.*"([^"]*vcluster-linux-amd64)".*!https://github.com\1!p' | xargs -n 1 curl -L -o vcluster && chmod +x vcluster; 75 | RUN sudo mv vcluster /usr/local/bin; 76 | 77 | # spin up webpack dev server 78 | CMD npm run dev 79 | -------------------------------------------------------------------------------- /KlustrSchema2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "users" ( 2 | "_id" smallserial PRIMARY KEY, 3 | "email" varchar, 4 | "password" varchar, 5 | "first_name" varchar, 6 | "last_name" varchar, 7 | "is_admin" varchar, 8 | "team_id" smallint 9 | ); 10 | 11 | CREATE TABLE "teams" ( 12 | "_id" smallserial PRIMARY KEY, 13 | "name" varchar 14 | ); 15 | 16 | CREATE TABLE "namespaces" ( 17 | "_id" smallserial PRIMARY KEY, 18 | "name" varchar, 19 | "cluster_id" smallint 20 | "team_id" smallint 21 | ); 22 | 23 | CREATE TABLE "vclusters" ( 24 | "_id" smallserial PRIMARY KEY, 25 | "name" varchar, 26 | "owner_id" smallint, 27 | "team_id" smallint, 28 | "namespace_id" smallint 29 | ); 30 | 31 | CREATE TABLE "clusters" ( 32 | "_id" smallserial PRIMARY KEY, 33 | "name" varchar 34 | ); 35 | 36 | ALTER TABLE "users" ADD FOREIGN KEY ("team_id") REFERENCES "teams" ("_id"); 37 | 38 | ALTER TABLE "vclusters" ADD FOREIGN KEY ("namespace_id") REFERENCES "namespaces" ("_id"); 39 | 40 | ALTER TABLE "vclusters" ADD FOREIGN KEY ("owner_id") REFERENCES "users" ("_id"); 41 | 42 | ALTER TABLE "vclusters" ADD FOREIGN KEY ("team_id") REFERENCES "teams" ("_id"); 43 | 44 | ALTER TABLE "namespaces" ADD FOREIGN KEY ("cluster_id") REFERENCES "clusters" ("_id"); 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## To run the app locally, follow the instructions below 2 | 3 | *For extended documentation, please visit our [website](http://ohana-app.io)* 4 | 5 | Run the following command to install the necessary dependencies: ```npm install ``` 6 | 7 | When the dependencies are finished installing, run the following to concurrently bundle the application's assets and start the Express server in a ```development``` environment: 8 | 9 | Ensure helm has been installed. You can do so with these commands: 10 | ```helm version``` to check your version. Please reference [helm](https://helm.sh/docs/intro/install/) for further instructions based on your OS 11 | 12 | Ensure vCluster has been installed: 13 | ```vcluster -v``` to check your version. Please reference the following to download for your respective OS or visit [vCluster](https://www.vcluster.com/): 14 | 15 | **Intel Mac** 16 | ``` 17 | curl -s -L "https://github.com/loft-sh/vcluster/releases/latest" | sed -nE 's!.*"([^"]*vcluster-darwin-amd64)".*!https://github.com\1!p' | xargs -n 1 curl -L -o vcluster && chmod +x vcluster; 18 | 19 | sudo mv vcluster /usr/local/bin; 20 | ``` 21 | **Silicon Mac** 22 | ``` 23 | curl -s -L "https://github.com/loft-sh/vcluster/releases/latest" | sed -nE 's!.*"([^"]*vcluster-darwin-arm64)".*!https://github.com\1!p' | xargs -n 1 curl -L -o vcluster && chmod +x vcluster; 24 | sudo mv vcluster /usr/local/bin; 25 | ``` 26 | **Windows** 27 | ``` 28 | md -Force "$Env:APPDATA\vcluster"; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Tls,Tls11,Tls12'; 29 | 30 | Invoke-WebRequest -UseBasicParsing ((Invoke-WebRequest -URI "https://github.com/loft-sh/vcluster/releases/latest" -UseBasicParsing).Content -replace "(?ms).*`"([^`"]*vcluster-windows-amd64.exe)`".*","https://github.com/`$1") -o $Env:APPDATA\vcluster\vcluster.exe; 31 | 32 | $env:Path += ";" + $Env:APPDATA + "\vcluster"; 33 | 34 | [Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::User); 35 | ``` 36 | **Linux AMD** 37 | ``` 38 | curl -s -L "https://github.com/loft-sh/vcluster/releases/latest" | sed -nE 's!.*"([^"]*vcluster-linux-amd64)".*!https://github.com\1!p' | xargs -n 1 curl -L -o vcluster && chmod +x vcluster; 39 | 40 | sudo mv vcluster /usr/local/bin; 41 | ``` 42 | **Linux ARM** 43 | ``` 44 | curl -s -L "https://github.com/loft-sh/vcluster/releases/latest" | sed -nE 's!.*"([^"]*vcluster-linux-arm64)".*!https://github.com\1!p' | xargs -n 1 curl -L -o vcluster && chmod +x vcluster; 45 | 46 | sudo mv vcluster /usr/local/bin; 47 | ``` 48 | To run the application, execute ```npm run dev``` 49 | 50 | After the application finishes compiling, you should be served the Ohana user interface on ```localhost:8080```, with the server listening on ```localhost:3000```. You should 51 | see a login screen rendered to your browser. 52 | -------------------------------------------------------------------------------- /__tests__/jest.js: -------------------------------------------------------------------------------- 1 | const userController = require("../server/controllers/userController"); 2 | const { Pool } = require("pg"); 3 | import { jest } from "@jest/globals"; 4 | 5 | const myURI = 6 | "postgres://calxwrwx:LmX5QBLtJ4IC4iyuJy3pbWm-KlFNxJFB@kashin.db.elephantsql.com/calxwrwx"; 7 | 8 | describe("db unit tests", () => { 9 | // async callback to run before every all tests run 10 | beforeAll((done) => { 11 | console.log("connecting to db"); 12 | 13 | const pool = new Pool({ 14 | connectionString: myURI, 15 | }); 16 | 17 | const db = { 18 | query: (text, params, callback) => { 19 | console.log("executed query", text); 20 | return pool.query(text, params, callback); 21 | }, 22 | }; 23 | console.log("connected to db"); 24 | done(); 25 | }); 26 | // async callback to run once all tests are completed regardless of pass/fail 27 | // afterAll((done) => { 28 | // testing github link 29 | 30 | // }) 31 | describe("add", () => { 32 | it("successfully adds a user", () => {}); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /__tests__/supertest.js: -------------------------------------------------------------------------------- 1 | const app = require("../server/server.js"); 2 | const request = require("supertest"); 3 | 4 | const server = "http://localhost:8080"; 5 | 6 | describe('GET "/"', () => { 7 | it("Serves the application", async () => { 8 | await request(server) 9 | .get("/") 10 | .expect(200) 11 | .expect("Content-Type", /text\/html/); 12 | }); 13 | }); 14 | 15 | describe('POST "/user/login" - login check', () => { 16 | it("Logs in with correct credentials", async () => { 17 | const res = await request(server) 18 | .post("/user/login") 19 | .set("Content-Type", "application/json") 20 | .send({ email: "test@email.com", password: "123" }) 21 | .expect(200); 22 | }); 23 | it("Sends cookies after successful login", async () => { 24 | const res = await request(server) 25 | .post("/user/login") 26 | .set("Content-Type", "application/json") 27 | .send({ email: "test@email.com", password: "123" }) 28 | .expect(200); 29 | expect(res.headers["set-cookie"].length).not.toBe(0); 30 | }); 31 | it("Returns an error with incorrect credentials", async () => { 32 | const res = await request(server) 33 | .post("/user/login") 34 | .set("Content-Type", "application/json") 35 | .send({ email: "test@email.com", password: "incorrect" }) 36 | .expect(500); 37 | expect(res.body).toBe("Incorrect username/password"); 38 | }); 39 | }); 40 | 41 | describe('POST "/user/create" - creating user', () => { 42 | afterAll(async () => { 43 | await request(server) 44 | .delete("/user") 45 | .set("Content-type", "application/json") 46 | .send({ email: "deleteme@test.com" }); 47 | }); 48 | 49 | it("Adds a new user to the database", async () => { 50 | const res = await request(server) 51 | .post("/user/create") 52 | .set("Content-type", "application/json") 53 | .send({ 54 | email: "deleteme@test.com", 55 | password: "123", 56 | firstName: "test", 57 | lastName: "test", 58 | teamName: "ohana", 59 | isAdmin: "true", 60 | }) 61 | .expect(200); 62 | expect(res.body).toBe("Successfully added new user"); 63 | }); 64 | 65 | it("New user appears in the database", async () => { 66 | const res = await request(server).get("/user").expect(200); 67 | expect(res.body[res.body.length - 1].email).toBe("deleteme@test.com"); 68 | }); 69 | }); 70 | 71 | describe('GET "/spaces" and "/vclusters"', () => { 72 | it("Receives a list of all the active namespaces", async () => { 73 | const res = await request(server).get("/spaces/fetch").expect(200); 74 | expect(Array.isArray(res.body)).toBe(true); 75 | }); 76 | it("Receives a list of all the active vClusters", async () => { 77 | const res = await request(server).get("/vclusters").expect(200); 78 | expect(Array.isArray(res.body)).toBe(true); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /client/assets/dashboard-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /client/assets/transparentohana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ohana/dd06c6f1c5daf786fe8424a9a1c05a814e99fd00/client/assets/transparentohana.png -------------------------------------------------------------------------------- /client/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import MainContainer from "../containers/MainContainer.jsx"; 3 | import { BrowserRouter as Router } from "react-router-dom"; 4 | import { AppContext } from "./AppContext"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | import ToggleTheme from "../themes/ToggleTheme.jsx"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | footer: { 10 | padding: theme.spacing(2, 2), 11 | marginTop: "auto", 12 | backgroundColor: theme.palette.primary.main, 13 | }, 14 | header: { 15 | paddingBottom: "2px", 16 | }, 17 | })); 18 | 19 | const App = (props) => { 20 | const [isLoggedIn, setIsLoggedIn] = useState(false); 21 | const [isAdmin, setIsAdmin] = useState(false); 22 | const [clusterNames, setClusterNames] = useState([]); 23 | const [namespaceNames, setNamespaces] = useState([]); 24 | const [teamId, setTeamId] = useState(""); 25 | const [firstName, setFirstName] = useState(""); 26 | const [lastName, setLastName] = useState(""); 27 | const [vClusters, setvClusters] = useState(""); 28 | const value = { 29 | isLoggedIn, 30 | setIsLoggedIn, 31 | isAdmin, 32 | setIsAdmin, 33 | clusterNames, 34 | setClusterNames, 35 | namespaceNames, 36 | setNamespaces, 37 | teamId, 38 | setTeamId, 39 | firstName, 40 | setFirstName, 41 | lastName, 42 | setLastName, 43 | vClusters, 44 | setvClusters, 45 | }; 46 | // if (isLoggedIn) navBar = ; 47 | 48 | const classes = useStyles(); 49 | 50 | return ( 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 | ); 60 | }; 61 | 62 | export default App; 63 | -------------------------------------------------------------------------------- /client/components/AppContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const AppContext = React.createContext() -------------------------------------------------------------------------------- /client/components/CreateCluster.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import { 3 | TextField, 4 | Button, 5 | CircularProgress, 6 | FormControl, 7 | InputLabel, 8 | Select, 9 | MenuItem, 10 | } from "@material-ui/core"; 11 | import { AppContext } from "./AppContext"; 12 | import { makeStyles } from "@material-ui/core/styles"; 13 | import Box from "@material-ui/core/Box"; 14 | 15 | const useStyles = makeStyles((theme) => ({ 16 | root: { 17 | flexGrow: 1, 18 | }, 19 | paper: { 20 | textAlign: "center", 21 | }, 22 | })); 23 | 24 | const CreateCluster = () => { 25 | const [clusterName, setClusterName] = useState(""); 26 | const [vClusterName, setvClusterName] = useState(""); 27 | const [hostNamespace, setHostNamespace] = useState(""); 28 | const [inProgress, setInProgress] = useState(""); 29 | const [currentProcess, setCurrentProcess] = useState(""); 30 | const { clusterNames, namespaceNames } = useContext(AppContext); 31 | 32 | const classes = useStyles(); 33 | 34 | const clusterNamesDropdown = []; 35 | clusterNames.forEach((name) => 36 | clusterNamesDropdown.push({name}) 37 | ); 38 | 39 | const namespaceDropdown = []; 40 | namespaceNames.forEach((name) => 41 | namespaceDropdown.push({name}) 42 | ); 43 | 44 | const handleClusterNameChange = (e) => { 45 | setClusterName(e.target.value); 46 | }; 47 | 48 | const handleSetvClusterName = (e) => { 49 | setvClusterName(e.target.value); 50 | }; 51 | 52 | const handleHostNamespaceChange = (e) => { 53 | setHostNamespace(e.target.value); 54 | }; 55 | 56 | const formSubmit = (e) => { 57 | const data = { clusterName, vClusterName, hostNamespace }; 58 | setCurrentProcess(Creating {vClusterName}); 59 | setInProgress(); 60 | e.preventDefault(); 61 | 62 | fetch("/vclusters/create", { 63 | method: "POST", 64 | headers: { 65 | "Content-Type": "application/json", 66 | }, 67 | body: JSON.stringify(data), 68 | }) 69 | .then((res) => res.json()) 70 | .then((data) => { 71 | setCurrentProcess(data); 72 | setInProgress(""); 73 | }) 74 | .catch((err) => console.log(err)); 75 | }; 76 | 77 | return ( 78 |
79 |
80 | 89 |

Create a new virtual cluster

90 |
91 |
92 | 98 |
99 | 105 | 112 | 113 | Select Cluster 114 | 120 | 121 | 122 | Select Namespace 123 | 130 | 131 | 132 | 137 | 138 | 139 | 147 | 148 | 157 | {inProgress} 158 |
159 | {currentProcess} 160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | ); 169 | }; 170 | 171 | export default CreateCluster; 172 | -------------------------------------------------------------------------------- /client/components/CreateSpace.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import { 3 | TextField, 4 | Button, 5 | CircularProgress, 6 | FormControl, 7 | InputLabel, 8 | Select, 9 | MenuItem, 10 | Box, 11 | } from "@material-ui/core"; 12 | import { AppContext } from "./AppContext"; 13 | 14 | const CreateSpace = () => { 15 | const [hostCluster, setHostClusterName] = useState(""); 16 | const [createHostNamespace, setCreateHostNamespace] = useState(""); 17 | const [deployHostNamespace, setDeployHostNamespace] = useState(""); 18 | const [imageFile, setImageFile] = useState(""); 19 | const [deploymentName, setDeploymentName] = useState(""); 20 | const [externalIp, setExternalIp] = useState(""); 21 | const [clickMe, setClickMe] = useState(""); 22 | const { clusterNames, namespaceNames } = useContext(AppContext); 23 | 24 | const clusterNamesDropdown = []; 25 | clusterNames.forEach((name) => 26 | clusterNamesDropdown.push({name}) 27 | ); 28 | 29 | const namespaceDropdown = []; 30 | namespaceNames.forEach((name) => 31 | namespaceDropdown.push({name}) 32 | ); 33 | 34 | const handleSetHostClusterName = (e) => { 35 | setHostClusterName(e.target.value); 36 | }; 37 | 38 | const handleSetCreateHostNamespace = (e) => { 39 | setCreateHostNamespace(e.target.value); 40 | setDeployHostNamespace(e.target.value); 41 | }; 42 | 43 | const handleSetImageFile = (e) => { 44 | setImageFile(e.target.value); 45 | }; 46 | 47 | const handleSetDeploymentName = (e) => { 48 | setDeploymentName(e.target.value); 49 | }; 50 | 51 | const formSubmit = (e) => { 52 | const data = { hostCluster, createHostNamespace }; 53 | e.preventDefault(); 54 | fetch("/spaces/create", { 55 | method: "POST", 56 | headers: { 57 | "Content-Type": "application/json", 58 | }, 59 | body: JSON.stringify(data), 60 | }) 61 | .then((res) => res.json()) 62 | .then((data) => { 63 | setExternalIp(data); 64 | }) 65 | .catch((err) => console.log(err)); 66 | }; 67 | 68 | const deployButton = (e) => { 69 | const data = { deploymentName, deployHostNamespace, imageFile }; 70 | e.preventDefault(); 71 | fetch("/spaces/deploy", { 72 | method: "POST", 73 | headers: { 74 | "Content-Type": "application/json", 75 | }, 76 | body: JSON.stringify(data), 77 | }) 78 | .then((res) => res.json()) 79 | .then((data) => console.log(data)) 80 | .catch((err) => console.log(err)); 81 | }; 82 | 83 | const getIp = (e) => { 84 | e.preventDefault(); 85 | const data = { deploymentName, deployHostNamespace }; 86 | fetch("/spaces/getip", { 87 | method: "POST", 88 | headers: { 89 | "Content-Type": "application/json", 90 | }, 91 | body: JSON.stringify(data), 92 | }) 93 | .then((res) => res.json()) 94 | .then((data) => { 95 | const ip = data.trim(); 96 | setExternalIp(ip); 97 | }); 98 | setClickMe(`Click here to visit ${deploymentName}`); 99 | }; 100 | 101 | return ( 102 |
103 | {/*

Create a namespace and deploy

*/} 104 | 105 |
106 |
107 | 113 | 120 | 121 |

Create a Namespace

122 |
123 |
124 | 125 | 131 | 132 | Select Cluster 133 | 140 | 141 | 142 | 147 | 155 | 156 |
157 |
158 |
159 |
160 | ); 161 | }; 162 | 163 | export default CreateSpace; 164 | -------------------------------------------------------------------------------- /client/components/CreateTeam.jsx: -------------------------------------------------------------------------------- 1 | import { Button, TextField, Box } from "@material-ui/core"; 2 | import React, { useState } from "react"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | 5 | const useStyles = makeStyles((theme) => ({ 6 | root: { 7 | flexGrow: 1, 8 | }, 9 | paper: { 10 | textAlign: "center", 11 | }, 12 | shape: { 13 | backgroundColor: theme.palette.primary.main, 14 | width: 55, 15 | height: 55, 16 | }, 17 | shapeCircle: { 18 | borderRadius: "100%", 19 | }, 20 | })); 21 | 22 | const CreateTeam = () => { 23 | const [teamName, setTeamName] = useState(""); 24 | const [addTeamStatus, setAddTeamStatus] = useState(""); 25 | 26 | const handleTeamNameChange = (e) => { 27 | setTeamName(e.target.value); 28 | }; 29 | const handleSubmit = (e) => { 30 | console.log("am i hitting submit?"); 31 | e.preventDefault(); 32 | fetch("/teams", { 33 | method: "POST", 34 | headers: { 35 | "Content-Type": "application/json", 36 | }, 37 | body: JSON.stringify({ teamName }), 38 | }) 39 | .then((res) => res.json()) 40 | .then((data) => setAddTeamStatus(

{data}

)) 41 | .catch((err) => { 42 | console.log(err); 43 | setAddTeamStatus(

Unable to create new team

); 44 | }); 45 | }; 46 | 47 | return ( 48 |
49 |
50 | 56 | 57 |

Create a Team

58 |
59 | {addTeamStatus} 60 |
61 | 69 | 74 |

75 | 78 |
79 |
80 |
81 | ); 82 | }; 83 | 84 | export default CreateTeam; 85 | -------------------------------------------------------------------------------- /client/components/CreateUser.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | Button, 4 | TextField, 5 | FormControlLabel, 6 | Checkbox, 7 | Box, 8 | } from "@material-ui/core"; 9 | 10 | const CreateUser = () => { 11 | const [isAdmin, setAdmin] = useState(false); 12 | const [email, setEmail] = useState(""); 13 | const [password, setPassword] = useState(""); 14 | const [firstName, setFirstName] = useState(""); 15 | const [lastName, setLastName] = useState(""); 16 | const [teamName, setTeamName] = useState(""); 17 | const [createdUser, setCreatedUser] = useState(""); 18 | 19 | const handleAdmin = (e) => { 20 | setAdmin(e.target.checked); 21 | }; 22 | 23 | const handlePassword = (e) => { 24 | setPassword(e.target.value); 25 | }; 26 | 27 | const handleFirstName = (e) => { 28 | setFirstName(e.target.value); 29 | }; 30 | 31 | const handleLastName = (e) => { 32 | setLastName(e.target.value); 33 | }; 34 | 35 | const handleTeamName = (e) => { 36 | setTeamName(e.target.value); 37 | }; 38 | 39 | const handleSubmit = (e) => { 40 | e.preventDefault(); 41 | const data = { email, password, firstName, lastName, teamName, isAdmin }; 42 | console.log("data", data); 43 | fetch("/user/create", { 44 | method: "POST", 45 | headers: { 46 | "Content-Type": "application/json", 47 | }, 48 | body: JSON.stringify(data), 49 | }) 50 | .then((res) => res.json()) 51 | .then((data) => setCreatedUser({data})) 52 | .catch((err) => { 53 | console.log(err); 54 | }); 55 | }; 56 | 57 | const handleEmail = (e) => { 58 | setEmail(e.target.value); 59 | }; 60 | 61 | return ( 62 |
63 |
64 | 71 | 72 |

Create a new User

73 |
74 |
75 | 82 | 87 |

88 | 94 |

95 |
96 | 103 | 108 |

109 | 114 |

115 | 116 | 121 |

122 |
123 | {/* */} 124 | 132 | } 134 | name='isAdmin' 135 | label='Add as Admin' 136 | labelPlacement='end' 137 | onChange={handleAdmin} 138 | value={isAdmin} 139 | /> 140 |

141 | {createdUser} 142 | 150 |
151 |
152 |
153 | ); 154 | }; 155 | 156 | export default CreateUser; 157 | -------------------------------------------------------------------------------- /client/components/DeployComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import { 3 | TextField, 4 | Button, 5 | FormControl, 6 | InputLabel, 7 | Select, 8 | MenuItem, 9 | Box, 10 | } from "@material-ui/core"; 11 | import { AppContext } from "./AppContext"; 12 | 13 | const deployComp = () => { 14 | const [hostCluster, setHostClusterName] = useState(""); 15 | const [createHostNamespace, setCreateHostNamespace] = useState(""); 16 | const [deployHostNamespace, setDeployHostNamespace] = useState(""); 17 | const [imageFile, setImageFile] = useState(""); 18 | const [deploymentName, setDeploymentName] = useState(""); 19 | const [externalIp, setExternalIp] = useState(""); 20 | const [clickMe, setClickMe] = useState(""); 21 | const { clusterNames, namespaceNames } = useContext(AppContext); 22 | 23 | const clusterNamesDropdown = []; 24 | clusterNames.forEach((name) => 25 | clusterNamesDropdown.push({name}) 26 | ); 27 | 28 | const namespaceDropdown = []; 29 | namespaceNames.forEach((name) => 30 | namespaceDropdown.push({name}) 31 | ); 32 | 33 | const handleSetHostClusterName = (e) => { 34 | setHostClusterName(e.target.value); 35 | }; 36 | 37 | const handleSetCreateHostNamespace = (e) => { 38 | setCreateHostNamespace(e.target.value); 39 | setDeployHostNamespace(e.target.value); 40 | }; 41 | 42 | const handleSetImageFile = (e) => { 43 | setImageFile(e.target.value); 44 | }; 45 | 46 | const handleSetDeploymentName = (e) => { 47 | setDeploymentName(e.target.value); 48 | }; 49 | 50 | const formSubmit = (e) => { 51 | const data = { hostCluster, createHostNamespace }; 52 | e.preventDefault(); 53 | fetch("/spaces/create", { 54 | method: "POST", 55 | headers: { 56 | "Content-Type": "application/json", 57 | }, 58 | body: JSON.stringify(data), 59 | }) 60 | .then((res) => res.json()) 61 | .then((data) => { 62 | setExternalIp(data); 63 | }) 64 | .catch((err) => console.log(err)); 65 | }; 66 | 67 | const deployButton = (e) => { 68 | const data = { deploymentName, deployHostNamespace, imageFile }; 69 | e.preventDefault(); 70 | fetch("/spaces/deploy", { 71 | method: "POST", 72 | headers: { 73 | "Content-Type": "application/json", 74 | }, 75 | body: JSON.stringify(data), 76 | }) 77 | .then((res) => res.json()) 78 | .then((data) => console.log(data)) 79 | .catch((err) => console.log(err)); 80 | }; 81 | 82 | const getIp = (e) => { 83 | e.preventDefault(); 84 | const data = { deploymentName, deployHostNamespace }; 85 | fetch("/spaces/getip", { 86 | method: "POST", 87 | headers: { 88 | "Content-Type": "application/json", 89 | }, 90 | body: JSON.stringify(data), 91 | }) 92 | .then((res) => res.json()) 93 | .then((data) => { 94 | const ip = data.trim(); 95 | setExternalIp(ip); 96 | }); 97 | setClickMe(`Click here to visit ${deploymentName}`); 98 | }; 99 | 100 | return ( 101 |
102 |
103 | 111 | 114 |

Deploy an Image

115 |
116 | 121 | 122 | 123 | Select Namespace 124 | 131 | 132 | 133 | 138 |
139 | 147 |
148 |
149 |
150 | 159 | 163 | 164 |

Visit your Container

165 |
166 | 167 | {clickMe} 168 | 169 |
170 | 171 | Select Namespace 172 | 179 | 180 | 181 | 186 | 194 |
195 |
196 | ); 197 | }; 198 | 199 | export default deployComp; 200 | -------------------------------------------------------------------------------- /client/components/DeployPage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box } from '@material-ui/core'; 3 | import clsx from 'clsx'; 4 | import { makeStyles } from '@material-ui/core/styles'; 5 | import Grid from '@material-ui/core/Grid'; 6 | import DeployComp from './DeployComponent.jsx'; 7 | import NavPane from '../containers/NavPane.jsx' 8 | 9 | 10 | const useStyles = makeStyles((theme) => ({ 11 | root: { 12 | flexGrow: 1, 13 | }, 14 | paper: { 15 | textAlign: 'center', 16 | }, 17 | shape: { 18 | backgroundColor: theme.palette.primary.main, 19 | width: 55, 20 | height: 55, 21 | }, 22 | shapeCircle: { 23 | borderRadius: '100%', 24 | }, 25 | })); 26 | 27 | 28 | const DeployPage = () => { 29 | 30 | const classes = useStyles(); 31 | const circle =
; 32 | 33 | return ( 34 |
35 |
36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | 62 | {circle} 63 | 64 | 65 | 66 | 76 | 79 |

Create and manage deployments

80 |
81 |
82 |
83 | 84 | 85 |
86 | 87 |
88 |
89 |
90 |
91 |
92 | ) 93 | } 94 | 95 | export default DeployPage; -------------------------------------------------------------------------------- /client/components/HomePage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useContext } from "react"; 2 | import { Button, Box } from "@material-ui/core"; 3 | import { Link } from "react-router-dom"; 4 | import { AppContext } from "./AppContext"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | import Grid from "@material-ui/core/Grid"; 7 | import clsx from "clsx"; 8 | import NavPane from "../containers/NavPane.jsx"; 9 | 10 | const useStyles = makeStyles((theme) => ({ 11 | root: { 12 | flexGrow: 1, 13 | }, 14 | paper: { 15 | textAlign: "center", 16 | }, 17 | shape: { 18 | backgroundColor: theme.palette.primary.main, 19 | width: 55, 20 | height: 55, 21 | }, 22 | shapeCircle: { 23 | borderRadius: "100%", 24 | }, 25 | })); 26 | 27 | const HomePage = () => { 28 | const classes = useStyles(); 29 | const circle =
; 30 | 31 | const { 32 | setIsLoggedIn, 33 | setIsAdmin, 34 | setClusterNames, 35 | setNamespaces, 36 | setTeamId, 37 | setFirstName, 38 | setLastName, 39 | vClusters, 40 | firstName, 41 | namespaceNames, 42 | } = useContext(AppContext); 43 | useEffect(() => { 44 | fetch("/cookies") 45 | .then((res) => res.json()) 46 | .then((data) => { 47 | setIsLoggedIn(data.isLoggedIn); 48 | setIsAdmin(data.isAdmin); 49 | setTeamId(data.teamId); 50 | setFirstName(data.firstName); 51 | setLastName(data.lastName); 52 | }); 53 | fetch("/clusters/list") 54 | .then((res) => res.json()) 55 | .then((data) => { 56 | let names = []; 57 | data.forEach((element) => names.push(element.name)); 58 | setClusterNames(names); 59 | }); 60 | fetch("/spaces/fetchspaces") 61 | .then((res) => res.json()) 62 | .then((data) => { 63 | let namespaces = []; 64 | data.forEach((element) => namespaces.push(element.name)); 65 | setNamespaces(namespaces); 66 | }); 67 | }, []); 68 | 69 | const namespaceList = []; 70 | namespaceNames.forEach((name) => namespaceList.push(
  • {name}
  • )); 71 | 72 | return ( 73 |
    74 |
    75 | 76 | 77 | 78 | 79 | 80 | 81 | 87 | 95 | {circle} 96 | 97 | 98 | 99 | 109 | 110 |

    111 | Welcome to Ohana,
    {firstName} 112 |

    113 |
    114 |
    115 |
    116 | 117 |
    118 | 126 | 137 |

    {vClusters.length}

    138 |

    Active vClusters

    139 |
    140 | 151 |

    {namespaceNames.length}

    152 |

    Active Namespaces

    153 |
    154 |
    155 |
    156 | 157 |
    158 | 170 | 175 | 176 |

    Active vClusters

    177 |
      {vClusters}
    178 |
    179 | 180 |

    Active Namespaces

    181 |
      {namespaceList}
    182 |
    183 |
    184 | 191 | 192 | 193 | 200 | 201 | 202 | 203 | 210 | 211 | 212 |
    213 |
    214 |
    215 |
    216 |
    217 |
    218 | ); 219 | }; 220 | 221 | export default HomePage; 222 | -------------------------------------------------------------------------------- /client/components/LoginPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from "react"; 2 | import { Button, TextField, Box } from "@material-ui/core"; 3 | import { useHistory } from "react-router-dom"; 4 | import { AppContext } from "./AppContext"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | import Grid from "@material-ui/core/Grid"; 7 | import dashboardIcon from "../assets/dashboard-icon.svg"; 8 | 9 | const useStyles = makeStyles((theme) => ({ 10 | button: { 11 | color: theme.palette.primary, 12 | }, 13 | root: { 14 | "& .MuiFilledInput-root": { 15 | background: "#d5d5d5", 16 | }, 17 | }, 18 | paper: { 19 | textAlign: "center", 20 | }, 21 | })); 22 | 23 | const LoginPage = () => { 24 | const { isLoggedIn, setIsLoggedIn, isAdmin, setIsAdmin } = 25 | useContext(AppContext); 26 | const [email, setEmail] = useState(""); 27 | const [password, setPassword] = useState(""); 28 | const [token, setToken] = useState(); 29 | const [incorrectInfo, setIncorrectInfo] = useState(""); 30 | let history = useHistory(); 31 | 32 | const classes = useStyles(); 33 | 34 | // when the component re-renders, check if the isLoggedIn is truthy and push 35 | // homepage endpoint so the route can render the proper page 36 | 37 | useEffect(() => { 38 | if (isLoggedIn) { 39 | history.push("/home"); 40 | } 41 | }); 42 | 43 | const formSubmit = (e) => { 44 | e.preventDefault(); 45 | setRedirect(true); 46 | }; 47 | 48 | const handleSubmit = (e) => { 49 | e.preventDefault(); 50 | const form = e.target; 51 | 52 | e.preventDefault(); 53 | fetch(form.action, { 54 | method: form.method, 55 | headers: { 56 | "Content-Type": "application/json", 57 | }, 58 | body: JSON.stringify({ 59 | email, 60 | password, 61 | }), 62 | }) 63 | .then((res) => { 64 | return res.json(); 65 | }) 66 | .then((data) => { 67 | if (data === "Incorrect username/password") 68 | setIncorrectInfo(

    Incorrect username/password

    ); 69 | else { 70 | fetch("/user/verify", { 71 | method: "POST", 72 | headers: { 73 | "Content-Type": "application/json", 74 | }, 75 | body: JSON.stringify({ 76 | token, 77 | }), 78 | }) 79 | .then((res) => { 80 | return res.json(); 81 | }) 82 | .then((res) => { 83 | setIsAdmin(res); 84 | if (typeof res === "boolean") { 85 | setIsLoggedIn(true); 86 | } 87 | if (isAdmin) history.push("/admin"); 88 | else history.push("/home"); 89 | }); 90 | } 91 | }); 92 | }; 93 | 94 | const handleEmail = (e) => { 95 | setEmail(e.target.value); 96 | }; 97 | 98 | const handlePassword = (e) => { 99 | setPassword(e.target.value); 100 | }; 101 | 102 | return ( 103 |
    104 |
    105 | 112 | 113 |
    114 | 120 | ohana 125 | 126 | 133 | 141 |
    142 |

    Aloha, welcome to Ohana

    143 |
    144 | 145 |
    146 |

    Effortless K8s Management

    147 |
    148 |
    149 |
    150 |
    151 |
    152 | 153 | 159 |
    166 |

    Sign in

    167 | 176 |

    177 | 187 |

    188 | {incorrectInfo} 189 | 197 |
    198 |
    199 |
    200 |
    201 |
    202 |
    203 | ); 204 | }; 205 | 206 | export default LoginPage; 207 | -------------------------------------------------------------------------------- /client/components/TeamsDisplay.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useEffect } from "react"; 2 | import { AppContext } from "../components/AppContext.js"; 3 | import clsx from "clsx"; 4 | import Box from "@material-ui/core/Box"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | import Grid from "@material-ui/core/Grid"; 7 | import CreateTeam from "./CreateTeam.jsx"; 8 | import NavPane from "../containers/NavPane.jsx"; 9 | 10 | const useStyles = makeStyles((theme) => ({ 11 | root: { 12 | flexGrow: 1, 13 | }, 14 | paper: { 15 | textAlign: "center", 16 | }, 17 | shape: { 18 | backgroundColor: theme.palette.primary.main, 19 | width: 55, 20 | height: 55, 21 | }, 22 | shapeCircle: { 23 | borderRadius: "100%", 24 | }, 25 | })); 26 | 27 | const TeamsDisplay = () => { 28 | const classes = useStyles(); 29 | const circle =
    ; 30 | 31 | const [teamNames, setTeamNames] = useState([]); 32 | const [users, setUsers] = useState([]); 33 | 34 | const { 35 | setIsLoggedIn, 36 | setIsAdmin, 37 | setClusterNames, 38 | setNamespaces, 39 | setTeamId, 40 | setFirstName, 41 | setLastName, 42 | setvClusters, 43 | vClusters, 44 | firstName, 45 | namespaceNames, 46 | } = useContext(AppContext); 47 | 48 | useEffect(() => { 49 | fetch("/teams/fetch") 50 | .then((res) => res.json()) 51 | .then((data) => { 52 | let names = []; 53 | data.forEach((element) => names.push(element.name)); 54 | setTeamNames(names); 55 | }); 56 | 57 | fetch("/user/") 58 | .then((res) => res.json()) 59 | .then((data) => { 60 | let users = []; 61 | data.forEach((element) => users.push(element.email)); 62 | setUsers(users); 63 | }); 64 | }, []); 65 | 66 | const teamNamesList = []; 67 | teamNames.forEach((name) => teamNamesList.push(
  • {name}
  • )); 68 | 69 | return ( 70 |
    71 |
    72 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 92 | {circle} 93 | 94 | 95 | 96 | 106 | 107 |

    Create and manage teams

    108 |
    109 |
    110 |
    111 | 112 |
    113 | 121 | 132 |

    {teamNamesList.length}

    133 |

    Active Teams

    134 |
    135 | 146 |

    {users.length}

    147 |

    Active Users

    148 |
    149 |
    150 |
    151 | 152 |
    153 | 164 |

    Current Teams

    165 | 166 |
      167 | {teamNamesList} 168 |
    169 |
    170 |
    171 |
    172 | 173 |
    174 | 175 |
    176 |
    177 |
    178 |
    179 |
    180 | ); 181 | }; 182 | 183 | export default TeamsDisplay; 184 | -------------------------------------------------------------------------------- /client/components/UsersDisplay.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from "react"; 2 | import { Box } from "@material-ui/core"; 3 | import clsx from "clsx"; 4 | import { makeStyles } from "@material-ui/core/styles"; 5 | import Grid from "@material-ui/core/Grid"; 6 | import CreateUser from "./CreateUser.jsx"; 7 | import NavPane from "../containers/NavPane.jsx"; 8 | import { AppContext } from "./AppContext"; 9 | 10 | const useStyles = makeStyles((theme) => ({ 11 | root: { 12 | flexGrow: 1, 13 | }, 14 | paper: { 15 | textAlign: "center", 16 | }, 17 | shape: { 18 | backgroundColor: theme.palette.primary.main, 19 | width: 55, 20 | height: 55, 21 | }, 22 | shapeCircle: { 23 | borderRadius: "100%", 24 | }, 25 | })); 26 | 27 | const UsersDisplay = () => { 28 | const classes = useStyles(); 29 | const circle =
    ; 30 | 31 | const [teamNames, setTeamNames] = useState([]); 32 | const [users, setUsers] = useState([]); 33 | 34 | const { 35 | setIsLoggedIn, 36 | setIsAdmin, 37 | setClusterNames, 38 | setNamespaces, 39 | setTeamId, 40 | setFirstName, 41 | setLastName, 42 | setvClusters, 43 | vClusters, 44 | firstName, 45 | namespaceNames, 46 | } = useContext(AppContext); 47 | 48 | useEffect(() => { 49 | fetch("/teams/fetch") 50 | .then((res) => res.json()) 51 | .then((data) => { 52 | let names = []; 53 | data.forEach((element) => names.push(element.name)); 54 | setTeamNames(names); 55 | }); 56 | fetch("/user/") 57 | .then((res) => res.json()) 58 | .then((data) => { 59 | let users = []; 60 | data.forEach((element) => users.push(element.email)); 61 | setUsers(users); 62 | }); 63 | }, []); 64 | 65 | const usersList = []; 66 | users.forEach((name) => usersList.push(
  • {name}
  • )); 67 | 68 | const teamNamesList = []; 69 | teamNames.forEach((name) => teamNamesList.push(
  • {name}
  • )); 70 | return ( 71 |
    72 |
    73 | 78 | 79 | 80 | 81 | 82 | 83 | 89 | 97 | {circle} 98 | 99 | 100 | 101 | 111 | 112 |

    Create and manage users

    113 |
    114 |
    115 |
    116 | 117 |
    118 | 126 | 137 |

    {teamNames.length}

    138 |

    Active Teams

    139 |
    140 | 151 |

    {users.length}

    152 |

    Active Users

    153 |
    154 |
    155 |
    156 | 157 |
    158 | 169 |

    Current Users

    170 | 171 |
      172 | {usersList} 173 |
    174 |
    175 |
    176 |
    177 | 178 |
    179 | 180 |
    181 |
    182 |
    183 |
    184 |
    185 | ); 186 | }; 187 | 188 | export default UsersDisplay; 189 | -------------------------------------------------------------------------------- /client/containers/ClusterContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useContext } from "react"; 2 | import CreateCluster from "../components/CreateCluster.jsx"; 3 | import { AppContext } from "../components/AppContext"; 4 | import { makeStyles } from "@material-ui/core/styles"; 5 | import Grid from "@material-ui/core/Grid"; 6 | import Box from "@material-ui/core/Box"; 7 | import { createMuiTheme, ThemeProvider } from "@material-ui/core"; 8 | import clsx from "clsx"; 9 | import NavPane from "./NavPane.jsx"; 10 | 11 | const useStyles = makeStyles((theme) => ({ 12 | root: { 13 | flexGrow: 1, 14 | }, 15 | paper: { 16 | textAlign: "center", 17 | }, 18 | shape: { 19 | backgroundColor: theme.palette.primary.main, 20 | width: 55, 21 | height: 55, 22 | }, 23 | shapeCircle: { 24 | borderRadius: "100%", 25 | }, 26 | })); 27 | 28 | const ClusterContainer = () => { 29 | const classes = useStyles(); 30 | const circle =
    ; 31 | 32 | const { 33 | setIsLoggedIn, 34 | setIsAdmin, 35 | setClusterNames, 36 | setNamespaces, 37 | setTeamId, 38 | vClusters, 39 | } = useContext(AppContext); 40 | useEffect(() => { 41 | fetch("/cookies") 42 | .then((res) => res.json()) 43 | .then((data) => { 44 | console.log("cookie request data", data); 45 | setIsLoggedIn(data.isLoggedIn); 46 | setIsAdmin(data.isAdmin); 47 | setTeamId(data.teamId); 48 | }); 49 | fetch("/clusters/list") 50 | .then((res) => res.json()) 51 | .then((data) => { 52 | let names = []; 53 | data.forEach((element) => names.push(element.name)); 54 | setClusterNames(names); 55 | }); 56 | fetch("/spaces/fetchspaces") 57 | .then((res) => res.json()) 58 | .then((data) => { 59 | let namespaces = []; 60 | data.forEach((element) => namespaces.push(element.name)); 61 | setNamespaces(namespaces); 62 | }); 63 | }, []); 64 | 65 | const nameSpacesList = []; 66 | return ( 67 |
    68 |
    69 | 70 | 71 | 72 | 73 | 74 | 75 | 81 | 89 | {circle} 90 | 91 | 92 | 93 | 103 | 104 |

    View and create virtual clusters

    105 |
    106 |
    107 |
    108 | 109 |
    110 | 118 | 129 |

    OK

    130 |

    Connected to GKE

    131 |
    132 | 143 |

    {vClusters.length}

    144 |

    Active vClusters

    145 |
    146 |
    147 |
    148 | 149 |
    150 | 161 |

    Active vClusters

    162 | 163 |
      {vClusters}
    164 |
    165 |
    166 |
    167 | 168 |
    169 | 170 |
    171 |
    172 |
    173 |
    174 |
    175 | ); 176 | }; 177 | 178 | export default ClusterContainer; 179 | -------------------------------------------------------------------------------- /client/containers/MainContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from "react"; 2 | import { AppContext } from "../components/AppContext"; 3 | import { Switch, Route } from "react-router-dom"; 4 | import SpacesContainer from "./SpacesContainer.jsx"; 5 | import ClusterContainer from "./ClusterContainer.jsx"; 6 | import LoginPage from "../components/LoginPage.jsx"; 7 | import HomePage from "../components/HomePage.jsx"; 8 | import TeamsDisplay from "../components/TeamsDisplay.jsx"; 9 | import UsersDisplay from "../components/UsersDisplay.jsx"; 10 | import DeployPage from "../components/DeployPage.jsx"; 11 | 12 | export const LoginContext = React.createContext(); 13 | 14 | const MainContainer = (props) => { 15 | const { 16 | setIsLoggedIn, 17 | setIsAdmin, 18 | setClusterNames, 19 | setNamespaces, 20 | setTeamId, 21 | setFirstName, 22 | setLastName, 23 | setvClusters, 24 | } = useContext(AppContext); 25 | useEffect(() => { 26 | fetch("/cookies") 27 | .then((res) => res.json()) 28 | .then((data) => { 29 | setIsLoggedIn(data.isLoggedIn); 30 | setIsAdmin(data.isAdmin); 31 | setTeamId(data.teamId); 32 | setFirstName(data.firstName); 33 | setLastName(data.lastName); 34 | }); 35 | fetch("/clusters/list") 36 | .then((res) => res.json()) 37 | .then((data) => { 38 | let names = []; 39 | data.forEach((element) => names.push(element.name)); 40 | setClusterNames(names); 41 | }); 42 | fetch("/spaces/fetchspaces") 43 | .then((res) => res.json()) 44 | .then((data) => { 45 | let namespaces = []; 46 | data.forEach((element) => namespaces.push(element.name)); 47 | setNamespaces(namespaces); 48 | }); 49 | fetch("/vclusters") 50 | .then((response) => response.json()) 51 | .then((data) => { 52 | const vClusterList = []; 53 | data.forEach((row) => vClusterList.push(
  • {row.name}
  • )); 54 | setvClusters(vClusterList); 55 | }); 56 | }, []); 57 | 58 | return ( 59 |
    60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | {props.children} 70 |
    71 | ); 72 | }; 73 | 74 | export default MainContainer; 75 | -------------------------------------------------------------------------------- /client/containers/NavPane.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Button, Box } from "@material-ui/core"; 3 | import clsx from "clsx"; 4 | import { makeStyles } from "@material-ui/core/styles"; 5 | import { useHistory, Link } from "react-router-dom"; 6 | import { AppContext } from "../components/AppContext"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | root: { 10 | flexGrow: 1, 11 | }, 12 | paper: { 13 | textAlign: "center", 14 | }, 15 | shape: { 16 | backgroundColor: theme.palette.primary.main, 17 | width: 55, 18 | height: 55, 19 | }, 20 | shapeCircle: { 21 | borderRadius: "100%", 22 | }, 23 | })); 24 | 25 | const NavPane = () => { 26 | const classes = useStyles(); 27 | const circle =
    ; 28 | 29 | const { isAdmin, firstName, lastName } = useContext(AppContext); 30 | console.log("firstname", firstName); 31 | let adminPane = ""; 32 | let userValue = ""; 33 | if (isAdmin) { 34 | userValue = "admin"; 35 | adminPane = ( 36 | 37 | 38 |

    Admin

    39 |
    40 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
    59 | ); 60 | } else { 61 | userValue = "user"; 62 | } 63 | let history = useHistory(); 64 | 65 | return ( 66 |
    67 | 68 | 74 | ohana 79 |

    Ohana

    80 |
    81 | 82 | 95 |    96 | {circle} 97 | 103 |

    104 | {firstName} {lastName} 105 |

    106 |

    107 | {userValue} 108 |

    109 |
    110 |
    111 |
    112 |
    113 | 120 | 121 | 122 |

    General

    123 |
    124 | 125 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 |
    154 | 155 | 156 |

    Support

    157 |
    158 | 165 | 166 | 169 | 170 | 171 | 177 | 178 | 179 | {adminPane} 180 |
    181 |
    182 |
    183 | ); 184 | }; 185 | 186 | export default NavPane; 187 | -------------------------------------------------------------------------------- /client/containers/SpacesContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import CreateSpace from "../components/CreateSpace.jsx"; 3 | import { AppContext } from "../components/AppContext.js"; 4 | import clsx from "clsx"; 5 | import Box from "@material-ui/core/Box"; 6 | import { makeStyles } from "@material-ui/core/styles"; 7 | import Grid from "@material-ui/core/Grid"; 8 | import NavPane from "./NavPane.jsx"; 9 | 10 | const useStyles = makeStyles((theme) => ({ 11 | root: { 12 | flexGrow: 1, 13 | }, 14 | paper: { 15 | textAlign: "center", 16 | }, 17 | shape: { 18 | backgroundColor: theme.palette.primary.main, 19 | width: 55, 20 | height: 55, 21 | }, 22 | shapeCircle: { 23 | borderRadius: "100%", 24 | }, 25 | list: { 26 | height: 400, 27 | maxWidth: 300, 28 | }, 29 | })); 30 | 31 | const SpacesContainer = () => { 32 | const classes = useStyles(); 33 | const circle =
    ; 34 | const { namespaceNames } = useContext(AppContext); 35 | const namespaceList = []; 36 | namespaceNames.forEach((name) => namespaceList.push(
  • {name}
  • )); 37 | 38 | return ( 39 |
    40 |
    41 | 42 | 43 | 44 | 45 | 46 | 47 | 53 | 61 | {circle} 62 | 63 | 64 | 65 | 75 | 76 |

    View and create Namespaces

    77 |
    78 |
    79 |
    80 | 81 |
    82 | 90 | 101 |

    OK

    102 |

    Connected to GKE

    103 |
    104 | 115 |

    {namespaceNames.length}

    116 |

    Active Spaces

    117 |
    118 |
    119 |
    120 | 121 |
    122 | 133 |

    Active Namespaces

    134 |
      {namespaceList}
    135 |
    136 |
    137 | 138 |
    139 | 140 |
    141 |
    142 |
    143 |
    144 |
    145 | ); 146 | }; 147 | 148 | export default SpacesContainer; 149 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "react-dom"; 3 | import App from "./components/App.jsx"; 4 | import { createMuiTheme } from "@material-ui/core"; 5 | import CustomThemeProvider from "./themes/CustomThemeProvider.jsx"; 6 | import CssBaseline from "@material-ui/core/CssBaseline"; 7 | 8 | const theme = createMuiTheme({ 9 | palette: { 10 | type: "light", 11 | primary: { 12 | main: "#EE5FA7", 13 | }, 14 | secondary: { 15 | main: "#5ACD8A", 16 | }, 17 | typography: { 18 | fontFamily: "Raleway, Arial", 19 | }, 20 | }, 21 | }); 22 | 23 | render( 24 | 25 | 26 | 27 | , 28 | 29 | document.getElementById("root") 30 | ); 31 | -------------------------------------------------------------------------------- /client/styles.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@100;600&display=swap"); 2 | 3 | html, 4 | body { 5 | font-family: "Public Sans", sans-serif; 6 | height: 100%; 7 | width: 100%; 8 | } 9 | 10 | mark { 11 | color: #35a22a; 12 | background-color: #e6f8e0; 13 | padding: 2px; 14 | } 15 | 16 | input[type="text"] { 17 | border-color: #fff; 18 | } 19 | 20 | button { 21 | margin: 1em; 22 | } 23 | 24 | .MainContainer { 25 | width: 95%; 26 | } 27 | 28 | #AdminContainer { 29 | width: 100%; 30 | } 31 | 32 | .ohana_logo { 33 | display: absolute; 34 | height: 50px; 35 | } 36 | 37 | #computer { 38 | height: 20vh; 39 | } 40 | 41 | #LoginPage { 42 | padding: 1em; 43 | height: 100%; 44 | } 45 | 46 | .shadow { 47 | box-shadow: 0 0 2rem #c8c8c8; 48 | } 49 | 50 | #leftPaneLogin { 51 | height: 100%; 52 | width: 300px; 53 | max-width: 350px; 54 | border: 2px solid #d5d5d5; 55 | border-radius: 15px; 56 | } 57 | 58 | #leftPaneNav { 59 | height: 100%; 60 | width: 300px; 61 | max-width: 350px; 62 | border: 2px solid #d5d5d5; 63 | border-radius: 15px; 64 | } 65 | 66 | #LoginForm { 67 | display: flex; 68 | flex-direction: column; 69 | justify-content: center; 70 | align-items: center; 71 | } 72 | 73 | #leftPaneLogin { 74 | height: 85vh; 75 | width: 25vw; 76 | border: 1px solid gray; 77 | border-radius: 15px; 78 | } 79 | 80 | #navbar { 81 | background: #eeeeee; 82 | margin: 1em; 83 | } 84 | 85 | #adminpage { 86 | display: grid; 87 | grid-template-columns: auto auto auto; 88 | } 89 | 90 | #createteam { 91 | display: flex; 92 | flex-direction: column; 93 | justify-content: space-around; 94 | align-items: center; 95 | border: 1px solid #d5d5d5; 96 | padding: 1em; 97 | border-radius: 20px; 98 | min-height: 15vh; 99 | max-height: 20vh; 100 | } 101 | 102 | #createuser { 103 | display: flex; 104 | flex-direction: column; 105 | justify-content: center; 106 | align-items: center; 107 | border: 1px solid #d5d5d5; 108 | padding: 1em; 109 | border-radius: 20px; 110 | } 111 | 112 | #create-spaces { 113 | display: flex; 114 | flex-direction: column; 115 | justify-content: space-around; 116 | border: 1px solid #d5d5d5; 117 | padding: 1em; 118 | border-radius: 20px; 119 | min-height: 25vh; 120 | max-height: 30vh; 121 | } 122 | 123 | #create-clusters { 124 | display: flex; 125 | flex-direction: column; 126 | justify-content: center; 127 | align-items: center; 128 | border: 1px solid #d5d5d5; 129 | padding: 1em; 130 | border-radius: 20px; 131 | min-height: 30vh; 132 | max-height: 40vh; 133 | } 134 | 135 | #SpacesList { 136 | display: flex; 137 | flex-direction: column; 138 | justify-content: center; 139 | align-items: center; 140 | border: 1px solid gray; 141 | padding: 1em; 142 | } 143 | 144 | #get-spaces { 145 | align-self: flex-start; 146 | } 147 | 148 | #vClustersList { 149 | display: flex; 150 | flex-direction: column; 151 | justify-content: center; 152 | align-items: center; 153 | border: 1px solid gray; 154 | padding: 1em; 155 | } 156 | 157 | #get-vclusters { 158 | align-self: flex-start; 159 | } 160 | 161 | a { 162 | text-decoration: none; 163 | } 164 | -------------------------------------------------------------------------------- /client/themes/CustomThemeProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState } from "react"; 2 | import { ThemeProvider } from "@material-ui/core/styles"; 3 | import getTheme from "./Themes.jsx"; 4 | 5 | export const CustomThemeContext = createContext({ 6 | currentTheme: "normal", 7 | setTheme: null, 8 | }); 9 | 10 | const CustomThemeProvider = (props) => { 11 | const { children } = props; 12 | 13 | const currentTheme = localStorage.getItem("appTheme") || "normal"; 14 | const [themeName, _setThemeName] = useState(currentTheme); 15 | const theme = getTheme(themeName); 16 | const setThemeName = (name) => { 17 | localStorage.setItem("appTheme", name); 18 | _setThemeName(name); 19 | }; 20 | 21 | const contextValue = { 22 | currentTheme: themeName, 23 | setTheme: setThemeName, 24 | }; 25 | 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | }; 32 | 33 | export default CustomThemeProvider; 34 | -------------------------------------------------------------------------------- /client/themes/Dark.jsx: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from "@material-ui/core/styles"; 2 | 3 | const dark = createMuiTheme({ 4 | palette: { 5 | type: "dark", 6 | primary: { 7 | main: "#ee5fa7", 8 | contrastText: "#ffffff", 9 | }, 10 | background: { 11 | default: "#18191a", 12 | }, 13 | secondary: { 14 | main: "#5ACD8A", 15 | }, 16 | }, 17 | }); 18 | 19 | export default dark; 20 | -------------------------------------------------------------------------------- /client/themes/Normal.jsx: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from "@material-ui/core/styles"; 2 | 3 | const normal = createMuiTheme({ 4 | type: "light", 5 | palette: { 6 | primary: { 7 | main: "#EE5FA7", 8 | contrastText: "#ffffff", 9 | }, 10 | background: { 11 | default: "#ffffff", 12 | }, 13 | secondary: { 14 | main: "#5ACD8A", 15 | }, 16 | }, 17 | }); 18 | 19 | export default normal; 20 | -------------------------------------------------------------------------------- /client/themes/Themes.jsx: -------------------------------------------------------------------------------- 1 | import dark from "./Dark.jsx"; 2 | import normal from "./Normal.jsx"; 3 | 4 | const themes = { 5 | normal, 6 | dark, 7 | }; 8 | 9 | export default function getTheme(theme) { 10 | return themes[theme]; 11 | } 12 | -------------------------------------------------------------------------------- /client/themes/ToggleTheme.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import { CustomThemeContext } from "./CustomThemeProvider.jsx"; 5 | import Brightness2OutlinedIcon from "@material-ui/icons/Brightness2Outlined"; 6 | import Brightness6OutlinedIcon from "@material-ui/icons/Brightness6Outlined"; 7 | 8 | const useStyles = makeStyles((theme) => ({ 9 | root: { 10 | height: 40, 11 | margin: "10px", 12 | }, 13 | button: { 14 | color: theme.palette.primary, 15 | paddingTop: "1em", 16 | paddingBottom: "1em", 17 | }, 18 | })); 19 | 20 | export default function ThemeToggle() { 21 | const classes = useStyles(); 22 | const { currentTheme, setTheme } = useContext(CustomThemeContext); 23 | const isNormal = Boolean(currentTheme === "normal"); 24 | console.log(isNormal); 25 | const handleThemeChange = (event, newTheme) => { 26 | if (isNormal) { 27 | setTheme(newTheme); 28 | } else { 29 | setTheme("normal"); 30 | } 31 | }; 32 | 33 | return ( 34 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | dev: 4 | image: . 5 | container_name: "ohana-dev" 6 | ports: ["3000:8080"] 7 | command: npm run dev 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ohana 6 | 7 | 8 | 9 | 10 |
    11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ohana", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "start": "nodemon server/start.js", 9 | "dev": "concurrently \"nodemon server/start.js\" \"webpack serve --open\"", 10 | "ohana-win": "curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 && chmod 700 get_helm.sh && ./get_helm.sh && md -Force \"$Env:APPDATA\\vcluster\"; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Tls,Tls11,Tls12'; Invoke-WebRequest -UseBasicParsing ((Invoke-WebRequest -URI \"https://github.com/loft-sh/vcluster/releases/latest\" -UseBasicParsing).Content -replace \"(?ms).*`\"([^`\"]*vcluster-windows-amd64.exe)`\".*\",\"https://github.com/`$1\") -o $Env:APPDATA\\vcluster\\vcluster.exe; $env:Path += \";\" + $Env:APPDATA + \"\\vcluster\"; [Environment]::SetEnvironmentVariable(\"Path\", $env:Path, [System.EnvironmentVariableTarget]::User); && (New-Object Net.WebClient).DownloadFile(\"https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe\", \"$env:Temp\\GoogleCloudSDKInstaller.exe\")& $env:Temp\\GoogleCloudSDKInstaller.exe", 11 | "ohana-mac": "brew install helm && curl -s -L \"https://github.com/loft-sh/vcluster/releases/latest\" | sed -nE 's!.*\"([^\"]*vcluster-darwin-amd64)\".*!https://github.com\\1!p' | xargs -n 1 curl -L -o vcluster && chmod +x vcluster; sudo mv vcluster /usr/local/bin;", 12 | "ohana-m1": "brew install helm && curl -s -L \"https://github.com/loft-sh/vcluster/releases/latest\" | sed -nE 's!.*\"([^\"]*vcluster-darwin-arm64)\".*!https://github.com\\1!p' | xargs -n 1 curl -L -o vcluster && chmod +x vcluster; sudo mv vcluster /usr/local/bin;", 13 | "ohana-nix": "curl https://baltocdn.com/helm/signing.asc | sudo apt-key add - && sudo apt-get install apt-transport-https --yes echo \"deb https://baltocdn.com/helm/stable/debian/ all main\" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list && sudo apt-get update && sudo apt-get install helm && sudo apt-get update && sudo apt-get install google-cloud-sdk && curl -s -L \"https://github.com/loft-sh/vcluster/releases/latest\" | sed -nE 's!.*\"([^\"]*vcluster-linux-amd64)\".*!https://github.com\\1!p' | xargs -n 1 curl -L -o vcluster && chmod +x vcluster; sudo mv vcluster /usr/local/bin;" 14 | }, 15 | "keywords": [], 16 | "author": "klustr", 17 | "license": "ISC", 18 | "browser:": { 19 | "child_process": false 20 | }, 21 | "dependencies": { 22 | "@material-ui/core": "^4.11.4", 23 | "@material-ui/icons": "^4.11.2", 24 | "@material-ui/lab": "^4.0.0-alpha.58", 25 | "bcrypt": "^5.0.1", 26 | "child_process": "^1.0.2", 27 | "clsx": "^1.1.1", 28 | "cookie-parser": "^1.4.5", 29 | "express": "^4.17.1", 30 | "helm": "^0.2.1", 31 | "jsonwebtoken": "^8.5.1", 32 | "nodemon": "^2.0.7", 33 | "pg": "^8.6.0", 34 | "react": "^17.0.2", 35 | "react-dom": "^17.0.2", 36 | "react-router-dom": "^5.2.0", 37 | "react-virtualized-auto-sizer": "^1.0.5", 38 | "react-window": "^1.8.6" 39 | }, 40 | "devDependencies": { 41 | "@babel/cli": "^7.1.0", 42 | "@babel/core": "^7.1.0", 43 | "@babel/preset-env": "^7.1.0", 44 | "@babel/preset-react": "^7.0.0", 45 | "babel-loader": "^8.2.2", 46 | "concurrently": "^6.2.0", 47 | "css-loader": "^5.2.4", 48 | "eslint": "^7.28.0", 49 | "jest": "^27.0.6", 50 | "prettier": "^2.3.2", 51 | "react-router-dom": "^5.2.0", 52 | "style-loader": "^2.0.0", 53 | "supertest": "^6.1.3", 54 | "webpack": "^5.37.0", 55 | "webpack-cli": "^4.7.0", 56 | "webpack-dev-server": "^3.11.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /server/controllers/clusterController.js: -------------------------------------------------------------------------------- 1 | const db = require("../db/models"); 2 | 3 | const clusterController = {}; 4 | 5 | clusterController.addCluster = (req, res, next) => { 6 | const { clusterName } = req.body; 7 | const query = ` 8 | INSERT INTO clusters (name) 9 | VALUES ('${clusterName}') 10 | `; 11 | db.query(query) 12 | .then(() => next()) 13 | .catch((err) => 14 | next({ 15 | log: `Error in clusterController.addClusters: ${err}`, 16 | message: "Error adding new cluster", 17 | }) 18 | ); 19 | }; 20 | 21 | clusterController.getClusters = (req, res, next) => { 22 | const query = "SELECT name FROM clusters;"; 23 | db.query(query).then((data) => { 24 | res.locals.clusterNames = data.rows; 25 | return next(); 26 | }); 27 | }; 28 | 29 | module.exports = clusterController; 30 | -------------------------------------------------------------------------------- /server/controllers/spacesController.js: -------------------------------------------------------------------------------- 1 | const db = require("../db/models"); 2 | const { 3 | runTerminalCommand, 4 | kubectl, 5 | gcloud, 6 | } = require("../../terminalCommands.js"); 7 | const { reset } = require("nodemon"); 8 | const spacesController = {}; 9 | 10 | spacesController.clusterIdLookup = (req, res, next) => { 11 | const { hostCluster } = req.body; 12 | const params = [hostCluster]; 13 | const query = `SELECT _id FROM clusters WHERE name=$1`; 14 | db.query(query, params) 15 | .then((data) => { 16 | console.log(data.rows[0]._id); 17 | res.locals.clusterId = data.rows[0]._id; 18 | return next(); 19 | }) 20 | .catch((err) => { 21 | return next({ 22 | log: `Error in spacesController.clusterIdLookup: ${err}`, 23 | message: `Error looking up cluster id`, 24 | }); 25 | }); 26 | }; 27 | 28 | spacesController.addNamespace = (req, res, next) => { 29 | const { hostNamespace } = req.body; 30 | const { clusterId, teamId } = res.locals; 31 | const params = [hostNamespace, clusterId, teamId]; 32 | // if the team ID is a foreign key, do we need to add this in or is it automatic? 33 | const query = 34 | "INSERT INTO namespaces (name, cluster_id, team_id) VALUES ($1, $2, $3)"; 35 | 36 | db.query(query, params) 37 | .then((data) => { 38 | console.log(data); 39 | return next(); 40 | }) 41 | .catch((err) => { 42 | return next({ log: `Error in spacesController.addNamespace: ${err}` }); 43 | }); 44 | }; 45 | 46 | spacesController.createNamespace = (req, res, next) => { 47 | console.log(req.body); 48 | const { hostCluster, hostNamespace } = req.body; 49 | runTerminalCommand(gcloud.getCredentials(hostCluster)).then((data) => { 50 | console.log(data); 51 | runTerminalCommand(kubectl.createNamespace(hostNamespace)).then((data) => { 52 | console.log(data); 53 | return next(); 54 | }); 55 | }); 56 | }; 57 | 58 | spacesController.deploy = (req, res, next) => { 59 | const { deploymentName, deployHostNamespace, imageFile } = req.body; 60 | runTerminalCommand( 61 | kubectl.deployImage(deploymentName, deployHostNamespace, imageFile) 62 | ).then(() => { 63 | runTerminalCommand(kubectl.expose(deploymentName, deployHostNamespace)) 64 | .then(() => 65 | runTerminalCommand( 66 | `kubectl get services -n ${deployHostNamespace} ${deploymentName}` 67 | ) 68 | ) 69 | .then((data) => { 70 | console.log(data); 71 | res.locals.jeff = data; 72 | return next(); 73 | }); 74 | }); 75 | }; 76 | 77 | spacesController.getExternalIp = (req, res, next) => { 78 | const { deploymentName, deployHostNamespace } = req.body; 79 | console.log(deploymentName); 80 | runTerminalCommand( 81 | kubectl.exposedIP(deploymentName, deployHostNamespace) 82 | ).then((data) => { 83 | res.locals.getServices = data; 84 | return next(); 85 | }); 86 | }; 87 | 88 | spacesController.fetchSpaces = (req, res, next) => { 89 | const query = "SELECT * FROM namespaces;"; 90 | db.query(query).then((data) => { 91 | res.locals.spaces = data.rows; 92 | return next(); 93 | }); 94 | }; 95 | 96 | spacesController.fetchNamespaces = (req, res, next) => { 97 | const { teamId } = res.locals; 98 | const params = [teamId]; 99 | const query = ` 100 | SELECT name FROM namespaces WHERE team_id = $1; 101 | `; 102 | db.query(query, params).then((data) => { 103 | res.locals.namespaces = data.rows; 104 | return next(); 105 | }); 106 | }; 107 | 108 | module.exports = spacesController; 109 | -------------------------------------------------------------------------------- /server/controllers/teamsController.js: -------------------------------------------------------------------------------- 1 | const db = require("../db/models"); 2 | 3 | const teamsController = {}; 4 | 5 | teamsController.createTeam = (req, res, next) => { 6 | const { teamName } = req.body; 7 | const query = `INSERT INTO teams (name) VALUES ('${teamName}')`; 8 | db.query(query) 9 | .then(() => next()) 10 | .catch((err) => { 11 | return next({ 12 | log: `Error in teamsController.createTeam: ${err}`, 13 | message: `Unable to add new team`, 14 | }); 15 | }); 16 | }; 17 | 18 | teamsController.fetchTeams = (req, res, next) => { 19 | const query = ` 20 | SELECT name FROM teams 21 | `; 22 | db.query(query) 23 | .then((data) => { 24 | res.locals.teamNames = data.rows; 25 | return next(); 26 | }) 27 | .catch((err) => { 28 | return next({ 29 | log: `Error in fetching teams: ${err}`, 30 | message: `Unable to fetch teams`, 31 | }); 32 | }); 33 | }; 34 | 35 | module.exports = teamsController; 36 | -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const db = require("../db/models"); 2 | const bcrypt = require("bcrypt"); 3 | const saltRounds = 10; 4 | const jwt = require("jsonwebtoken"); 5 | const secret = "ohana"; 6 | 7 | const userController = {}; 8 | 9 | userController.bcryptPassword = (req, res, next) => { 10 | const { password } = req.body; 11 | bcrypt 12 | .hash(password, saltRounds) 13 | .then((hash) => { 14 | res.locals.password = hash; 15 | return next(); 16 | }) 17 | .catch((err) => next({ log: `Error in userController.bcrypt: ${err}` })); 18 | }; 19 | 20 | userController.teamIdLookup = (req, res, next) => { 21 | const { teamName } = req.body; 22 | const query = `SELECT _id FROM teams WHERE name='${teamName}'`; 23 | db.query(query) 24 | .then((data) => { 25 | res.locals.teamId = data.rows[0]._id; 26 | return next(); 27 | }) 28 | .catch((err) => { 29 | return next({ 30 | log: `Error in userController.teamIdLookup: ${err}`, 31 | message: `Please enter valid team name`, 32 | }); 33 | }); 34 | }; 35 | userController.addNewUser = (req, res, next) => { 36 | const { teamId, password } = res.locals; 37 | const { email, firstName, lastName, isAdmin } = req.body; 38 | const params = [email, password, firstName, lastName, isAdmin, teamId]; 39 | const query = ` 40 | INSERT INTO users(email, password, first_name, last_name, is_admin, team_id) 41 | VALUES ($1, $2, $3, $4, $5, $6);`; 42 | db.query(query, params) 43 | .then(() => next()) 44 | .catch((err) => { 45 | return next({ log: `Error in userController.addNewUser: ${err}` }); 46 | }); 47 | }; 48 | userController.loginCheck = (req, res, next) => { 49 | const { email, password } = req.body; 50 | const query = ` 51 | SELECT password 52 | FROM users 53 | WHERE email = '${email}'`; 54 | db.query(query) 55 | .then((result) => { 56 | if (!result.rows.length) { 57 | res.locals.user = false; 58 | return next({ 59 | log: "Incorrect username/password", 60 | message: "Incorrect username/password", 61 | }); 62 | } 63 | bcrypt.compare(password, result.rows[0].password, (err, result) => { 64 | if (err) 65 | return next({ log: `Error in userController.loginCheck: ${err}` }); 66 | if (!result) 67 | return next({ 68 | log: "Incorrect username/password", 69 | message: "Incorrect username/password", 70 | }); 71 | return next(); 72 | }); 73 | }) 74 | .catch((err) => 75 | next({ 76 | log: `Error in userController.loginCheck: ${err}`, 77 | message: "Incorrect username/password", 78 | }) 79 | ); 80 | }; 81 | 82 | userController.isAdminCheck = (req, res, next) => { 83 | const { email } = req.body; 84 | const params = [email]; 85 | const query = ` 86 | SELECT is_admin, first_name, last_name 87 | FROM users 88 | WHERE email=$1; 89 | `; 90 | db.query(query, params) 91 | .then((result) => { 92 | const firstName = result.rows[0].first_name; 93 | const lastName = result.rows[0].last_name; 94 | const isAdminResult = result.rows[0].is_admin; 95 | if (!isAdminResult) isAdminResult = false; 96 | res.locals.isAdminResult = isAdminResult; 97 | res.locals.firstName = firstName; 98 | res.locals.lastName = lastName; 99 | return next(); 100 | }) 101 | .catch((err) => 102 | next({ log: `Error in userController.isAdminCheck: ${err}` }) 103 | ); 104 | }; 105 | 106 | userController.teamId = (req, res, next) => { 107 | const { email } = req.body; 108 | const params = [email]; 109 | const query = ` 110 | SELECT team_id 111 | FROM users 112 | WHERE email=$1; 113 | `; 114 | db.query(query, params) 115 | .then((result) => { 116 | let teamId = result.rows[0].team_id; 117 | res.locals.teamId = teamId; 118 | return next(); 119 | }) 120 | .catch((err) => next({ log: `Error in userController.teamId: ${err}` })); 121 | }; 122 | 123 | userController.assignJwt = (req, res, next) => { 124 | const { isAdminResult, teamId, firstName, lastName } = res.locals; 125 | const { email } = req.body; 126 | jwt.sign( 127 | { email, teamId, isAdmin: isAdminResult, firstName, lastName }, 128 | secret, 129 | (err, token) => { 130 | if (err) 131 | return next({ log: `Error in userController.assignJwt: ${err}` }); 132 | res.locals.token = token; 133 | return next(); 134 | } 135 | ); 136 | }; 137 | 138 | userController.verifyAdmin = (req, res, next) => { 139 | const { AuthToken } = req.cookies; 140 | jwt.verify(AuthToken, secret, (err, decoded) => { 141 | if (err) 142 | return next({ log: `Error in userController.verifyAdmin: ${err}` }); 143 | res.locals.firstName = decoded.firstName; 144 | res.locals.lastName = decoded.lastName; 145 | res.locals.isAdmin = decoded.isAdmin; 146 | res.locals.teamId = decoded.teamId; 147 | return next(); 148 | }); 149 | }; 150 | 151 | userController.deleteUser = (req, res, next) => { 152 | const { email } = req.body; 153 | const params = [email]; 154 | const query = `DELETE FROM users WHERE email=$1`; 155 | db.query(query, params) 156 | .then(() => next()) 157 | .catch((err) => 158 | next({ 159 | log: `Error in userController.deleteUser: ${err}`, 160 | message: "Unable to delete user", 161 | }) 162 | ); 163 | }; 164 | 165 | userController.getAllUsers = (req, res, next) => { 166 | const query = "SELECT * FROM users"; 167 | db.query(query) 168 | .then((result) => { 169 | res.locals.allUsers = result.rows; 170 | return next(); 171 | }) 172 | .catch((err) => 173 | next({ 174 | log: `Error in userController.getAllUsers: ${err}`, 175 | message: "Unable to get users", 176 | }) 177 | ); 178 | }; 179 | 180 | module.exports = userController; 181 | -------------------------------------------------------------------------------- /server/controllers/vClusterController.js: -------------------------------------------------------------------------------- 1 | const db = require("../db/models"); 2 | const { 3 | runTerminalCommand, 4 | vCluster, 5 | gcloud, 6 | } = require("../../terminalCommands.js"); 7 | const vClusterController = {}; 8 | 9 | vClusterController.addVCluster = (req, res, next) => { 10 | const { vClusterName } = req.body; 11 | const params = [vClusterName]; 12 | const query = ` 13 | INSERT INTO vclusters(name) 14 | VALUES ($1)`; 15 | db.query(query, params) 16 | .then(() => { 17 | return next(); 18 | }) 19 | .catch((err) => { 20 | return next({ log: `Error in clusterController.addVCluster: ${err}` }); 21 | }); 22 | }; 23 | 24 | // clusterController.deleteClusterFromDB = (req, res, next) => { 25 | // const { hostNamespace, vClusterName, projectName } = req.body; 26 | // const params = [hostNamespace, vClusterName, projectName]; 27 | // const query = ` 28 | // INSERT INTO vclusters3(team_id, namespace_id, project) 29 | // VALUES ($1, $2, $3)` 30 | 31 | // db.query(query, params) 32 | // .then(() => { 33 | // return next(); 34 | // }) 35 | // .catch((err) => { 36 | // return next({ log: `Error in clusterController.addCluster: ${err}` }); 37 | // }) 38 | // } 39 | 40 | vClusterController.createVCluster = (req, res, next) => { 41 | const { clusterName, vClusterName, hostNamespace } = req.body; 42 | res.locals.vClusterName = vClusterName; 43 | runTerminalCommand(gcloud.getCredentials(clusterName)) 44 | .then((data) => { 45 | runTerminalCommand(vCluster.create(vClusterName, hostNamespace)) 46 | .then(() => next()) 47 | .catch((err) => console.log(err)); 48 | }) 49 | .catch((err) => next({ log: `clusterController.createCluster: ${err}` })); 50 | }; 51 | 52 | vClusterController.fetchVClusters = (req, res, next) => { 53 | const query = ` 54 | SELECT * FROM vclusters; 55 | `; 56 | db.query(query).then((data) => { 57 | res.locals.kyung = data.rows; 58 | return next(); 59 | }); 60 | }; 61 | 62 | vClusterController.fetchNamespaces = (req, res, next) => { 63 | const query = ` 64 | SELECT name FROM namespaces; 65 | `; 66 | db.query(query).then((data) => { 67 | res.locals.clusternamespaces = data.rows; 68 | return next(); 69 | }); 70 | }; 71 | 72 | vClusterController.fetchClusters = (req, res, next) => { 73 | const query = ` 74 | SELECT name FROM clusters; 75 | `; 76 | db.query(query).then((data) => { 77 | res.locals.clusterclusters = data.rows; 78 | return next(); 79 | }); 80 | }; 81 | 82 | module.exports = vClusterController; 83 | -------------------------------------------------------------------------------- /server/db/models.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require("pg"); 2 | const myURI = 3 | "postgres://uoxhbipl:h1t_kf6PCEbJ5Zdv2thayRY6y008Jf7R@kashin.db.elephantsql.com/uoxhbipl"; 4 | 5 | const URI = process.env.PG_URI || myURI; 6 | 7 | const pool = new Pool({ 8 | connectionString: URI, 9 | }); 10 | 11 | module.exports = { 12 | query: (text, params, callback) => { 13 | return pool.query(text, params, callback); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /server/routers/clusterRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const clusterController = require('../controllers/clusterController.js'); 4 | 5 | router.post('/', 6 | clusterController.addCluster, 7 | // clusterController.createCluster, 8 | (req, res) => { 9 | const { vClusterName } = res.locals; 10 | return res.status(200).json(`Successfully created ${vClusterName} on cluster-1!`); 11 | } 12 | ) 13 | 14 | // router.get('/vclusters', 15 | // clusterController.fetchClusters, 16 | // (req, res) => res.status(200).json(res.locals.kyung)); 17 | 18 | 19 | router.get('/list', 20 | clusterController.getClusters, 21 | (req, res) => { 22 | const { clusterNames } = res.locals; 23 | console.log('clusterNames', clusterNames) 24 | return res.status(200).json(clusterNames) 25 | }) 26 | module.exports = router; -------------------------------------------------------------------------------- /server/routers/spacesRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const spacesController = require("../controllers/spacesController"); 4 | const userController = require("../controllers/userController"); 5 | 6 | router.post( 7 | "/create", 8 | spacesController.clusterIdLookup, 9 | userController.verifyAdmin, 10 | spacesController.addNamespace, 11 | spacesController.createNamespace, 12 | (req, res) => { 13 | res.status(200).json("Successfully created namespace"); 14 | } 15 | ); 16 | 17 | router.post("/deploy", spacesController.deploy, (req, res) => { 18 | const { jeff } = res.locals; 19 | res.status(200).json(jeff); 20 | }); 21 | 22 | router.post("/getip", spacesController.getExternalIp, (req, res) => { 23 | const { getServices } = res.locals; 24 | res.status(200).json(getServices); 25 | }); 26 | 27 | router.get("/fetch", spacesController.fetchSpaces, (req, res) => { 28 | res.status(200).json(res.locals.spaces); 29 | }); 30 | 31 | router.get( 32 | "/fetchspaces", 33 | userController.verifyAdmin, 34 | spacesController.fetchNamespaces, 35 | (req, res) => { 36 | res.status(200).json(res.locals.namespaces); 37 | } 38 | ); 39 | 40 | // router.get('/fetchclusters', 41 | // spacesController.fetchClusters, 42 | // (req, res) => { 43 | // res.status(200).json(res.locals.clusters) 44 | // }); 45 | 46 | module.exports = router; 47 | -------------------------------------------------------------------------------- /server/routers/teamsRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const teamsController = require('../controllers/teamsController'); 4 | 5 | router.post('/', teamsController.createTeam, (req, res) => { 6 | return res.status(200).json('Successfully created team on database'); 7 | }) 8 | 9 | router.get('/fetch', teamsController.fetchTeams, (req, res) => { 10 | teamNames = res.locals.teamNames 11 | return res.status(200).json(teamNames) 12 | }) 13 | 14 | module.exports = router; -------------------------------------------------------------------------------- /server/routers/userRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const userController = require("../controllers/userController"); 4 | 5 | router.post( 6 | "/create", 7 | userController.bcryptPassword, 8 | userController.teamIdLookup, 9 | userController.addNewUser, 10 | (req, res) => res.status(200).json("Successfully added new user") 11 | ); 12 | 13 | router.post( 14 | "/login", 15 | userController.loginCheck, 16 | userController.isAdminCheck, 17 | userController.teamId, 18 | userController.assignJwt, 19 | (req, res) => { 20 | const { token } = res.locals; 21 | res.cookie("AuthToken", token, { maxAge: 900000, httpOnly: true }); 22 | res.status(200).json(token); 23 | } 24 | ); 25 | 26 | router.post("/verify", userController.verifyAdmin, (req, res) => { 27 | const { isAdmin, teamId, firstName, lastName } = res.locals; 28 | const userInfo = { isAdmin, teamId, firstName, lastName }; 29 | res.status(200).json(userInfo); 30 | }); 31 | 32 | router.delete("/", userController.deleteUser, (req, res) => { 33 | res.status(200).json("Successfully deleted user."); 34 | }); 35 | 36 | router.get("/", userController.getAllUsers, (req, res) => { 37 | const { allUsers } = res.locals; 38 | res.status(200).json(allUsers); 39 | }); 40 | 41 | module.exports = router; 42 | -------------------------------------------------------------------------------- /server/routers/vClusterRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const vClusterController = require("../controllers/vClusterController"); 4 | 5 | router.post( 6 | "/create", 7 | vClusterController.addVCluster, 8 | vClusterController.createVCluster, 9 | (req, res) => { 10 | const { vClusterName } = res.locals; 11 | res.status(200).json(`Successfully created ${vClusterName} on cluster-1!`); 12 | } 13 | ); 14 | 15 | router.get("/", vClusterController.fetchVClusters, (req, res) => { 16 | res.status(200).json(res.locals.kyung); 17 | }); 18 | 19 | router.get( 20 | "/fetchnamespaces", 21 | vClusterController.fetchNamespaces, 22 | (req, res) => { 23 | res.status(200).json(res.locals.clusternamespaces); 24 | } 25 | ); 26 | 27 | router.get("/fetchclusters", vClusterController.fetchClusters, (req, res) => { 28 | res.status(200).json(res.locals.clusterclusters); 29 | }); 30 | 31 | module.exports = router; 32 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | const path = require("path"); 4 | const userRouter = require("./routers/userRouter"); 5 | const spacesRouter = require("./routers/spacesRouter"); 6 | const teamsRouter = require("./routers/teamsRouter"); 7 | const vClusterRouter = require("./routers/vClusterRouter"); 8 | const userController = require("./controllers/userController"); 9 | const clusterRouter = require("./routers/clusterRouter"); 10 | const cookieParser = require("cookie-parser"); 11 | 12 | app.use(express.json()); 13 | app.use(express.urlencoded({ extended: true })); 14 | app.use(cookieParser()); 15 | 16 | app.get("/cookies", userController.verifyAdmin, (req, res) => { 17 | const { isAdmin, teamId, firstName, lastName } = res.locals; 18 | return res 19 | .status(200) 20 | .json({ isAdmin, isLoggedIn: true, teamId, firstName, lastName }); 21 | }); 22 | 23 | app.get("/admin", userController.verifyAdmin, (req, res) => { 24 | const { isAdmin } = res.locals; 25 | console.log(isAdmin); 26 | if (isAdmin === undefined) return res.redirect("/"); 27 | if (isAdmin === true) 28 | return res.status(200).sendFile(path.resolve(__dirname, "../index.html")); 29 | if (isAdmin === false) return res.redirect("/vcluster"); 30 | }); 31 | 32 | app.use("/user", userRouter); 33 | app.use("/spaces", spacesRouter); 34 | app.use("/vclusters", vClusterRouter); 35 | app.use("/teams", teamsRouter); 36 | app.use("/clusters", clusterRouter); 37 | 38 | app.get("*", (req, res) => { 39 | const { AuthToken } = req.cookies; 40 | if (AuthToken === undefined) return res.redirect("/"); 41 | return res.status(200).sendFile(path.resolve(__dirname, "../index.html")); 42 | }); 43 | 44 | app.use((err, req, res, next) => { 45 | const defaultErr = { 46 | log: "Express error handler caught unknown middleware error", 47 | message: "An error occurred", 48 | }; 49 | const errorObj = Object.assign({}, defaultErr, err); 50 | console.log(errorObj.log); 51 | return res.status(500).json(errorObj.message); 52 | }); 53 | 54 | // app.listen(3000, () => { 55 | // console.log('Listening on port 3000...'); 56 | // }); 57 | 58 | module.exports = app; 59 | -------------------------------------------------------------------------------- /server/start.js: -------------------------------------------------------------------------------- 1 | const app = require("./server"); 2 | 3 | app.listen(3000, () => console.log("Express listening on port 3000")); 4 | -------------------------------------------------------------------------------- /terminalCommands.js: -------------------------------------------------------------------------------- 1 | const { exec } = require("child_process"); 2 | const { hostname } = require("os"); 3 | 4 | const runTerminalCommand = (command) => { 5 | return new Promise((resolve, reject) => { 6 | exec(command, (error, stdout, stderr) => { 7 | if (error) { 8 | console.warn(error); 9 | } 10 | resolve(stdout ? stdout : stderr); 11 | }); 12 | }); 13 | }; 14 | 15 | // can delete this 16 | // // gcloud variables 17 | // let clusterName = 'klustr-jefftest'; 18 | // let gcloudRegion = 'us-west1'; 19 | // let numNodes = '2'; 20 | // let gcloudUserEmail = 'contact.jeffchen@gmail.com'; 21 | // // kubctl variables 22 | // let imageFile = 'docker.io/klustr/watchr' 23 | // // imageFile only for watchr purposes 24 | // let deploymentName = 'klustr-deployment' 25 | // // changed to hostNameSpace instead of namespace 26 | // let hostNamespace = 'kiosk'; 27 | // let configFile = '/yamlConfigs/account.yaml'; 28 | // let userConfigFile = '/yamlConfigs/userAccount.yaml'; 29 | // let podName = 'klustr-deployment-786bd87dd4-pvrk6'; 30 | // let userName = 'john'; 31 | // let portIn = '80'; 32 | // let portOut = '8080'; 33 | // let space = 'johns-space'; 34 | // let vClusterName = 'testing'; 35 | 36 | // gcloud terminal commands 37 | const gcloud = {}; 38 | // necessary to create a cluster if it doesn't already exist; be aware of regional resource availability 39 | gcloud.create = (clusterName, numNodes, gcloudRegion) => 40 | `gcloud container clusters create ${clusterName} --num-nodes=${numNodes} --region=${gcloudRegion}`; 41 | // 'gcloud container clusters get-credentials : 42 | gcloud.getCredentials = (clusterName) => 43 | `gcloud container clusters get-credentials ${clusterName} --region=us-west1-a`; 44 | // 'gcloud config set account ' 45 | gcloud.switchAccount = (gcloudUserEmail) => 46 | `gcloud config set account ${gcloudUserEmail}`; 47 | 48 | const kubectl = {}; 49 | 50 | kubectl.currentContext = () => `kubectl config current-context`; 51 | // executes command to get the current context 52 | kubectl.createNamespace = (hostNamespace) => 53 | `kubectl create namespace ${hostNamespace}`; 54 | // can create spaces, accounts, configurations, namespaces etc based on the config file passed in 55 | kubectl.createFromConfig = (configFile) => `kubectl apply -f ${configFile}`; 56 | // can create spaces, accounts, configurations, namespaces, roles, etc based on the config file passed in impersonating a user; admin only 57 | kubectl.createFromConfigAs = (configFile, email) => 58 | `kubectl apply -f /Users/fenris/Desktop/Codesmith/klustr.dev/yamlConfigs/userAccount.yaml --as=dev-account`; 59 | // get additional detail on pods 60 | kubectl.describe = (hostNamespace) => 61 | `kubectl describe pods -n ${hostNamespace}`; 62 | // kubectl create deployment --image= 63 | kubectl.deployImage = (deploymentName, hostNamespace, imageFile) => 64 | `kubectl create deployment ${deploymentName} -n ${hostNamespace} --image=${imageFile}`; 65 | // expose the deployment for kubernetes 66 | kubectl.expose = (deploymentName, hostNamespace) => 67 | `kubectl expose deployment ${deploymentName} -n ${hostNamespace} --type LoadBalancer --port=80 --target-port=8080`; 68 | // deploy the pod in a specific namespace with the image configuration 69 | kubectl.deploy = (hostNamespace, configFile) => 70 | `kubectl apply -n ${hostNamespace} -f /Users/fenris/Desktop/Codesmith/klustr.dev/yamlConfigs/${configFile}.yaml`; 71 | // deploy the pod in a specific namespace with the image configuration impersonating a user; admin only 72 | kubectl.deployAs = (hostNamespace, configFile, userName) => 73 | `kubectl apply -n ${hostNamespace} -f /yamlConfigs/${configFile}.yaml --as=${userName}`; 74 | // grabs the external exposed IP address 75 | kubectl.exposedIP = (deploymentName, deployedNamespace) => 76 | `kubectl get services ${deploymentName} -n ${deployedNamespace} -o jsonpath="{.status.loadBalancer.ingress[0].ip}"`; 77 | 78 | const vCluster = {}; 79 | 80 | vCluster.create = (vClusterName, hostNamespace) => 81 | `vcluster create ${vClusterName} -n ${hostNamespace}`; 82 | vCluster.connect = (vClusterName, hostNamespace) => 83 | `vcluster connect ${vClusterName} -n ${hostNamespace} \export KUBECONFIG=./kubeconfig.yaml`; 84 | vCluster.delete = (vClusterName, hostNamespace) => 85 | `vcluster delete ${vClusterName} -n ${hostNamespace}`; 86 | 87 | // can delete this 88 | // const serviceAccount = {} 89 | // serviceAccount.user = (email) => `USER_NAME="${email}" \ kubectl -n kiosk create serviceaccount "${email}"` 90 | 91 | //fs write file? 92 | 93 | // serviceAccount.userConfig = (email) => { 94 | // `USER_NAME="${email}" KUBECONFIG_PATH="$HOME/.kube/config-kiosk" && 95 | // kubectl config view --minify --raw > "$HOME/.kube/config-kiosk" && 96 | // export KUBECONFIG="$HOME/.kube/config-kiosk"` 97 | // CURRENT_CONTEXT=$(kubectl config current-context) \ 98 | // kubectl config rename-context $CURRENT_CONTEXT kiosk-admin \ 99 | // CLUSTER_NAME=$(kubectl config view -o jsonpath="{.clusters[].name}") \ 100 | // ADMIN_USER=$(kubectl config view -o jsonpath="{.users[].name}") \ 101 | // SA_NAME=$(kubectl -n kiosk get serviceaccount ${email} -o jsonpath="{.secrets[0].name}") \ 102 | // SA_TOKEN=$(kubectl -n kiosk get secret $SA_NAME -o jsonpath="{.data.token}" | base64 -d) \ 103 | // kubectl config set-credentials ${email} --token=$SA_TOKEN \ 104 | // kubectl config set-context kiosk-user --cluster=$CLUSTER_NAME --user=${email} \ 105 | // kubectl config use-context kiosk-user 106 | // } 107 | 108 | // removed serviceAccount 109 | module.exports = { 110 | kubectl, 111 | gcloud, 112 | vCluster, 113 | runTerminalCommand, 114 | }; 115 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./client/index.js", 6 | output: { 7 | path: path.resolve(__dirname, 'build'), 8 | filename: 'bundle.js' 9 | }, 10 | mode: "development", 11 | module: { 12 | rules: [ 13 | { 14 | test: /.(js|jsx)$/, 15 | exclude: /(node_modules|bower_components)/, 16 | use: { 17 | loader: 'babel-loader', 18 | options: { 19 | presets: ['@babel/preset-env', '@babel/preset-react'] 20 | } 21 | } 22 | }, 23 | { 24 | test: /\.css$/, 25 | exclude: /node_modules/, 26 | use: ["style-loader", "css-loader"] 27 | }, 28 | { 29 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 30 | type: 'asset/resource', 31 | }, 32 | ], 33 | }, 34 | // resolve: { extensions: ["*", ".js", ".jsx"] }, 35 | devServer: { 36 | host: '0.0.0.0', 37 | publicPath: '/build/', 38 | port: 8080, 39 | proxy: { 40 | '/user': 'http://localhost:3000', 41 | '/admin': 'http://localhost:3000', 42 | '/clusters': 'http://localhost:3000', 43 | '/spaces': 'http://localhost:3000', 44 | '/vcluster': 'http://localhost:3000', 45 | '/cookies': 'http://localhost:3000', 46 | '/teams': 'http://localhost:3000', 47 | '/home': 'http://localhost:3000', 48 | '/team': 'http://localhost:3000', 49 | '/users': 'http://localhost:3000', 50 | '/deploy': 'http://localhost:3000', 51 | } 52 | }, 53 | plugins: [new webpack.HotModuleReplacementPlugin()] 54 | }; 55 | 56 | // /\.(js|jsx)$/ -------------------------------------------------------------------------------- /yamlConfigs/deployImage.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: watchr 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: watchr 9 | replicas: 2 # tells deployment to run 2 pods matching the template 10 | template: 11 | metadata: 12 | labels: 13 | app: watchr 14 | spec: 15 | containers: 16 | - name: watchr 17 | image: docker.io/klustr/watchr 18 | ports: 19 | - containerPort: 80 -------------------------------------------------------------------------------- /yamlConfigs/rbac-creator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: kiosk-role-binding 5 | subjects: 6 | - kind: User 7 | name: dev1 8 | apiGroup: rbac.authorization.k8s.io 9 | - kind: User 10 | name: dba 11 | apiGroup: rbac.authorization.k8s.io 12 | roleRef: 13 | kind: ClusterRole 14 | name: kiosk-edit 15 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /yamlConfigs/spaceConfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tenancy.kiosk.sh/v1alpha1 2 | kind: Space 3 | metadata: 4 | name: dev1-space 5 | spec: 6 | account: dev-account 7 | subjects: 8 | - kind: User 9 | name: dev1 10 | apiGroup: rbac.authorization.k8s.io 11 | -------------------------------------------------------------------------------- /yamlConfigs/spaceLimitConfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tenancy.kiosk.sh/v1alpha1 2 | kind: Account 3 | metadata: 4 | name: dev-account 5 | spec: 6 | space: 7 | limit: 2 8 | subjects: 9 | - kind: User 10 | name: dev1 11 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /yamlConfigs/userAccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tenancy.kiosk.sh/v1alpha1 2 | kind: Account 3 | metadata: 4 | name: dev-account 5 | # spec: 6 | # space: 7 | # limit: 2 8 | spec: 9 | subjects: 10 | - kind: User 11 | name: dev-account 12 | # resources: ['get', ] 13 | # need to replace name with the user email 14 | apiGroup: rbac.authorization.k8s.io -------------------------------------------------------------------------------- /yamlConfigs/vCluster.yaml: -------------------------------------------------------------------------------- 1 | vcluster: 2 | image: rancher/k3s:v1.19.5-k3s2 3 | extraArgs: 4 | - --service-cidr=10.96.0.0/12 5 | baseArgs: 6 | - server 7 | - --write-kubeconfig=/k3s-config/kube-config.yaml 8 | - --data-dir=/data 9 | - --no-deploy=traefik,servicelb,metrics-server,local-storage 10 | - --disable-network-policy 11 | - --disable-agent 12 | - --disable-scheduler 13 | - --disable-cloud-controller 14 | - --flannel-backend=none 15 | - --kube-controller-manager-arg=controllers=*,-nodeipam,-nodelifecycle,-persistentvolume-binder,-attachdetach,-persistentvolume-expander,-cloud-node-lifecycle 16 | storage: 17 | size: 5Gi -------------------------------------------------------------------------------- /yamlConfigs/viewUserAccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: tenancy.kiosk.sh/v1alpha1 2 | kind: Account 3 | metadata: 4 | name: dev-account 5 | # spec: 6 | # space: 7 | # limit: 2 8 | spec: 9 | subjects: 10 | - kind: User 11 | name: dev1 12 | # need to replace name with the user email 13 | apiGroup: rbac.authorization.k8s.io 14 | -------------------------------------------------------------------------------- /yamlConfigs/yamlGenerator.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/ohana/dd06c6f1c5daf786fe8424a9a1c05a814e99fd00/yamlConfigs/yamlGenerator.js --------------------------------------------------------------------------------