├── .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 |
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()
37 | );
38 |
39 | const namespaceDropdown = [];
40 | namespaceNames.forEach((name) =>
41 | namespaceDropdown.push()
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 |
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()
27 | );
28 |
29 | const namespaceDropdown = [];
30 | namespaceNames.forEach((name) =>
31 | namespaceDropdown.push()
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 |
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 |
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 |
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()
26 | );
27 |
28 | const namespaceDropdown = [];
29 | namespaceNames.forEach((name) =>
30 | namespaceDropdown.push()
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 |
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 |
178 |
179 |
180 | Active Namespaces
181 |
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 |
125 |
126 |
133 |
141 |
142 |
Aloha, welcome to Ohana
143 |
144 |
145 |
146 |
Effortless K8s Management
147 |
148 |
149 |
150 |
151 |
152 |
153 |
159 |
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 |
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 |
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 |
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 |
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
--------------------------------------------------------------------------------