├── .github
├── build.png
├── containers.png
├── dockureIconLogoV2.png
├── images.png
├── signin.png
└── yamlEditor.png
├── .gitignore
├── README.md
├── babel.config.js
├── client
├── App.js
├── asset
│ ├── ball.svg
│ ├── dockureIcon.svg
│ ├── dockureIconLogo.svg
│ ├── dockureIconLogoV.svg
│ ├── dockureIconLogoV2.svg
│ ├── dockureIconLogoVW.svg
│ ├── dockureIconLogoW.svg
│ ├── dockureIconV.svg
│ ├── dockureIconV2.svg
│ ├── dockureIconV3.svg
│ ├── dockureIconW1.svg
│ ├── dockureIconW2.svg
│ ├── dockureIconW3.svg
│ ├── dockureLogo.svg
│ ├── dockureLogoB.svg
│ ├── dockureLogoTitle.svg
│ └── dockureLogoW.svg
├── components
│ ├── containerComponents
│ │ ├── containerItem.js
│ │ ├── containerItems.test.js
│ │ ├── containerList.js
│ │ └── dockerCommand.js
│ ├── imageComponents
│ │ ├── createImage.js
│ │ ├── dockerBuild.js
│ │ ├── editor.js
│ │ ├── imageItem.js
│ │ ├── imageItemDeleteBtn.js
│ │ ├── imageList.js
│ │ └── pullImage.js
│ ├── loader.js
│ ├── nav.js
│ ├── selectors
│ │ ├── stats.selector.js
│ │ └── time.selector.js
│ ├── statComponents
│ │ ├── graph.js
│ │ └── statsContainer.js
│ ├── titlebar.js
│ └── userComponents
│ │ ├── login.js
│ │ ├── signUp.js
│ │ └── userStatus.js
├── containers
│ ├── contentContainer.js
│ ├── imageContainer.js
│ ├── mainContainer.js
│ ├── protectedRoute.js
│ └── unProtectedRoute.js
├── db
│ ├── token.js
│ └── user.js
├── index.js
├── listButton.js
├── redux
│ ├── action
│ │ ├── action.js
│ │ └── actionTypes.js
│ └── reducers
│ │ ├── containerReducer.js
│ │ └── index.js
├── scss
│ ├── application.scss
│ ├── containerList.scss
│ ├── createImage.scss
│ ├── dockerCommand.scss
│ ├── images.scss
│ ├── login.scss
│ ├── mainContainer.scss
│ ├── nav.scss
│ ├── stats.scss
│ ├── titlebar.scss
│ ├── userStatus.scss
│ └── variables.scss
├── services
│ ├── axiosService.js
│ ├── containerService.js
│ ├── containerService.test.js
│ ├── imageService.js
│ ├── prometheusService.js
│ ├── userDbService.js
│ └── utilities.js
└── store.js
├── electron
└── main.js
├── index.html
├── package.json
├── server
├── assets
│ └── prometheus.yaml
├── config.js
├── controllers
│ ├── authentication.js
│ ├── cadvisorStart.js
│ ├── dContainer.js
│ ├── dImage.js
│ ├── isAuth.js
│ ├── metricQueries.js
│ ├── nodeExporter.js
│ ├── promMetrics.js
│ ├── timeConversion.js
│ ├── userController.js
│ └── ymlParser.js
├── database
│ ├── dbConnect.js
│ └── setup.sql
├── routers
│ ├── dContainer.js
│ ├── dImage.js
│ ├── promMetrics.js
│ └── user.js
└── server.js
├── tsconfig.json
├── webpack.config.js
├── yarn-error.log
└── yarn.lock
/.github/build.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dockure/a7bee2cd86829dada9abd51789d066904ce46f57/.github/build.png
--------------------------------------------------------------------------------
/.github/containers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dockure/a7bee2cd86829dada9abd51789d066904ce46f57/.github/containers.png
--------------------------------------------------------------------------------
/.github/dockureIconLogoV2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dockure/a7bee2cd86829dada9abd51789d066904ce46f57/.github/dockureIconLogoV2.png
--------------------------------------------------------------------------------
/.github/images.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dockure/a7bee2cd86829dada9abd51789d066904ce46f57/.github/images.png
--------------------------------------------------------------------------------
/.github/signin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dockure/a7bee2cd86829dada9abd51789d066904ce46f57/.github/signin.png
--------------------------------------------------------------------------------
/.github/yamlEditor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/dockure/a7bee2cd86829dada9abd51789d066904ce46f57/.github/yamlEditor.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | package-lock.json
4 | .env
5 | promConfigFile.yaml
6 | data-out.yaml
7 | yarn.lock
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![Contributors][contributors-shield]][contributors-url]
2 | [![Forks][forks-shield]][forks-url]
3 | [![Stargazers][stars-shield]][stars-url]
4 | [![Issues][issues-shield]][issues-url]
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Simplifying the containerization process outside of the command line.
19 |
20 | Explore the docs »
21 |
22 |
23 | View Demo
24 | ·
25 | Report Bug
26 | ·
27 | Request Feature
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | Table of Contents
36 |
37 | -
38 | About The Project
39 |
42 |
43 | -
44 | Getting Started
45 |
49 |
50 | - Usage
51 | - Roadmap
52 | - Contributing
53 | - License
54 | - Contact
55 | - Acknowledgements
56 |
57 |
58 |
59 |
60 |
61 |
62 | ## About The Project
63 |
64 | ### Built With
65 | * [React](https://reactjs.org/docs/getting-started.html)
66 | * [Redux](https://redux.js.org/)
67 | * [Express](https://expressjs.com/)
68 | * [Docker](https://docs.docker.com/)
69 | * [Docker REST API](https://docs.docker.com/engine/api/v1.41/#)
70 | * [Node child_process](https://nodejs.org/api/child_process.html)
71 | * [Electron](https://www.electronjs.org/docs)
72 | * [Prometheus](https://prometheus.io/docs/introduction/overview/)
73 | * [CAdvisor](https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md)
74 | * [Socat (in the localhost)]()
75 |
76 |
77 | ## Getting Started
78 |
79 | Starting our app is super easy! Just make sure you have Yarn and Docker if you don't already.
80 |
81 | ### Prerequisites
82 | In order for the application to work:
83 | 1. Must have [Docker Desktop](https://www.docker.com/products/docker-desktop) or Docker Daemon running in the background.
84 | 2. Must install yarn:
85 | ```sh
86 | npm install yarn
87 | ```
88 | * For security purposes, please make sure you use this app in your local network unless you provide your own TCP TLS/SSH security. Pre-packaged SSH capabilities are currently in beta!
89 | ### Installation
90 | The pre-bundled app will be coming in Dockure 2.0, but for now:
91 | 1. Clone the repo
92 | 2. install dependencies
93 | ```sh
94 | yarn install
95 | ```
96 | 3. create an .env file in the root directory filling in your personal DB_host and JWT secret like below:
97 | ```
98 | # Database
99 | DB_HOST=your.db.here
100 | # Bcrypt
101 | BCRYPT_SALT_ROUNDS=10
102 | # JWT
103 | JWT_SECRET=your.secret.here
104 | JWT_EXPIRES_SEC=86400
105 | ```
106 | ```
107 | |-- .github (folder)
108 | |-- client (folder)
109 | |-- electron (folder)
110 | |-- server (folder)
111 |
112 | |-- .env (file) <----- right here!
113 | |-- index.html
114 | |-- etcetra...
115 | ```
116 | 4. build the app
117 | ```sh
118 | yarn build
119 | ```
120 | 5. start it
121 | ```sh
122 | yarn start
123 | ```
124 | * After logging in for the first time, it may take some time for dependencies to load.
125 |
126 | 
127 |
128 |
129 |
130 | Alternatively, you can skip steps 4-5 and run this application in dev mode outside of electron:
131 | ```sh
132 | yarn dev
133 | ```
134 |
135 |
136 |
137 | ## Usage
138 | Once you are logged in there are loads you can do. Here are some examples:
139 |
140 | * Our simple homepage displays containers and their data. You can view container data and/or select multiple containers you'd like to start and stop
141 | 
142 |
143 | * In our images tab, you can run your images to build containers. You can also pull locally or on Docker Hub and build images.
144 | 
145 |
146 | 
147 |
148 |
149 | * In our YAML/Dockerfile editor tab, we provide a simple Dockerfile or YAML editor for you to create, edit and save your own files without opening up an IDE.
150 | 
151 |
152 |
153 | ## Roadmap
154 |
155 | See the [open issues](https://github.com/oslabs-beta/dockure/issues) for a list of proposed features (and known issues).
156 |
157 |
158 |
159 |
160 | ## Contributing
161 |
162 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
163 |
164 | 1. Fork the Project
165 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
166 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
167 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
168 | 5. Open a Pull Request
169 |
170 |
171 | ## License
172 |
173 | Distributed under the MIT License. See `LICENSE` for more information.
174 |
175 |
176 | ## Contact
177 |
178 |
179 | * Liam - [LinkedIn](https://www.linkedin.com/in/liamtalty/), [Github](https://github.com/lptalty)
180 | * Van - [LinkedIn](https://www.linkedin.com/in/van-biet-nguyen/), [Github](https://github.com/vanbietnguyen)
181 | * Alex - [LinkedIn](https://www.linkedin.com/in/alexander-zayas-jr/), [Github](https://github.com/AlexZayas)
182 | * Hazel - [LinkedIn](https://www.linkedin.com/in/hyeseon-na/), [Github](https://github.com/hazel0109)
183 | * Nate - [LinkedIn](https://Linkedin.com/in/nathanael-tracy/), [Github](https://github.com/n-tracy1)
184 |
185 |
186 | ## Acknowledgements
187 | * [Yarn](https://classic.yarnpkg.com/en/docs/)
188 | * [Webpack](https://webpack.js.org/)
189 | * [CodeMirror](https://codemirror.net/doc/manual.html)
190 | * [BCrypt](https://www.npmjs.com/package/bcrypt)
191 | * [PostGreSQL](https://www.postgresql.org/docs/)
192 |
193 |
194 |
195 | [contributors-shield]: https://img.shields.io/github/contributors/oslabs-beta/dockure.svg?style=for-the-badge
196 | [contributors-url]: https://github.com/oslabs-beta/dockure/graphs/contributors
197 | [forks-shield]: https://img.shields.io/github/forks/oslabs-beta/dockure.svg?style=for-the-badge
198 | [forks-url]: https://github.com/oslabs-beta/dockure/network/members
199 | [stars-shield]: https://img.shields.io/github/stars/oslabs-beta/dockure.svg?style=for-the-badge
200 | [stars-url]: https://github.com/oslabs-beta/dockure/stargazers
201 | [issues-shield]: https://img.shields.io/github/issues/oslabs-beta/dockure.svg?style=for-the-badge
202 | [issues-url]: https://github.com/oslabs-beta/dockure/issues
203 |
204 |
206 |
209 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['@babel/plugin-transform-async-to-generator'],
3 | presets: [
4 | '@babel/preset-env',
5 | '@babel/preset-react',
6 | '@babel/preset-typescript',
7 | ],
8 | env: {
9 | test: {
10 | plugins: ['@babel/plugin-transform-runtime'],
11 | },
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/client/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MainContainer from './containers/mainContainer';
3 | import Login from './components/userComponents/login';
4 | import SignUP from './components/userComponents/signUp';
5 | import { HashRouter as Router, Switch } from 'react-router-dom';
6 | import ProtectedRoute from './containers/protectedRoute';
7 | import UnProtectedRoute from './containers/unProtectedRoute';
8 |
9 | const App = () => {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default App;
22 |
--------------------------------------------------------------------------------
/client/asset/ball.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/asset/dockureIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
91 |
--------------------------------------------------------------------------------
/client/asset/dockureIconLogo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
145 |
--------------------------------------------------------------------------------
/client/asset/dockureIconLogoV.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
141 |
--------------------------------------------------------------------------------
/client/asset/dockureIconLogoV2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
141 |
--------------------------------------------------------------------------------
/client/asset/dockureIconLogoVW.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
141 |
--------------------------------------------------------------------------------
/client/asset/dockureIconV.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
--------------------------------------------------------------------------------
/client/asset/dockureIconV2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
91 |
--------------------------------------------------------------------------------
/client/asset/dockureIconV3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
91 |
--------------------------------------------------------------------------------
/client/asset/dockureIconW1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
91 |
--------------------------------------------------------------------------------
/client/asset/dockureIconW2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
91 |
--------------------------------------------------------------------------------
/client/asset/dockureIconW3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
95 |
--------------------------------------------------------------------------------
/client/asset/dockureLogo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
61 |
--------------------------------------------------------------------------------
/client/asset/dockureLogoB.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
63 |
--------------------------------------------------------------------------------
/client/asset/dockureLogoTitle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
61 |
--------------------------------------------------------------------------------
/client/asset/dockureLogoW.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
61 |
--------------------------------------------------------------------------------
/client/components/containerComponents/containerItem.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import moment from 'moment';
3 |
4 | const ContainerItem = ({
5 | container,
6 | getData,
7 | onCheckboxClickCallback,
8 | isChecked,
9 | }) => {
10 | const [defaultCon, setDefaultCon] = useState(false);
11 | const utc = new Date(0);
12 | const date = utc.setUTCSeconds(container.Created);
13 |
14 | useEffect(() => {
15 | setDefaultCon(() => {
16 | const name = container.Names[0].slice(1);
17 | if (name === 'cadvisor' || name === 'prometheus' || name === 'socat') {
18 | return true;
19 | }
20 | });
21 | }, [container]);
22 |
23 | return (
24 |
25 |
26 |
27 |
onCheckboxClickCallback(e.target.value)}
33 | />
34 |
{container.Names[0].slice(1)}
35 |
36 |
{moment(date).fromNow()}
37 |
38 |
39 |
40 | {container.State}
41 |
42 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default ContainerItem;
51 |
52 | function containerStatus(state) {
53 | switch (state) {
54 | case 'running':
55 | return 'is_running';
56 | case 'exited':
57 | return 'is_exited';
58 | default:
59 | return 'is_else';
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/client/components/containerComponents/containerItems.test.js:
--------------------------------------------------------------------------------
1 | import { render, fireEvent, waitFor, screen } from '@testing-library/react';
2 | import React from 'react';
3 | import '@testing-library/jest-dom';
4 | import ContainerItem from './containerItem';
5 |
6 | describe('Container Items', () => {
7 | const props = {
8 | id: 1001,
9 | container: {
10 | Id: 1001,
11 | Names: ['0test'],
12 | State: 'running',
13 | Created: 1367854155,
14 | },
15 | getData: () => {},
16 | onCheckboxClickCallback: () => {},
17 | conStatus: true,
18 | isChecked: false,
19 | };
20 |
21 | describe('render the component', () => {
22 | it('should have a button', () => {
23 | render();
24 | const button = screen.getByText(/get data/i);
25 | expect(button).toBeInTheDocument();
26 | });
27 | it('should render the container name only the first word', () => {
28 | render();
29 | const name = screen.getByText('test');
30 | expect(name).toBeInTheDocument();
31 | });
32 | it('should render how long is the container since it is created', () => {
33 | render();
34 | const time = screen.getByText(/ago/i);
35 | expect(time).toBeInTheDocument();
36 | });
37 | });
38 |
39 | describe('assign class name based on the container state', () => {
40 | it('should have class name is_running', () => {
41 | render();
42 | const state = screen.getByText('running');
43 | expect(state).toHaveClass('is_running');
44 | });
45 | it('should have class name is_existed', () => {
46 | const props2 = {
47 | ...props,
48 | container: {
49 | ...props.container,
50 | State: 'exited',
51 | },
52 | };
53 | render();
54 | const state = screen.getByText('exited');
55 | expect(state).toHaveClass('is_exited');
56 | });
57 | it('should have class name is_else', () => {
58 | const props3 = {
59 | ...props,
60 | container: {
61 | ...props.container,
62 | State: 'created',
63 | },
64 | };
65 | render();
66 | const state = screen.getByText('created');
67 | expect(state).toHaveClass('is_else');
68 | });
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/client/components/containerComponents/containerList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ContainerItem from './containerItem';
3 | import containerService from '../../services/containerService';
4 | import { useDispatch } from 'react-redux';
5 | import { setStateMetrics } from '../../redux/action/action.js';
6 | import { useSelector } from 'react-redux';
7 | import { timeSelector } from '../selectors/time.selector';
8 | import Loader from '../loader';
9 |
10 | const ContainerList = ({ conList, onCheckboxClickCallback, selectedIds }) => {
11 | const { time } = useSelector(timeSelector);
12 | const dispatch = useDispatch();
13 | const getData = async (id, containerState) => {
14 | let stats = {
15 | cpu: [],
16 | memory: [],
17 | };
18 | if (containerState === 'running')
19 | stats = await containerService.getMetrics(id, time);
20 | dispatch(setStateMetrics(stats));
21 | };
22 |
23 | if (!conList) return null;
24 |
25 | if (!conList.length) return ;
26 |
27 | const con = conList.map((container, inx) => {
28 | const isChecked = !!selectedIds[container.Id];
29 | return (
30 | getData(container.Id, container.State)}
34 | container={container}
35 | isChecked={isChecked}
36 | />
37 | );
38 | });
39 |
40 | return ;
41 | };
42 |
43 | export default ContainerList;
44 |
--------------------------------------------------------------------------------
/client/components/containerComponents/dockerCommand.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback } from 'react';
2 | import ContainerList from './containerList';
3 | import { Link } from 'react-router-dom';
4 | import ContainerService from '../../services/containerService';
5 | import { throttle } from '../../services/utilities';
6 |
7 | const DockerCommand = ({
8 | conList,
9 | toggle,
10 | callConStatus,
11 | loading,
12 | setLoading,
13 | error,
14 | }) => {
15 | const [selectedIds, setSelectedIds] = useState({});
16 | const onCheckboxClickCallback = (id) => {
17 | let newSelectedIds = { ...selectedIds };
18 | if (selectedIds[id]) {
19 | delete newSelectedIds[id];
20 | setSelectedIds(newSelectedIds);
21 | return;
22 | }
23 | newSelectedIds[id] = true;
24 | setSelectedIds(newSelectedIds);
25 | };
26 |
27 | const updateContainerStatuses = (ids, command) => {
28 | setLoading(true);
29 | const promiseArr = [];
30 | for (let id in ids) {
31 | let result = ContainerService.postClickBtn(command, id);
32 | promiseArr.push(result);
33 | }
34 | Promise.all(promiseArr).then((values) => {
35 | setSelectedIds({});
36 | callConStatus();
37 | });
38 | };
39 |
40 | const updateContainerStatusesThrottle = useCallback(
41 | throttle(updateContainerStatuses, 5000),
42 | []
43 | );
44 |
45 | const onClickButton = (command) => {
46 | updateContainerStatusesThrottle(selectedIds, command);
47 | };
48 |
49 | return (
50 |
55 |
56 |
57 |
63 |
69 |
75 |
81 |
87 |
93 |
99 |
100 |
101 |
104 |
105 |
106 | {error && (
107 |
Cannot get the docker containers. Please reopen the app
108 | )}
109 | {loading &&
Please wait...
}
110 |
111 |
116 |
117 | );
118 | };
119 | export default DockerCommand;
120 |
--------------------------------------------------------------------------------
/client/components/imageComponents/createImage.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Editor from './editor.js';
3 | import imageService from '../../services/imageService';
4 |
5 | const CreateImage = ({ toggle }) => {
6 | const [yaml, setYaml] = useState(imageService.yamlBoiler());
7 | const [dockerfile, setDockerfile] = useState(imageService.dockerBoiler());
8 |
9 | return (
10 |
15 |
22 |
23 |
30 |
31 | );
32 | };
33 |
34 | export default CreateImage;
35 |
--------------------------------------------------------------------------------
/client/components/imageComponents/dockerBuild.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import imageService from '../../services/imageService';
3 |
4 | const DockerBuild = ({ updateImage, setUpdateImage }) => {
5 | const defaultPath = '~/';
6 | const [dockerPath, setDockerPath] = useState(defaultPath);
7 | const [imageName, setImageName] = useState('');
8 |
9 | const handleBuild = async (e) => {
10 | e.preventDefault();
11 | let result = await imageService.buildImage({
12 | imageName: imageName,
13 | path: dockerPath,
14 | });
15 | setUpdateImage(!updateImage);
16 | };
17 |
18 | return (
19 |
36 | );
37 | };
38 |
39 | export default DockerBuild;
40 |
--------------------------------------------------------------------------------
/client/components/imageComponents/editor.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import CodeMirror from 'codemirror/lib/codemirror.js';
3 | import 'codemirror/mode/dockerfile/dockerfile';
4 | import 'codemirror/mode/yaml/yaml';
5 | import 'codemirror/lib/codemirror.css';
6 | import 'codemirror/theme/nord.css';
7 | import { Controlled as ControlledEditor } from 'react-codemirror2';
8 | import { saveAs } from 'file-saver';
9 |
10 | const Editor = (props) => {
11 | const { language, displayName, value, onChange, saveType } = props;
12 |
13 | const [open, setOpen] = useState(true);
14 |
15 | const handleChange = (editor, data, value) => {
16 | onChange(value);
17 | };
18 |
19 | const save = async () => {
20 | const file = await new Blob([value], { type: `text/${displayName}` });
21 | saveAs(file, `${saveType}`);
22 | };
23 |
24 | return (
25 | <>
26 |
27 |
28 | {' '}
29 | {`Edit your ${displayName}`}
30 |
36 |
37 |
38 |
50 |
51 |
52 | {`Save your ${displayName}`}
53 |
54 |
55 | >
56 | );
57 | };
58 |
59 | export default Editor;
60 |
--------------------------------------------------------------------------------
/client/components/imageComponents/imageItem.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import imageService from '../../services/imageService';
3 | import { useHistory } from 'react-router-dom';
4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5 | import { faEllipsisV } from '@fortawesome/free-solid-svg-icons';
6 | import ImageItemDeleteBtn from './imageItemDeleteBtn';
7 |
8 | const ImageItem = ({ image, setUpdateImage, updateImage }) => {
9 | const [isRunning, setIsRunning] = useState();
10 | const [optClick, setOptClick] = useState(false);
11 | let history = useHistory();
12 |
13 | const checkRepoTag = ({ image }) => {
14 | if (image.RepoTags) {
15 | if (image.RepoTags[0] === ':') {
16 | image.RepoTags = ['Anonymous'];
17 | }
18 | return image.RepoTags[0];
19 | } else if (image.RepoDigests) {
20 | const nameArr = image.RepoDigests[0].split('@');
21 | return nameArr[0];
22 | } else {
23 | return 'Anonymous';
24 | }
25 | };
26 |
27 | const startClick = async (e) => {
28 | const ID = { image }.image.Id.slice(7, 19);
29 | const handleSubmit = await imageService.startImage(ID);
30 | if (handleSubmit.data === 'running') {
31 | setIsRunning(true);
32 | history.push('/main');
33 | }
34 | };
35 |
36 | const deleteClick = async (e) => {
37 | const ID = { image }.image.Id.slice(7, 19);
38 | setOptClick(false);
39 | await imageService.deleteImage(ID);
40 | setUpdateImage(!updateImage);
41 | };
42 |
43 | const optHandler = () => {
44 | setOptClick(!optClick);
45 | };
46 |
47 | return (
48 |
49 |
50 |
51 |
Image Name
52 |
53 |
54 |
55 | {optClick &&
}
56 |
57 |
{checkRepoTag({ image })}
58 |
59 | {isRunning ? (
60 |
In Use
61 | ) : (
62 |
65 | )}
66 |
67 |
68 | );
69 | };
70 |
71 | export default ImageItem;
72 |
--------------------------------------------------------------------------------
/client/components/imageComponents/imageItemDeleteBtn.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const ImageItemDeleteBtn = ({ deleteClick }) => {
4 | return (
5 |
6 |
9 |
10 | );
11 | };
12 | export default ImageItemDeleteBtn;
13 |
--------------------------------------------------------------------------------
/client/components/imageComponents/imageList.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import ImageItem from './imageItem';
3 | import PullImage from './pullImage';
4 | import DockerBuild from './dockerBuild';
5 | import Loader from '../loader';
6 |
7 | const ImageList = ({ imageList, updateImage, setUpdateImage }) => {
8 | const [dockerAction, setDockerAction] = useState(true);
9 |
10 | const handleChange = (e) => {
11 | if (e.target.value === 'Build') return setDockerAction(false);
12 | else return setDockerAction(true);
13 | };
14 |
15 | if (!imageList) return null;
16 |
17 | if (!imageList.length) return ;
18 |
19 | const image = imageList.map((image, inx) => {
20 | return (
21 |
28 | );
29 | });
30 |
31 | return (
32 |
33 |
34 |
38 | {dockerAction ? (
39 |
43 | ) : (
44 |
48 | )}
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default ImageList;
56 |
--------------------------------------------------------------------------------
/client/components/imageComponents/pullImage.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import imageService from '../../services/imageService';
3 |
4 | const PullImage = ({ updateImage, setUpdateImage }) => {
5 | const [imageName, setImageName] = useState('');
6 |
7 | const handlePull = async () => {
8 | await imageService.pullImageInfo(imageName);
9 | setUpdateImage(!updateImage);
10 | };
11 |
12 | const onSubmit = () => {
13 | const input = document.querySelector('.image_input');
14 | setImageName('');
15 | handlePull();
16 | input.focus();
17 | };
18 |
19 | const searchSubmit = () => {
20 | window.open(`https://hub.docker.com/search?q=${imageName}&type=image`);
21 | };
22 |
23 | return (
24 |
25 |
setImageName(e.target.value)}
31 | />
32 |
35 |
or
36 |
39 |
40 | );
41 | };
42 |
43 | export default PullImage;
44 |
--------------------------------------------------------------------------------
/client/components/loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ball from '../asset/ball.svg';
3 |
4 | const Loader = () => {
5 | return
;
6 | };
7 |
8 | export default Loader;
9 |
--------------------------------------------------------------------------------
/client/components/nav.js:
--------------------------------------------------------------------------------
1 | import React, { component } from 'react';
2 | import dockureIconV2 from '../asset/dockureIconLogoV2.svg';
3 |
4 | import {
5 | BrowserRouter as Router,
6 | Switch,
7 | Route,
8 | useRouteMatch,
9 | Link,
10 | Redirect,
11 | } from 'react-router-dom';
12 |
13 | const Nav = () => {
14 | let main = useRouteMatch();
15 | return (
16 |
17 |
18 | {/*
*/}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | export default Nav;
33 |
--------------------------------------------------------------------------------
/client/components/selectors/stats.selector.js:
--------------------------------------------------------------------------------
1 | export const getMetricsSelector = (state) => ({
2 | metrics: state.containers.metrics,
3 | })
--------------------------------------------------------------------------------
/client/components/selectors/time.selector.js:
--------------------------------------------------------------------------------
1 | export const timeSelector = (state) => ({
2 | time: state.containers.time,
3 | })
--------------------------------------------------------------------------------
/client/components/statComponents/graph.js:
--------------------------------------------------------------------------------
1 | import React, { component, useEffect, useState } from 'react';
2 | import {
3 | LineChart,
4 | Line,
5 | AreaChart,
6 | Area,
7 | CartesianGrid,
8 | XAxis,
9 | YAxis,
10 | Tooltip,
11 | ResponsiveContainer,
12 | BarChart,
13 | Bar,
14 | } from 'recharts';
15 |
16 | const Graph = ({ data, dataKey, dataType }) => {
17 | const CustomTooltip = ({ active, payload, label }) => {
18 | if (active) {
19 | let value;
20 | if (payload === null) return null;
21 | value = Object.values(payload[0].payload);
22 |
23 | return (
24 |
34 |
{`Time: ${label} value: ${
35 | value === undefined ? 0 : value[1].toFixed(5)
36 | }`}
37 |
38 | );
39 | } else return loading
;
40 | };
41 |
42 | return (
43 |
44 |
45 |
46 | {/* */}
47 |
48 |
49 |
50 |
51 |
52 | {/* */}
53 |
54 |
55 |
61 | `${number.toFixed(2)}%`}
65 | dataKey={dataKey}
66 | domain={['auto', 'auto']}
67 | style={{ fontSize: '.8rem' }}
68 | label={{
69 | value: `Percent of Total ${dataType} Used`,
70 | angle: -90,
71 | position: 'insideLeft',
72 | dy: 100,
73 | dx: -3,
74 | style: {
75 | fill: '#989898',
76 | fontSize: '1rem',
77 | },
78 | }}
79 | />
80 | } />
81 |
82 |
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | export default Graph;
95 |
--------------------------------------------------------------------------------
/client/components/statComponents/statsContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Graph from './graph';
3 |
4 | import { useDispatch } from 'react-redux';
5 | import { setTimeSelector } from '../../redux/action/action.js';
6 | import { useSelector } from 'react-redux';
7 | import { getMetricsSelector } from '../selectors/stats.selector';
8 |
9 | const Stats = () => {
10 | const { metrics } = useSelector(getMetricsSelector);
11 | const dispatch = useDispatch();
12 |
13 | return (
14 |
15 |
16 |
24 |
30 |
36 |
37 |
38 |
CPU Usage
39 |
44 |
Memory Usage
45 |
50 |
51 | );
52 | };
53 |
54 | export default Stats;
55 |
--------------------------------------------------------------------------------
/client/components/titlebar.js:
--------------------------------------------------------------------------------
1 | import React, { component, useEffect, useState } from 'react';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import UserStatus from './userComponents/userStatus';
4 | import dockureLogoTitle from '../asset/dockureLogoTitle.svg';
5 | import dockureIconW from '../asset/dockureIconW1.svg';
6 | import { faBars, faUser } from '@fortawesome/free-solid-svg-icons';
7 |
8 | const Titlebar = ({ toggle, setToggle, isLogin, setIsLogin, userName }) => {
9 | const [userStat, setUserStat] = useState(false);
10 |
11 | const toggleHandler = () => {
12 | setToggle(!toggle);
13 | };
14 |
15 | const userHandler = () => {
16 | setUserStat(!userStat);
17 | };
18 |
19 | useEffect(() => {
20 | const offUserStat = () => {
21 | setUserStat(false);
22 | };
23 | if (userStat) {
24 | document.body.addEventListener('click', offUserStat);
25 | }
26 | return () => {
27 | document.body.removeEventListener('click', offUserStat);
28 | };
29 | }, [userStat]);
30 |
31 | return (
32 | <>
33 | {isLogin ? (
34 |
35 |
36 | {/*
*/}
37 |
38 |
39 |
40 | {/*
*/}
41 |

42 |
50 |
51 |
52 | ) : (
53 |
54 |

55 |
56 | )}
57 | {userStat && (
58 |
63 | )}
64 | >
65 | );
66 | };
67 |
68 | export default Titlebar;
69 |
--------------------------------------------------------------------------------
/client/components/userComponents/login.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link, Redirect } from 'react-router-dom';
3 | import Titlebar from '../titlebar';
4 | import UserDbService from '../../services/userDbService';
5 | import TokenStorage from '../../db/token';
6 |
7 | const Login = () => {
8 | const [userData, setUserData] = useState({ username: '', password: '' });
9 | const [showError, setShowError] = useState(false);
10 | const [isAuthenticated, setIsAuthenticated] = useState(false);
11 |
12 | const userHandler = (e) => {
13 | setUserData((userData) => ({
14 | ...userData,
15 | [e.target.name]: e.target.value,
16 | }));
17 | };
18 |
19 | const postSignIn = async () => {
20 | const pwInput = document.querySelector('.signin_pw');
21 | const result = await UserDbService.postUserData(userData);
22 | if (result.id) {
23 | TokenStorage.saveToken(result.token);
24 | return setIsAuthenticated(true);
25 | }
26 | setUserData((userData) => ({
27 | ...userData,
28 | password: '',
29 | }));
30 | pwInput.focus();
31 | return setShowError(true);
32 | };
33 |
34 | const userSignIn = (e) => {
35 | e.preventDefault();
36 | if (!userData.username || !userData.password) {
37 | return setShowError(true);
38 | }
39 | postSignIn();
40 | };
41 |
42 | if (isAuthenticated) {
43 | return ;
44 | }
45 |
46 | return (
47 |
48 |
49 |
50 |
51 |
Welcome back!
52 |
You are almost in the promise land
53 |
54 |
55 |
56 |
57 |
58 |
Sign in to Dockure
59 |
83 |
84 |
85 |
86 | );
87 | };
88 |
89 | export default Login;
90 |
--------------------------------------------------------------------------------
/client/components/userComponents/signUp.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Titlebar from '../titlebar';
3 | import { Link, Redirect } from 'react-router-dom';
4 | import UserDbService from '../../services/userDbService';
5 |
6 | const SignUP = () => {
7 | const [userData, setUserData] = useState({
8 | username: '',
9 | email: '',
10 | password: '',
11 | });
12 | const [showUserError, setShowUserError] = useState(false);
13 | const [showPWError, setShowPWError] = useState(false);
14 | const [isSignUp, setIsSignUp] = useState(false);
15 |
16 | const userHandler = (e) => {
17 | setUserData((userData) => ({
18 | ...userData,
19 | [e.target.name]: e.target.value,
20 | }));
21 | };
22 |
23 | const sendUserData = async () => {
24 | const result = await UserDbService.postUserData(
25 | 'http://localhost:3000/api/user/signup',
26 | userData
27 | );
28 | };
29 |
30 | const signUpHandler = (e) => {
31 | if (!userData.username || !userData.email) {
32 | setShowUserError(true);
33 | setShowPWError(false);
34 | return;
35 | }
36 | if (userData.password.length < 5) {
37 | setShowUserError(false);
38 | setShowPWError(true);
39 | return;
40 | }
41 | sendUserData();
42 | setIsSignUp(true);
43 | };
44 |
45 | if (isSignUp) {
46 | return ;
47 | }
48 |
49 | //*******only when the sign up is successful, we are going to sign in link.******
50 |
51 | return (
52 |
53 |
54 |
55 |
56 |
Create Account
57 |
93 |
96 |
97 |
98 |
Hello, Friend!
99 |
Enter your personal details
100 |
and enjoy Dockure
101 |
102 |
103 |
104 |
105 |
106 |
107 | );
108 | };
109 |
110 | export default SignUP;
111 |
--------------------------------------------------------------------------------
/client/components/userComponents/userStatus.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Link } from 'react-router-dom';
3 | import UserDbService from '../../services/userDbService';
4 |
5 | const UserStatus = ({ userHandler, userName, setIsLogin }) => {
6 | const signOutHandler = () => {
7 | UserDbService.logout();
8 | setIsLogin(false);
9 | userHandler();
10 | };
11 |
12 | return (
13 |
14 |
15 |
Hello, {userName}
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default UserStatus;
29 |
--------------------------------------------------------------------------------
/client/containers/contentContainer.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useCallback } from 'react';
2 | import DockerCommand from '../components/containerComponents/dockerCommand';
3 | import StatsContainer from '../components/statComponents/statsContainer';
4 | import ContainerService from '../services/containerService';
5 |
6 | const ContentContainer = ({ toggle }) => {
7 | const [conList, setConList] = useState([]);
8 | const [error, setError] = useState(null);
9 | const [loading, setLoading] = useState(false);
10 |
11 | useEffect(() => {
12 | const setupCon = async () => {
13 | try {
14 | await ContainerService.setupCon();
15 | const clear = setTimeout(() => callConStatus(), 1000);
16 | return () => clearTimeout(clear);
17 | } catch (err) {
18 | setError(true);
19 | }
20 | };
21 | setupCon();
22 | }, []);
23 |
24 | const callConStatus = useCallback(async () => {
25 | try {
26 | const result = await ContainerService.getConInfo();
27 | setConList(result);
28 | setLoading(false);
29 | } catch (err) {
30 | setError(true);
31 | }
32 | }, []);
33 |
34 | return (
35 |
36 |
44 |
45 |
46 | );
47 | };
48 |
49 | export default ContentContainer;
50 |
--------------------------------------------------------------------------------
/client/containers/imageContainer.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import imageService from '../services/imageService';
3 | import ImageList from '../components/imageComponents/imageList';
4 |
5 | const ImageContainer = ({ toggle }) => {
6 | const [imageList, setImageList] = useState([]);
7 | const [updateImage, setUpdateImage] = useState(false);
8 |
9 | useEffect(async () => {
10 | const result = await imageService.getImageInfo();
11 | setImageList(result);
12 | }, [updateImage]);
13 |
14 | return (
15 |
20 |
25 |
26 | );
27 | };
28 |
29 | export default ImageContainer;
30 |
--------------------------------------------------------------------------------
/client/containers/mainContainer.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Nav from '../components/nav';
3 | import Titlebar from '../components/titlebar';
4 | import ContentContainer from './contentContainer';
5 | import ImageContainer from './imageContainer';
6 | import CreateImage from '../components/imageComponents/createImage';
7 | import UserDbService from '../services/userDbService';
8 | import ProtectedRoute from '../containers/protectedRoute';
9 |
10 | import {
11 | BrowserRouter as Router,
12 | Switch,
13 | useRouteMatch,
14 | } from 'react-router-dom';
15 |
16 | const MainContainer = (props) => {
17 | const [toggle, setToggle] = useState(true);
18 | const [isLogin, setIsLogin] = useState(false);
19 | const [userName, setUserName] = useState('');
20 | useEffect(() => {
21 | let result;
22 | const getUser = async () => {
23 | result = await UserDbService.getUserToken();
24 | if (result.token) {
25 | setIsLogin(true);
26 | setUserName(result.username);
27 | }
28 | };
29 | getUser();
30 | return () => {
31 | result = null;
32 | };
33 | }, []);
34 |
35 | let main = useRouteMatch();
36 | return (
37 |
38 |
45 |
46 |
47 | {toggle &&
}
48 |
49 |
55 |
61 |
67 |
68 |
69 |
70 |
71 | );
72 | };
73 | export default MainContainer;
74 |
--------------------------------------------------------------------------------
/client/containers/protectedRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect, Route } from 'react-router-dom';
3 | import decode from 'jwt-decode';
4 | import TokenStorage from '../db/token';
5 |
6 | const ProtectedRoute = ({ component: Component, ...rest }) => {
7 | const token = TokenStorage.getToken();
8 | const decodeToken = token ? decode(token) : false;
9 | const now = Math.floor(new Date().getTime() / 1000);
10 | const isNotExpired = decodeToken ? decodeToken.exp > now : false;
11 |
12 | return (
13 |
16 | isNotExpired ? :
17 | }
18 | />
19 | );
20 | };
21 |
22 | export default ProtectedRoute;
23 |
--------------------------------------------------------------------------------
/client/containers/unProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect, Route } from 'react-router-dom';
3 | import decode from 'jwt-decode';
4 | import TokenStorage from '../db/token';
5 |
6 | const UnProtectedRoute = ({ component: Component, ...rest }) => {
7 | const token = TokenStorage.getToken();
8 | const decodeToken = token ? decode(token) : false;
9 | const now = Math.floor(new Date().getTime() / 1000);
10 | const isNotExpired = decodeToken ? decodeToken.exp > now : false;
11 |
12 | return (
13 |
16 | isNotExpired ? :
17 | }
18 | />
19 | );
20 | };
21 |
22 | export default UnProtectedRoute;
23 |
--------------------------------------------------------------------------------
/client/db/token.js:
--------------------------------------------------------------------------------
1 | const TOKEN = 'token';
2 | class TokenStorage {
3 | static saveToken(token) {
4 | localStorage.setItem(TOKEN, token);
5 | }
6 |
7 | static getToken() {
8 | return localStorage.getItem(TOKEN);
9 | }
10 |
11 | static clearToken() {
12 | localStorage.clear(TOKEN);
13 | }
14 |
15 | s;
16 | }
17 |
18 | export default TokenStorage;
19 |
--------------------------------------------------------------------------------
/client/db/user.js:
--------------------------------------------------------------------------------
1 | const USER = 'user';
2 |
3 | export default class UserStorage {
4 | saveUser(user) {
5 | localStorage.setItem(USER, user);
6 | }
7 |
8 | getUser() {
9 | return localStorage.getItem(USER);
10 | }
11 |
12 | clearUser() {
13 | localStorage.clear(USER);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import App from './App';
5 | import store from './store';
6 | import styles from './scss/application.scss';
7 |
8 | // render(, document.getElementById('root'));
9 |
10 | // Once we make files with redux, we can change the code to below.
11 | render(
12 |
13 |
14 | ,
15 | document.getElementById('root')
16 | );
17 |
--------------------------------------------------------------------------------
/client/listButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Output = (props) => {
4 |
5 | return (
6 |
9 | );
10 | };
11 |
12 | export default Output;
--------------------------------------------------------------------------------
/client/redux/action/action.js:
--------------------------------------------------------------------------------
1 | import * as types from './actionTypes';
2 |
3 | export const getContainerInfo = (payload) => ({
4 | type: types.CONTAINER_INFO_GET,
5 | payload,
6 | });
7 |
8 | export const setStateMetrics = (payload) => ({
9 | type: types.SET_STATE_METRICS,
10 | payload,
11 | });
12 |
13 | export const setTimeSelector = (payload) => ({
14 | type: types.TIME_SELECTOR,
15 | payload,
16 | })
17 |
--------------------------------------------------------------------------------
/client/redux/action/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const CONTAINER_INFO_GET = 'CONTAINER_INFO_GET';
2 | export const SET_STATE_METRICS = 'SET_STATE_METRICS';
3 | export const TIME_SELECTOR = 'TIME_SELECTOR';
--------------------------------------------------------------------------------
/client/redux/reducers/containerReducer.js:
--------------------------------------------------------------------------------
1 | import * as types from '../action/actionTypes';
2 |
3 | const initialState = {
4 | containerList: [],
5 | metrics: [],
6 | time: 1
7 | };
8 |
9 | export const containerReducer = (state = initialState, action) => {
10 | switch (action.type) {
11 | case types.CONTAINER_INFO_GET: {
12 | return {
13 | ...state,
14 | containerList: action.payload,
15 | };
16 | }
17 | case types.SET_STATE_METRICS:
18 | const newMetrics = action.payload;
19 | return {
20 | ...state,
21 | metrics: newMetrics
22 | }
23 | case types.TIME_SELECTOR: {
24 | return {
25 | ...state,
26 | time: action.payload
27 | }
28 | }
29 | default: {
30 | return state;
31 | }
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/client/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { containerReducer } from './containerReducer';
3 |
4 | export const reducers = combineReducers({
5 | containers: containerReducer,
6 | });
7 |
--------------------------------------------------------------------------------
/client/scss/application.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 | @import 'mainContainer';
3 | @import 'nav';
4 | @import 'dockerCommand';
5 | @import 'stats';
6 | @import 'containerList';
7 | @import 'createImage';
8 | @import 'login';
9 | @import 'titlebar';
10 | @import 'images';
11 | @import 'userStatus';
12 |
--------------------------------------------------------------------------------
/client/scss/containerList.scss:
--------------------------------------------------------------------------------
1 | .container_list {
2 | background-color: $background-color;
3 | text-align: center;
4 | }
5 |
6 | .container_head {
7 | display: flex;
8 | justify-content: space-around;
9 | width: 100%;
10 | }
11 |
12 | .container_item {
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | height: 5.2rem;
17 | border: 1px solid darken($background-color, 10%);
18 | background-color: darken($background-color, 3%);
19 | border-radius: 10px;
20 | margin: 1rem 0;
21 | padding: 1rem 1.7rem 1rem 0.8rem;
22 | }
23 |
24 | .item_name_time {
25 | display: flex;
26 | width: 100%;
27 | justify-content: space-between;
28 | }
29 |
30 | .item_check {
31 | display: flex;
32 | align-items: center;
33 | }
34 |
35 | .item_checkbox {
36 | width: 1rem;
37 | height: 1rem;
38 | }
39 |
40 | .checkbox_invisible {
41 | visibility: hidden;
42 | }
43 |
44 | .item_name {
45 | font-weight: bold;
46 | margin-left: 0.8rem;
47 | font-size: $font-medium;
48 | }
49 |
50 | .item_createdat {
51 | font-weight: lighter;
52 | font-size: $font-small;
53 | }
54 |
55 | .item_state_dateBtn {
56 | display: flex;
57 | width: 100%;
58 | justify-content: space-between;
59 | align-items: center;
60 | padding: 0.8rem 0 0 0.5rem;
61 | }
62 |
63 | .item_state {
64 | height: 1.4rem;
65 | padding: 0.15rem 0.6rem;
66 | border-radius: 10px;
67 | font-size: $font-small;
68 | margin-left: 1.2rem;
69 | }
70 |
71 | .is_running {
72 | background-color: darken($background-color, 3%);
73 | border: 1px solid darken($green-color, 5%);
74 | color: darken($green-color, 5%);
75 | }
76 |
77 | .is_exited {
78 | background-color: darken($background-color, 3%);
79 | border: 1px solid lighten($orange-color, 4%);
80 | color: lighten($orange-color, 4%);
81 | }
82 |
83 | .is_else {
84 | background-color: darken($background-color, 3%);
85 | border: 1px solid lighten($accent-color, 3%);
86 | color: lighten($accent-color, 3%);
87 | }
88 |
89 | .item_dataBtn {
90 | padding: 0.25rem 0.65rem;
91 | color: $white-color;
92 | background-color: lighten($primary-color, 5%);
93 | border: 1px solid $primary-color;
94 | border-radius: 10px;
95 | font-size: $font-small;
96 | }
97 |
98 | .item_dataBtn:hover {
99 | background-color: saturate($primary-color, 10%);
100 | border: 1px solid lighten($primary-color, 5%);
101 | }
102 |
103 | .item_dataBtn:focus {
104 | background-color: saturate($primary-color, 10%);
105 | border: 1px solid lighten($primary-color, 5%);
106 | }
107 |
--------------------------------------------------------------------------------
/client/scss/createImage.scss:
--------------------------------------------------------------------------------
1 | .create_image {
2 | display: flex;
3 | justify-content: space-around;
4 | height: 95vh;
5 | background-color: $background-color;
6 | overflow-y: auto;
7 | }
8 |
9 | .create_toggle {
10 | width: 84vw;
11 | }
12 |
13 | .create_toggle_inactive {
14 | width: 100vw;
15 | }
16 |
17 | .editor_con {
18 | margin: 2rem 0;
19 | width: 28rem;
20 | display: flex;
21 | flex-direction: column;
22 | }
23 |
24 | .YAML.collapsed {
25 | height: 0;
26 | width: 0;
27 | }
28 |
29 | .Dockerfile.collapsed {
30 | height: 0;
31 | width: 0;
32 | }
33 |
34 | .editor_title {
35 | display: flex;
36 | justify-content: space-between;
37 | background-color: darken($accent-background-color, 4%);
38 | padding: 0.5rem 0.5rem 0.5rem 1rem;
39 | border-top-right-radius: 0.5rem;
40 | border-top-left-radius: 0.5rem;
41 | color: darken($font-color, 10%);
42 | }
43 |
44 | .editor_openBtn {
45 | background-color: darken($accent-background-color, 4%);
46 | color: darken($font-color, 10%);
47 | font-size: $font-small;
48 | padding: 3px;
49 | border: 1px solid darken($font-color, 50%);
50 | border-radius: 6px;
51 | }
52 | .editor_openBtn:hover {
53 | background-color: $primary-color;
54 | color: darken($font-color, 10%);
55 | border: 1px solid darken($primary-color, 10%);
56 | border-radius: 6px;
57 | }
58 | .editor_openBtn:focus {
59 | color: darken($font-color, 10%);
60 | border: 1px solid darken($font-color, 50%);
61 | border-radius: 6px;
62 | }
63 |
64 | .code-mirror-wrapper {
65 | // flex-grow: 1;
66 | overflow: hidden;
67 | }
68 |
69 | .editor_save {
70 | background-color: darken($accent-background-color, 4%);
71 | padding: 0.5rem 0.5rem 0.5rem 1rem;
72 | color: darken($font-color, 10%);
73 | border-bottom-right-radius: 0.5rem;
74 | border-bottom-left-radius: 0.5rem;
75 | }
76 |
--------------------------------------------------------------------------------
/client/scss/dockerCommand.scss:
--------------------------------------------------------------------------------
1 | .content_container {
2 | display: flex;
3 | }
4 | .docker_command {
5 | height: 95vh;
6 | padding: 1rem 1.5rem;
7 | background-color: $background-color;
8 | overflow-y: auto;
9 | }
10 |
11 | .content_toggle {
12 | width: 37vw;
13 | }
14 |
15 | .content_toggle_inactive {
16 | width: 53vw;
17 | }
18 |
19 | .docker_buttons {
20 | display: flex;
21 | align-items: center;
22 | height: 6vh;
23 | margin-bottom: 0.1rem;
24 | }
25 |
26 | .docker_btn {
27 | margin: 2px;
28 | padding: 0.25rem 0.5rem;
29 | font-size: $font-small;
30 | border-radius: 5px;
31 | }
32 |
33 | .docker_start {
34 | background-color: transparentize($green-color, 0.3%);
35 | color: $white-color;
36 | }
37 |
38 | .docker_start:hover {
39 | background-color: $green-color;
40 | color: $white-color;
41 | }
42 |
43 | .docker_start:focus {
44 | background-color: transparentize($green-color, 0.3%);
45 | color: $white-color;
46 | }
47 |
48 | .docker_redbtn {
49 | background-color: transparentize($orange-color, 0.3%);
50 | color: $white-color;
51 | }
52 |
53 | .docker_redbtn:hover {
54 | background-color: lighten($orange-color, 2%);
55 | color: $white-color;
56 | }
57 |
58 | .docker_redbtn:focus {
59 | background-color: transparentize($orange-color, 0.3%);
60 | color: $white-color;
61 | }
62 |
63 | .docker_commonbtn {
64 | background-color: lighten($background-color, 12%);
65 | color: $white-color;
66 | }
67 |
68 | .docker_commonbtn:hover {
69 | background-color: lighten($accent-color, 10%);
70 | color: $white-color;
71 | }
72 |
73 | .docker_commonbtn:focus {
74 | background-color: transparentize($accent-color, 0.5%);
75 | color: $white-color;
76 | }
77 |
78 | .add_btn {
79 | margin-bottom: 0.8rem;
80 | background-color: darken($accent-color, 20%);
81 | color: $white-color;
82 | }
83 |
84 | .add_btn:hover {
85 | background-color: lighten($accent-color, 10%);
86 | color: $white-color;
87 | }
88 |
89 | .add_btn:focus {
90 | background-color: darken($accent-color, 20%);
91 | color: $white-color;
92 | }
93 |
--------------------------------------------------------------------------------
/client/scss/images.scss:
--------------------------------------------------------------------------------
1 | .image_container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | background-color: $background-color;
6 | height: 95vh;
7 | padding: 2.5rem;
8 | overflow-y: auto;
9 | }
10 |
11 | .image_toggle {
12 | width: 84vw;
13 | }
14 |
15 | .image_toggle_inactive {
16 | width: 100vw;
17 | }
18 |
19 | .image_main {
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | }
24 |
25 | .image_select {
26 | display: flex;
27 | align-items: center;
28 | }
29 |
30 | .select_opt {
31 | margin-right: 0.6rem;
32 | border-radius: 5px;
33 | height: 1.9rem;
34 | width: 4.5rem;
35 | background-color: lighten($background-color, 12%);
36 | color: $font-color;
37 | outline: none;
38 | }
39 |
40 | .image_pull {
41 | display: flex;
42 | align-items: center;
43 | }
44 |
45 | .image_input {
46 | height: 2.1rem;
47 | width: 15rem;
48 | margin-right: 0.8rem;
49 | border: 1px solid lighten($background-color, 26%);
50 | border-radius: 5px;
51 | background-color: lighten($background-color, 12%);
52 | color: $font-color;
53 | }
54 |
55 | ::placeholder {
56 | color: $font-color;
57 | }
58 |
59 | .image_or {
60 | font-size: $font-small;
61 | margin: 0 0.5rem;
62 | }
63 |
64 | .image_submit {
65 | height: 2rem;
66 | padding: 0 0.5rem;
67 | background-color: lighten($primary-color, 5%);
68 | border: 1px solid lighten($primary-color, 5%);
69 | color: $white-color;
70 | border-radius: 5px;
71 | font-weight: bold;
72 | }
73 |
74 | .image_submit:hover {
75 | background-color: saturate($primary-color, 5%);
76 | border: 1px solid darken($primary-color, 30%);
77 | color: $white-color;
78 | }
79 |
80 | .image_submit:focus {
81 | background-color: lighten($primary-color, 5%);
82 | border: 1px solid darken($primary-color, 30%);
83 | color: $white-color;
84 | }
85 |
86 | .build_textbox {
87 | height: 2.1rem;
88 | width: 12rem;
89 | margin-right: 0.5rem;
90 | border: 1px solid lighten($background-color, 26%);
91 | border-radius: 5px;
92 | background-color: lighten($background-color, 12%);
93 | color: $font-color;
94 | }
95 |
96 | .image_list {
97 | display: flex;
98 | width: 35rem;
99 | flex-wrap: wrap;
100 | justify-content: space-between;
101 | margin: 1rem;
102 | }
103 |
104 | .image_item {
105 | display: flex;
106 | flex-direction: column;
107 | align-items: center;
108 | justify-content: center;
109 | height: 7rem;
110 | width: 17rem;
111 | padding: 10px;
112 | margin: 0.5rem 0;
113 | border: 1px solid darken($background-color, 10%);
114 | background-color: darken($background-color, 3%);
115 | border-radius: 5px;
116 | }
117 |
118 | .image_tagOpt {
119 | display: flex;
120 | justify-content: space-between;
121 | align-items: center;
122 | width: 100%;
123 | }
124 |
125 | .image_tag {
126 | margin-bottom: 0.3rem;
127 | font-weight: 500;
128 | color: lighten($accent-background-color, 60%);
129 | font-size: $font-small;
130 | }
131 |
132 | .image_opt {
133 | padding: 3px 4px;
134 | border: 1px solid darken($background-color, 3%);
135 | }
136 |
137 | .image_opt:hover {
138 | color: lighten($primary-color, 10%);
139 | border: 1px solid lighten($primary-color, 10%);
140 | border-radius: 5px;
141 | }
142 |
143 | .image_name {
144 | margin-bottom: 1rem;
145 | font-size: $font-medium;
146 | }
147 |
148 | .image_button {
149 | margin: 0.2rem 0.3rem;
150 | padding: 0.3rem 1.2rem;
151 | border-radius: 40px;
152 | font-size: $font-small;
153 | }
154 |
155 | .image_start {
156 | color: darken($background-color, 3%);
157 | background-color: transparentize($secondary-color, 0.1%);
158 | font-weight: bold;
159 | }
160 |
161 | .image_start:hover {
162 | background-color: saturate($secondary-color, 30%);
163 | box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.24);
164 | color: $white-color;
165 | font-weight: bold;
166 | transform: scale(1.03);
167 | }
168 |
169 | .image_running {
170 | width: 80px;
171 | height: 26px;
172 | margin: 0.2rem 0.3rem;
173 | padding-top: 2px;
174 | border-radius: 2rem;
175 | color: darken($green-color, 10%);
176 | background-color: lighten($green-color, 42%);
177 | border: 2px solid darken($green-color, 10%);
178 | text-align: center;
179 | font-size: $font-small;
180 | }
181 |
182 | .imagebtns {
183 | display: flex;
184 | }
185 |
186 | .opt_box {
187 | position: absolute;
188 | margin-left: 16rem;
189 | width: 4.5rem;
190 | height: 2.5rem;
191 | background-color: lighten($background-color, 10%);
192 | border: 1px solid lighten($background-color, 20%);
193 | border-radius: 5px;
194 | text-align: center;
195 | padding: 0.6rem 0;
196 | }
197 |
198 | .image_remove {
199 | // height: 1.5rem;
200 | color: $white-color;
201 | font-size: $font-small;
202 | background-color: lighten($background-color, 10%);
203 | }
204 |
205 | .image_remove:hover {
206 | // background-color: lighten($background-color, 50%);
207 | color: $orange-color;
208 | font-weight: bolder;
209 | transform: scale(1.1);
210 | }
211 |
--------------------------------------------------------------------------------
/client/scss/login.scss:
--------------------------------------------------------------------------------
1 | .login_page {
2 | display: flex;
3 | height: 95vh;
4 | }
5 |
6 | .login_wallpaper {
7 | display: flex;
8 | flex-direction: column;
9 | justify-content: center;
10 | align-items: center;
11 | width: 40vw;
12 | height: 100%;
13 | background-color: $accent-background-color;
14 | color: darken($font-color, 8%);
15 | }
16 |
17 | .login_head {
18 | margin-bottom: 1rem;
19 | font-size: $font-head;
20 | font-weight: bold;
21 | }
22 |
23 | .login_intro {
24 | font-size: $font-medium;
25 | margin: 0.2rem 0;
26 | }
27 |
28 | .login_wall_btn {
29 | margin-top: 4rem;
30 | font-size: $font-medium;
31 | width: 200px;
32 | height: 50px;
33 | background-color: $accent-background-color;
34 | color: $font-color;
35 | border-radius: 30px;
36 | border: 1px solid $font-color;
37 | }
38 |
39 | .login_wall_btn:hover {
40 | transform: scale(1.1);
41 | background-color: $primary-color;
42 | border: none;
43 | }
44 |
45 | .login_wall_btn:focus {
46 | transform: scale(1.1);
47 | background-color: $accent-background-color;
48 | border: none;
49 | }
50 |
51 | .signin_page {
52 | display: flex;
53 | flex-direction: column;
54 | justify-content: center;
55 | align-items: center;
56 | width: 60vw;
57 | height: 100%;
58 | background-color: lighten($background-color, 5%);
59 | }
60 |
61 | .signin_head {
62 | font-size: $font-head;
63 | font-weight: bold;
64 | color: $white-color;
65 | margin-bottom: 4rem;
66 | }
67 |
68 | .signin_form {
69 | display: flex;
70 | flex-direction: column;
71 | margin-bottom: 1rem;
72 | }
73 |
74 | .signin_forgotPW {
75 | padding-bottom: 0.5rem;
76 | border-bottom: 2px solid $font-dark-color;
77 | font-size: $font-medium;
78 | }
79 |
80 | .signin_input {
81 | width: 340px;
82 | height: 48px;
83 | margin: 0.3rem;
84 | border: 1px solid lighten($background-color, 26%);
85 | border-radius: 5px;
86 | background-color: lighten($background-color, 16%);
87 | color: $font-color;
88 | }
89 |
90 | .login_error {
91 | color: $orange-color;
92 | font-size: $font-small;
93 | padding: 0.5rem 1rem 0;
94 | }
95 |
96 | .signin_btn {
97 | margin-top: 2rem;
98 | font-size: $font-medium;
99 | width: 200px;
100 | height: 50px;
101 | background-color: $primary-color;
102 | color: $white-color;
103 | border-radius: 30px;
104 | }
105 |
106 | .signin_btn:hover {
107 | transform: scale(1.1);
108 | background-color: lighten($primary-color, 5%);
109 | }
110 |
111 | .signin_btn:focus {
112 | background-color: $primary-color;
113 | }
114 |
--------------------------------------------------------------------------------
/client/scss/mainContainer.scss:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | body {
8 | font-family: 'Roboto', sans-serif;
9 | color: $font-color;
10 | }
11 |
12 | button,
13 | button:focus {
14 | border: none;
15 | outline: none;
16 | cursor: pointer;
17 | }
18 |
19 | li {
20 | list-style: none;
21 | }
22 |
23 | a {
24 | text-decoration: none;
25 | }
26 | input {
27 | padding: 0.8rem 0.6rem;
28 | outline: none;
29 | }
30 |
31 | .main_container {
32 | display: flex;
33 | flex-direction: column;
34 | }
35 |
36 | .nav_content {
37 | display: flex;
38 | }
39 |
--------------------------------------------------------------------------------
/client/scss/nav.scss:
--------------------------------------------------------------------------------
1 | .nav {
2 | display: flex;
3 | flex-direction: column;
4 | width: 16vw;
5 | min-width: 9rem;
6 | background-color: $accent-background-color;
7 | height: 95vh;
8 | padding: 2.5rem 0;
9 | }
10 |
11 | .nav_icon {
12 | width: 5rem;
13 | height: auto;
14 | align-self: center;
15 | margin: 0 0 2.5rem 0;
16 | }
17 |
18 | .nav_btns {
19 | text-align: start;
20 | width: 15vw;
21 | min-width: 8.5rem;
22 | background-color: $accent-background-color;
23 | color: $font-color;
24 | font-size: $font-nav;
25 | padding: 1rem 0 1rem 2rem;
26 | border-radius: 0 10px 10px 0;
27 | }
28 |
29 | .nav_btns:hover {
30 | background-color: lighten($accent-background-color, 5%);
31 | border-left: 0.5rem solid lighten($primary-color, 30%);
32 | padding: 1rem 0 1rem 1.5rem;
33 | color: $font-color;
34 | }
35 |
36 | .nav_btns:focus {
37 | background-color: transparentize($primary-color, 0.6%);
38 | color: $white-color;
39 | font-weight: bold;
40 | }
41 |
--------------------------------------------------------------------------------
/client/scss/stats.scss:
--------------------------------------------------------------------------------
1 | .stats {
2 | background-color: $background-color;
3 | color: $gray-color;
4 | display: flex;
5 | width: 47vw;
6 | flex-direction: column;
7 | align-items: center;
8 | height: 95vh;
9 | padding: 3rem 1rem;
10 | overflow-y: auto;
11 | border-left: 1px solid lighten($background-color, 10%);
12 |
13 | #stats_time {
14 | display: flex;
15 | flex-direction: row;
16 | width: 80%;
17 | justify-content: space-evenly;
18 | padding: 1rem 0.33rem;
19 | }
20 |
21 | .stats_graph_dataKey {
22 | color: $font-color;
23 | }
24 |
25 | h3 {
26 | text-align: center;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/client/scss/titlebar.scss:
--------------------------------------------------------------------------------
1 | .titlebar {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | width: 100%;
6 | height: 5vh;
7 | background-color: darken($accent-background-color, 3%);
8 | border-bottom: 1px solid lighten($background-color, 10%);
9 | }
10 |
11 | .title_bar {
12 | display: flex;
13 | align-items: center;
14 | justify-content: space-between;
15 | -webkit-app-region: drag;
16 | width: 98vw;
17 | height: 3.5vh;
18 | color: $font-color;
19 | }
20 |
21 | .titlebar_traffic {
22 | display: flex;
23 | align-items: center;
24 | }
25 |
26 | .titlebar_toggle {
27 | width: 2.5vh;
28 | height: 2.5vh;
29 | font-size: 2.5vh;
30 | cursor: pointer;
31 | margin-left: 13vw;
32 | }
33 |
34 | .titlebar_toggle:hover {
35 | color: $white-color;
36 | cursor: pointer;
37 | }
38 |
39 | .titlebar_logo {
40 | width: 6vw;
41 | margin-left: 1.5rem;
42 | }
43 |
44 | .titlebar_btns {
45 | display: flex;
46 | align-items: center;
47 | font-size: 2.5vh;
48 | margin-right: 3vw;
49 | }
50 |
51 | .titlebar_link {
52 | width: 2.8vh;
53 | height: 2.8vh;
54 | margin: 0 15px;
55 | }
56 |
57 | .titlebar_icon {
58 | width: 3vh;
59 | height: 3vh;
60 | }
61 |
62 | .titlebar_btn {
63 | width: 2.5vh;
64 | height: 2.5vh;
65 | margin: 0 15px;
66 | cursor: pointer;
67 | }
68 |
69 | .titlebar_btn:hover {
70 | color: $white-color;
71 | cursor: pointer;
72 | }
73 |
--------------------------------------------------------------------------------
/client/scss/userStatus.scss:
--------------------------------------------------------------------------------
1 | .user_status {
2 | width: 200px;
3 | position: absolute;
4 | margin-left: 79vw;
5 | z-index: 1;
6 | padding: 0.5rem 0;
7 | background-color: darken($background-color, 4%);
8 | border-radius: 5px;
9 | border: 1px solid darken($background-color, 10%);
10 | box-shadow: 0 0 10px rgba(26, 25, 25, 0.2);
11 | color: $font-color;
12 | }
13 |
14 | .starus_user_name {
15 | margin: 1rem 1.5rem;
16 | font-weight: bolder;
17 | }
18 |
19 | .status_block {
20 | width: 100%;
21 | padding: 0.5rem 0;
22 | }
23 |
24 | .block_line {
25 | border-bottom: 1.5px solid lighten($background-color, 10%);
26 | }
27 |
28 | .user_btn {
29 | text-align: left;
30 | width: 100%;
31 | height: 30px;
32 | cursor: pointer;
33 | padding: 0 1.5rem;
34 | margin: 1rem 0 0;
35 | color: $font-color;
36 | background-color: darken($background-color, 5%);
37 | }
38 |
39 | .user_btn:hover {
40 | background-color: transparentize($primary-color, 0.3%);
41 | color: $white-color;
42 | }
43 |
--------------------------------------------------------------------------------
/client/scss/variables.scss:
--------------------------------------------------------------------------------
1 | $white-color: #ffffff;
2 | $black-color: #000000;
3 | $gray-color: #f7f7f7;
4 | $darker-color: #032244;
5 | $primary-color: #4741c0;
6 | $secondary-color: #47c796;
7 | $accent-color: #0091e2;
8 | $background-color: #2c2f35;
9 | $accent-background-color: #202631;
10 | $font-color: #dbdbdb;
11 | $font-dark-color: #1b1b1b;
12 | $yellow-color: #ffe551;
13 | $green-color: rgb(66, 233, 102);
14 | $orange-color: #fa4b2cef;
15 | $font-head: 32px;
16 | $font-nav: 1em;
17 | $font-large: 17px;
18 | $font-medium: 15px;
19 | $font-small: 12px;
20 | $font-micro: 11px;
21 |
--------------------------------------------------------------------------------
/client/services/axiosService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import TokenStorage from '../db/token';
3 |
4 | class axiosService {
5 | static authHeaders() {
6 | const token = TokenStorage.getToken();
7 | return {
8 | headers: { Authorization: `Bearer ${token}` },
9 | };
10 | }
11 |
12 | static getRequest(url, ...args) {
13 | return axios.get(url, ...args, axiosService.authHeaders());
14 | }
15 |
16 | static postRequest(url, ...args) {
17 | return axios.post(url, ...args, axiosService.authHeaders());
18 | }
19 | }
20 |
21 | export default axiosService;
22 |
--------------------------------------------------------------------------------
/client/services/containerService.js:
--------------------------------------------------------------------------------
1 | import axiosService from './axiosService';
2 |
3 | class ContainerService {
4 | static async setupCon() {
5 | try {
6 | await axiosService.getRequest('http://localhost:3000/api/containers');
7 | } catch (err) {
8 | throw new Error(
9 | 'There was an error setting up Containers from services/containerService'
10 | );
11 | }
12 | }
13 |
14 | static async getConInfo() {
15 | try {
16 | let result = await axiosService.getRequest(
17 | 'http://localhost:3000/api/containers/containers'
18 | );
19 | return result.data;
20 | } catch (err) {
21 | throw new Error(
22 | 'There was an error getting container information from services/containerService'
23 | );
24 | }
25 | }
26 |
27 | static async getMetrics(id, time) {
28 | try {
29 | const memoryStats = await axiosService.getRequest(
30 | 'http://localhost:3000/api/metrics',
31 | {
32 | params: {
33 | id,
34 | start: time,
35 | query: `container_memory_usage_bytes{id=~'/docker/${id}'}`,
36 | },
37 | }
38 | );
39 |
40 | const machineMem = await axiosService.getRequest(
41 | 'http://localhost:3000/api/metrics',
42 | {
43 | params: {
44 | id,
45 | start: time,
46 | query: `machine_memory_bytes`,
47 | },
48 | }
49 | );
50 |
51 | const cpuStats = await axiosService.getRequest(
52 | 'http://localhost:3000/api/metrics',
53 | {
54 | params: {
55 | id,
56 | start: time,
57 | query: `sum(rate(container_cpu_usage_seconds_total {id=~'/docker/${id}'} [5m]))`,
58 | },
59 | }
60 | );
61 |
62 | const cores = await axiosService.getRequest(
63 | 'http://localhost:3000/api/metrics',
64 | {
65 | params: {
66 | id,
67 | start: time,
68 | query: 'machine_cpu_cores',
69 | },
70 | }
71 | );
72 | const coreCount = cores.data[0][1];
73 | const data = {};
74 | data.memory = [];
75 | data.cpu = [];
76 |
77 | memoryStats.data.forEach((dataPoint, i) => {
78 | const machineMemory = machineMem.data[0][1];
79 | const time = new Date(dataPoint[0] * 1000);
80 | data.memory.push({
81 | time: time.toTimeString().slice(0, 5),
82 | percentTotalMemoryUsed: (dataPoint[1] / machineMemory) * 100,
83 | });
84 | });
85 |
86 | cpuStats.data.forEach((dataPoint) => {
87 | const time = new Date(dataPoint[0] * 1000);
88 | data.cpu.push({
89 | time: time.toTimeString().slice(0, 5),
90 | percentTotalCpuUsed: (dataPoint[1] / coreCount) * 100,
91 | });
92 | });
93 |
94 | return data;
95 | } catch (err) {
96 | throw new Error(
97 | 'There was an error getting container information from services/containerService: '
98 | );
99 | }
100 | }
101 |
102 | static postClickBtn(command, id) {
103 | try {
104 | const result = axiosService.postRequest(
105 | `http://localhost:3000/api/containers/${command}`,
106 | { containerID: id }
107 | );
108 | return result;
109 | } catch (err) {
110 | throw new Error(
111 | 'There is error on button functions in container service'
112 | );
113 | }
114 | }
115 | }
116 |
117 | export default ContainerService;
118 |
--------------------------------------------------------------------------------
/client/services/containerService.test.js:
--------------------------------------------------------------------------------
1 | import Service from './containerService';
2 | import axios from 'axios';
3 |
4 | describe('>> Container Service', () => {
5 | describe('>> getConInfo', () => {
6 | it('should call url with Axios', async () => {
7 | const spy = jest.spyOn(axios, 'get');
8 | const url = '/any-url-you-want';
9 | await Service.getConInfo(url);
10 |
11 | expect(spy).toBeCalledWith(url);
12 | });
13 |
14 | // it('should call return err string ', async () => {
15 | // const spy = jest.spyOn(console, 'log');
16 | // const url = '/any-url-you-want';
17 | // const message = await Service.getConInfo(url).rejects.toThrowError(
18 | // new InvalidArgumentError('Some error message')
19 | // );
20 |
21 | // expect(message).toBeCalledWith('err');
22 | // });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/client/services/imageService.js:
--------------------------------------------------------------------------------
1 | import axiosService from './axiosService';
2 |
3 | class ImageService {
4 | static async getImageInfo() {
5 | try {
6 | const result = await axiosService.getRequest(
7 | 'http://localhost:3000/api/images'
8 | );
9 | return result.data;
10 | } catch (error) {
11 | throw new Error(
12 | 'There was an error getting image information from services/imageService: '
13 | );
14 | }
15 | }
16 |
17 | static async pullImageInfo(image) {
18 | try {
19 | const result = await axiosService.postRequest(
20 | 'http://localhost:3000/api/images/pull',
21 | {
22 | imageName: image,
23 | }
24 | );
25 | return result;
26 | } catch (error) {
27 | throw new Error('There was an error pulling Image Information: ', error);
28 | }
29 | }
30 |
31 | static async startImage(ID) {
32 | try {
33 | const result = await axiosService.postRequest(
34 | 'http://localhost:3000/api/images/start',
35 | { imageID: ID }
36 | );
37 | return result;
38 | } catch (error) {
39 | throw new Error('There was an error starting the Image: ', error);
40 | }
41 | }
42 |
43 | static async deleteImage(ID) {
44 | try {
45 | const result = await axiosService.postRequest(
46 | 'http://localhost:3000/api/images/delete',
47 | { imageID: ID }
48 | );
49 | return result;
50 | } catch (error) {
51 | throw new Error('There was an error starting the Image: ', error);
52 | return error;
53 | }
54 | }
55 |
56 | static async DockerFileDefaultText() {
57 | return boilerPlate;
58 | }
59 |
60 | static async buildImage(info) {
61 | try {
62 | const result = await axiosService.postRequest(
63 | 'http://localhost:3000/api/images/build',
64 | {
65 | imageName: info.imageName,
66 | path: info.dockerPath,
67 | }
68 | );
69 | return result;
70 | } catch (error) {
71 | throw new Error('There was an error building the image: ', error);
72 | return error;
73 | }
74 | }
75 |
76 | static dockerBoiler() {
77 | const dockerBoiler = `FROM
78 |
79 | WORKDIR
80 |
81 | COPY
82 |
83 | COPY
84 |
85 | RUN
86 |
87 | COPY
88 |
89 | CMD
90 | `;
91 |
92 | return dockerBoiler;
93 | }
94 |
95 | static yamlBoiler() {
96 | const yamlBoiler = `version:
97 | services:
98 | web:
99 | build: .
100 | ports:
101 | - "5000:5000"
102 | volumes:
103 | - .:/code
104 | - logvolume01:/var/log
105 | volumes:
106 | logvolume01: {}
107 | `;
108 |
109 | return yamlBoiler;
110 | }
111 | }
112 |
113 | export default ImageService;
114 |
--------------------------------------------------------------------------------
/client/services/prometheusService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | class StartUp {
4 | static async prometheus() {
5 | try {
6 | await axios.get('http://localhost:3000/metrics/node-exporter');
7 | await axios.get('http://localhost:3000/metrics/yamlParse');
8 | return;
9 | } catch (error) {
10 | if (error) {
11 | console.log('Error in startup of prometheus: ', error);
12 | }
13 | }
14 | }
15 | }
16 |
17 | export default StartUp;
--------------------------------------------------------------------------------
/client/services/userDbService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import TokenStorage from '../db/token';
3 |
4 | class UserDbService {
5 | static async postUserData(userData) {
6 | try {
7 | let result = await axios.post(
8 | 'http://localhost:3000/api/user/login',
9 | userData
10 | );
11 | return result.data;
12 | } catch (error) {
13 | console.log('this error is from User Service: ', error);
14 | return { error: true };
15 | }
16 | }
17 |
18 | static async getUserToken() {
19 | const token = TokenStorage.getToken();
20 | try {
21 | const result = await axios.get('http://localhost:3000/api/user/me', {
22 | headers: { Authorization: `Bearer ${token}` },
23 | });
24 | return result.data;
25 | } catch (error) {
26 | console.log('There was an error getting user token: ', error);
27 | return { error: 'unauthenticated' };
28 | }
29 | }
30 |
31 | static logout() {
32 | TokenStorage.clearToken();
33 | }
34 | }
35 |
36 | export default UserDbService;
37 |
--------------------------------------------------------------------------------
/client/services/utilities.js:
--------------------------------------------------------------------------------
1 | export const throttle = (fn, time) => {
2 | let timeoutId = null;
3 |
4 | return function () {
5 | if (timeoutId !== null) {
6 | return;
7 | }
8 | fn(...arguments);
9 | timeoutId = setTimeout(() => {
10 | timeoutId = null;
11 | }, time);
12 | };
13 | };
14 |
--------------------------------------------------------------------------------
/client/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import { composeWithDevTools } from 'redux-devtools-extension';
3 | import { reducers } from './redux/reducers';
4 |
5 | const store = createStore(reducers, composeWithDevTools());
6 |
7 | export default store;
8 |
--------------------------------------------------------------------------------
/electron/main.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow, ipcMain, Menu } = require('electron');
2 | const path = require('path');
3 | const url = require('url');
4 | const server = require('../server/server.js');
5 |
6 | require('../server/server');
7 |
8 | let mainWindow;
9 |
10 | function createWindow() {
11 | mainWindow = new BrowserWindow({
12 | width: 1200,
13 | height: 770,
14 | minWidth: 1040,
15 | minHeight: 680,
16 | // frame: false,
17 | titleBarStyle: 'hiddenInset',
18 | webPreferences: {
19 | nodeIntegration: true,
20 | contextIsolation: false,
21 | enableRemoteModule: true,
22 | },
23 | });
24 |
25 | if (process.env.NODE_ENV === 'development') {
26 | // when it is in development mode, it opens in browser.
27 | mainWindow.webContents.openDevTools();
28 | mainWindow.loadURL(`http://localhost:3000`);
29 | } else {
30 | // when it is production mode, we are opening the electron app
31 | // need to yarn build first, and then yarn start.
32 | mainWindow.loadURL(
33 | url.format({
34 | pathname: path.join(__dirname, '../index.html'),
35 | protocol: 'file:',
36 | slashes: true,
37 | })
38 | );
39 | }
40 |
41 | Menu.setApplicationMenu(null);
42 |
43 | // mainWindow.on('closed', () => {
44 | // mainWindow = null;
45 | // });
46 |
47 | // mainWindow.on('maximize', () => {
48 | // mainWindow.webContents.send('maximized');
49 | // });
50 |
51 | // mainWindow.on('unmaximize', () => {
52 | // mainWindow.webContents.send('unmaximized');
53 | // });
54 | }
55 |
56 | // ipcMain.handle('minimize-event', () => {
57 | // mainWindow.minimize();
58 | // });
59 |
60 | // ipcMain.handle('maximize-event', () => {
61 | // mainWindow.maximize();
62 | // });
63 |
64 | // ipcMain.handle('unmaximize-event', () => {
65 | // mainWindow.unmaximize();
66 | // });
67 |
68 | // ipcMain.handle('close-event', () => {
69 | // app.quit();
70 | // });
71 |
72 | // app.on('browser-window-focus', () => {
73 | // mainWindow.webContents.send('focused');
74 | // });
75 |
76 | // app.on('browser-window-blur', () => {
77 | // mainWindow.webContents.send('blurred');
78 | // });
79 |
80 | app.whenReady().then(() => {
81 | createWindow();
82 |
83 | app.on('activate', () => {
84 | // on macOS it is common to re-create a window in the app when the dock icon is clicked and there are no other windows open.
85 | if (BrowserWindow.getAllWindows().length === 0) createWindow();
86 | });
87 | });
88 |
89 | // Quit when all windows are closed.
90 | app.on('window-all-closed', () => {
91 | // on OS X it is common for applications and their menu bar to stay active until the user quits explicity with Cmd + Q.
92 | if (process.platform !== 'darwin') app.quit();
93 | });
94 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | dockure
9 |
10 |
11 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dockure",
3 | "version": "1.0.0",
4 | "description": "Simplifying the containerization process",
5 | "main": "./electron/main.js",
6 | "scripts": {
7 | "start": "cross-env NODE_ENV=production electron .",
8 | "build": "NODE_ENV=production webpack",
9 | "dev": "cross-env NODE_ENV=development concurrently \"NODE_ENV=development webpack serve --open\" \"NODE_ENV=development nodemon ./server/server.js\"",
10 | "test-start": "cross-env NODE_ENV=development concurrently 'electron .' 'nodemon server/server.js'",
11 | "test": "jest"
12 | },
13 | "nodemonConfig": {
14 | "ignore": [
15 | "build",
16 | "client"
17 | ]
18 | },
19 | "author": "Alex Zayas, Nathanael Tracy, Liam Talty, Van Nguyen, Hazel Na",
20 | "license": "MIT",
21 | "dependencies": {
22 | "@fortawesome/fontawesome-svg-core": "^1.2.36",
23 | "@fortawesome/free-solid-svg-icons": "^5.15.4",
24 | "@fortawesome/react-fontawesome": "^0.1.15",
25 | "axios": "^0.21.1",
26 | "babelify": "^10.0.0",
27 | "bcryptjs": "^2.4.3",
28 | "body-parser": "^1.19.0",
29 | "codemirror": "^5.62.3",
30 | "cors": "^2.8.5",
31 | "dotenv": "^10.0.0",
32 | "express": "^4.17.1",
33 | "file-saver": "^2.0.5",
34 | "js-yaml": "^4.1.0",
35 | "jsonwebtoken": "^8.5.1",
36 | "jwt-decode": "^3.1.2",
37 | "moment": "^2.29.1",
38 | "node-fetch": "^2.6.1",
39 | "pg": "^8.7.1",
40 | "prop-types": "^15.7.2",
41 | "raw-loader": "^4.0.2",
42 | "react": "^17.0.2",
43 | "react-codemirror2": "^7.2.1",
44 | "react-dom": "^17.0.2",
45 | "react-redux": "^7.2.4",
46 | "react-router-dom": "^5.2.0",
47 | "recharts": "^2.1.2",
48 | "redux": "^4.1.1",
49 | "redux-devtools-extension": "^2.13.9",
50 | "util": "^0.12.4",
51 | "yarn": "^1.22.11"
52 | },
53 | "devDependencies": {
54 | "@babel/core": "^7.15.0",
55 | "@babel/plugin-transform-runtime": "^7.15.0",
56 | "@babel/preset-env": "^7.15.0",
57 | "@babel/preset-react": "^7.14.5",
58 | "@babel/preset-typescript": "^7.15.0",
59 | "@testing-library/jest-dom": "^5.15.1",
60 | "@testing-library/react": "^12.1.2",
61 | "@testing-library/user-event": "^13.5.0",
62 | "babel-jest": "^27.3.1",
63 | "babel-loader": "^8.2.2",
64 | "concurrently": "^6.2.1",
65 | "cross-env": "^7.0.3",
66 | "css-loader": "^6.2.0",
67 | "electron": "13.1.9",
68 | "html-webpack-plugin": "^5.3.2",
69 | "isomorphic-fetch": "^3.0.0",
70 | "jest": "^27.3.1",
71 | "nodemon": "^2.0.12",
72 | "react-test-renderer": "^17.0.2",
73 | "resolve-url-loader": "^4.0.0",
74 | "sass": "^1.37.5",
75 | "sass-loader": "^12.1.0",
76 | "source-map-loader": "^3.0.0",
77 | "style-loader": "^3.2.1",
78 | "svg-url-loader": "^7.1.1",
79 | "webpack": "^5.50.0",
80 | "webpack-cli": "^4.7.2",
81 | "webpack-dev-server": "^3.11.2"
82 | },
83 | "jest": {
84 | "testEnvironment": "jsdom"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/server/assets/prometheus.yaml:
--------------------------------------------------------------------------------
1 | # my global config
2 | global:
3 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
4 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
5 | # scrape_timeout is set to the global default (10s).
6 |
7 |
8 | # Attach these labels to any time series or alerts when communicating with
9 | # external systems (federation, remote storage, Alertmanager).
10 | external_labels:
11 | monitor: 'codelab-monitor'
12 |
13 | # Alertmanager configuration
14 | alerting:
15 | alertmanagers:
16 | - static_configs:
17 | - targets:
18 | # - alertmanager:9093
19 |
20 | # Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
21 | rule_files:
22 | - 'prometheus.rules.yml'
23 | # - "first_rules.yml"
24 | # - "second_rules.yml"
25 |
26 | # A scrape configuration containing exactly one endpoint to scrape:
27 | # Here it's Prometheus itself.
28 | scrape_configs:
29 | # The job name is added as a label `job=` to any timeseries scraped from this config.
30 | - job_name: "prometheus"
31 |
32 | # Override the global default and scrape targets from this job every 5 seconds.
33 | scrape_interval: 5s
34 |
35 | # metrics_path defaults to '/metrics'
36 | # scheme defaults to 'http'.
37 |
38 | static_configs:
39 | - targets: ["localhost:9090", "host.docker.internal:9323"]
40 |
41 | - job_name: 'cadvisor'
42 |
43 | scrape_interval: 10s
44 |
45 | tls_config:
46 | insecure_skip_verify: true
47 |
48 | static_configs:
49 | - targets:
50 | # - 'localhost:9101'
51 | # - 'cadvisor:9101'
52 | - 'host.docker.internal:9101'
53 | labels:
54 | alias: 'cadvisor'
55 |
--------------------------------------------------------------------------------
/server/config.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv');
2 | dotenv.config();
3 |
4 | const config = {
5 | jwt: {
6 | secretKey: process.env.JWT_SECRET,
7 | expiresInSec: parseInt(process.env.JWT_EXPIRES_SEC),
8 | },
9 | bcrypt: {
10 | saltRounds: parseInt(process.env.BCRYPT_SALT_ROUNDS),
11 | },
12 | db: {
13 | host: process.env.DB_HOST,
14 | },
15 | };
16 |
17 | module.exports = config;
18 |
--------------------------------------------------------------------------------
/server/controllers/authentication.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const config = require('../config');
3 |
4 | const AUTH_ERROR = { message: 'Authentication Error' };
5 |
6 | const authentication = async (req, res, next) => {
7 | const authHeader = req.get('Authorization');
8 | if (!(authHeader && authHeader.startsWith('Bearer '))) {
9 | return res.status(401).json(AUTH_ERROR);
10 | }
11 |
12 | const token = authHeader.split(' ')[1];
13 | jwt.verify(token, config.jwt.secretKey, (error, decoded) => {
14 | if (error) {
15 | return res.status(401).json(AUTH_ERROR);
16 | }
17 | next();
18 | });
19 | };
20 |
21 | module.exports = authentication;
22 |
--------------------------------------------------------------------------------
/server/controllers/cadvisorStart.js:
--------------------------------------------------------------------------------
1 | const { exec } = require('child_process');
2 | const path = require('path');
3 |
4 | const cadvisorStartController = {};
5 |
6 | //Standard middleware error handling won't work here
7 | cadvisorStartController.restartCadvisor = async (req, res, next) => {
8 | exec('docker start cadvisor');
9 | return next();
10 | }
11 |
12 | cadvisorStartController.startCadvisor = async (req, res, next) => {
13 | if (res.locals.cadvRunning) return next();
14 | if (process.platform === 'linux' || process.platform === 'win32') {
15 | await exec(`docker run --volume=/sys:/sys:ro --volume=/cgroup:/cgroup:ro --publish=9101:8080 --detach=true --name=cadvisor gcr.io/cadvisor/cadvisor:latest`, (error, stdout, stderr) => {});
16 | }
17 | else {
18 | await exec(`docker run -d --name=cadvisor -p 9101:8080 --volume=/:/rootfs:ro --volume=/var/run:/var/run:rw --volume=/sys:/sys:ro --volume=/var/lib/docker/:/var/lib/docker:ro --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro --volume=/dev/disk/:/dev/disk:ro raymondmm/cadvisor`, (error, stdout, stderr) => {});
19 | }
20 | return next();
21 | }
22 |
23 | module.exports = cadvisorStartController;
--------------------------------------------------------------------------------
/server/controllers/dContainer.js:
--------------------------------------------------------------------------------
1 | const { exec } = require('child_process');
2 | const axios = require('axios');
3 | const util = require('util');
4 | const conController = {};
5 |
6 |
7 | conController.getContainers = async(req, res, next) => {
8 | try {
9 | const result = await axios.get('http://localhost:2375/containers/json?all=true')
10 | res.locals.containers = result.data;
11 | return next();
12 | } catch(err) {
13 | return next(err)
14 | }
15 | }
16 |
17 | conController.getStats = async (req, res, next) => {
18 | const { containerID } = req.body
19 | try {
20 | const result = await axios.get(`http://localhost:2375/containers/${containerID}/stats?stream=false`)
21 | res.locals.data = result.data
22 | return next();
23 | } catch(err) {
24 | console.log(err)
25 | return next(err)
26 | }
27 | }
28 |
29 | //https://docs.docker.com/engine/api/v1.24/
30 | conController.startContainer = async (req, res, next) => {
31 | const { containerID } = req.body
32 |
33 | try {
34 | const { status } = await axios.post(`http://localhost:2375/containers/${containerID}/start`)
35 | res.locals.status = status;
36 | return next();
37 | } catch(err) {
38 | return next(err);
39 | }
40 | }
41 |
42 | conController.stopContainer = async (req, res, next) => {
43 | const { containerID } = req.body;
44 |
45 | try{
46 | const { status } = await axios.post(`http://localhost:2375/containers/${containerID}/stop`)
47 | res.locals.status = status;
48 | return next();
49 | } catch(err) {
50 | if(err.response.status === 304) {
51 | res.locals.status = 304;
52 | return next()
53 | }
54 | return next(err);
55 | }
56 | };
57 |
58 | conController.killContainer = async (req, res, next) => {
59 | const { containerID } = req.body
60 | try {
61 | const { status } = await axios.post(`http://localhost:2375/containers/${containerID}/kill`)
62 | res.locals.status = status;
63 | return next();
64 | } catch(err) {
65 | console.log('There was an error killing the container: ', err);
66 | return next(err)
67 | }
68 | }
69 |
70 | conController.restartContainer = async (req, res, next) => {
71 | const { containerID } = req.body
72 |
73 | try {
74 | const { status } = await axios.post(`http://localhost:2375/containers/${containerID}/restart?t=5`)
75 | res.locals.status = status;
76 | return next();
77 | } catch(err) {
78 | console.log('There was an error restarting the container: ', err);
79 | return next(err)
80 | }
81 | }
82 |
83 | conController.pauseContainer = async (req, res, next) => {
84 | const { containerID } = req.body
85 |
86 | try {
87 | const { status } = await axios.post(`http://localhost:2375/containers/${containerID}/pause`)
88 |
89 | res.locals.status = status;
90 | return next();
91 | } catch(err) {
92 | console.log('There was an error pausing the container: ', err);
93 | return next(err)
94 | }
95 | }
96 |
97 | conController.resumeContainer = async (req, res, next) => {
98 |
99 | const { containerID } = req.body
100 |
101 |
102 | try {
103 | const { status } = await axios.post(`http://localhost:2375/containers/${containerID}/unpause`)
104 | res.locals.status = status;
105 | return next();
106 | } catch(err) {
107 | console.log(err)
108 | return next(err)
109 | }
110 | }
111 |
112 | conController.removeContainer = async (req, res, next) => {
113 | const { containerID } = req.body
114 |
115 | try {
116 | const { status } = await axios.delete(`http://localhost:2375/containers/${containerID}`)
117 | res.locals.status = status;
118 | return next();
119 | } catch(err) {
120 | console.log(err)
121 | return next(err)
122 | }
123 | }
124 |
125 | //Standard middleware error handling won't work here
126 | conController.restartSocat = async (req, res, next) => {
127 | exec('docker start socat');
128 | return next();
129 | }
130 |
131 | //docker run -d -v /var/run/docker.sock:/var/run/docker.sock --name socat -p 127.0.0.1:2375:2375 bobrik/socat TCP-LISTEN:2375,fork UNIX-CONNECT:/var/run/docker.sock
132 | conController.startSocat = async (req, res, next) => {
133 | if (res.locals.running) return next();
134 | await exec(`docker run -d -v /var/run/docker.sock:/var/run/docker.sock --name socat -p 127.0.0.1:2375:2375 bobrik/socat TCP-LISTEN:2375,fork UNIX-CONNECT:/var/run/docker.sock`, (error, stdout, stderr) => {});
135 | return next();
136 | }
137 |
138 | //throttler for getting containers
139 | conController.throttle = async (req, res, next) => {
140 | let finished = false;
141 | const throttle = () => {
142 | setTimeout(async () => {
143 | await exec('docker ps', (error, stdout, stderr) => {
144 | if (finished) return next();
145 | if (stdout.includes('prometheus') && stdout.includes('cadvisor') && stdout.includes('socat')) {
146 | finished = true;
147 | throttle()
148 | }
149 | else {
150 | throttle();
151 | }
152 | })
153 | }, 200);
154 | }
155 | throttle();
156 | }
157 |
158 |
159 | module.exports = conController;
160 |
161 |
--------------------------------------------------------------------------------
/server/controllers/dImage.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 | const { exec } = require('child_process');
3 | const path = require('path');
4 |
5 | const imageController = {};
6 |
7 | imageController.getImages = async(req, res, next) => {
8 | try {
9 | const result = await axios.get('http://localhost:2375/images/json?all=true');
10 | res.locals.images = result.data;
11 | return next();
12 | } catch(err){
13 | return next(err);
14 | }
15 | }
16 |
17 | imageController.startImage = (req, res, next) => {
18 |
19 | res.locals.imageID = req.body.imageID
20 | exec(`docker run ${req.body.imageID}`, (error, stdout, stderr) => {
21 | if (error) {
22 | console.log(`error: ${error.message}`);
23 | return next(error);
24 | }
25 | if (stderr) {
26 | console.log(`stderr: ${stderr}`);
27 | return next(stderr);
28 | };
29 | });
30 | return next();
31 | }
32 |
33 | imageController.deleteImage = (req, res, next) => {
34 | res.locals.imageID = req.body.imageID
35 |
36 | exec(`docker image rm ${req.body.imageID}`, (error, stdout, stderr) => {
37 | if (error) {
38 | console.log(`error: ${error.message}`);
39 | return next(error);
40 | }
41 | if (stderr) {
42 | console.log(`stderr: ${stderr}`);
43 | return next(stderr);
44 | };
45 | });
46 | return next();
47 | }
48 |
49 | imageController.pullImage = (req, res, next) => {
50 |
51 | const { imageName } = req.body
52 |
53 | try {
54 | exec(`docker pull ${imageName}`, (error, stdout, stderr) => {
55 | if (error) {
56 | return next(error);
57 | }
58 | if (stderr) {
59 | return next(stderr);
60 | };
61 | });
62 | return next();
63 | } catch(err) {
64 | return next(err)
65 | }
66 | }
67 |
68 | imageController.buildImage = (req, res, next) => {
69 | try {
70 | exec(`docker build -t ${req.body.imageName} ${req.body.path}`, (error, stdout, stderr) => {
71 | if (error) {
72 | return next(error);
73 | }
74 | if (stderr) {
75 | return next(stderr);
76 | };
77 | });
78 |
79 | return next();
80 | } catch(err) {
81 | return next(err)
82 | }
83 | }
84 |
85 |
86 | module.exports = imageController;
--------------------------------------------------------------------------------
/server/controllers/isAuth.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const config = require('../config');
3 | const pool = require('../database/dbConnect');
4 |
5 | const AUTH_ERROR = { message: 'Authentication Error' };
6 |
7 | const isAuth = async (req, res, next) => {
8 | const authHeader = req.get('Authorization');
9 | if (!(authHeader && authHeader.startsWith('Bearer '))) {
10 | return res.status(401).json(AUTH_ERROR);
11 | }
12 |
13 | const token = authHeader.split(' ')[1];
14 | jwt.verify(token, config.jwt.secretKey, async (error, decoded) => {
15 | if (error) {
16 | return res.status(401).json(AUTH_ERROR);
17 | }
18 |
19 | const id = decoded.id;
20 | const params = [id];
21 | const query = `SELECT id FROM users WHERE id=$1 `;
22 | const { rows } = await pool.query(query, params);
23 | const user = rows[0];
24 | if (!user) {
25 | return res.status(401).json(AUTH_ERROR);
26 | }
27 | req.userId = user.id;
28 | req.token = token;
29 | next();
30 | });
31 | };
32 |
33 | module.exports = isAuth;
34 |
--------------------------------------------------------------------------------
/server/controllers/metricQueries.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch');
2 |
3 | const metricQueriesController = {};
4 |
5 | metricQueriesController.getMetrics = async (req, res, next) => {
6 | try {
7 | const start = res.locals.start;
8 | const end = res.locals.end;
9 | const metrics = req.query.query;
10 | const query = `http://localhost:9090/api/v1/query_range?query=${metrics}&start=${start}&end=${end}&step=15`;
11 | const data = await fetch(query);
12 | const results = await data.json();
13 | res.locals.values = results.data.result[0].values
14 | return next();
15 | } catch (error) {
16 | if (error) return next(error);
17 | }
18 |
19 | }
20 |
21 | module.exports = metricQueriesController;
--------------------------------------------------------------------------------
/server/controllers/nodeExporter.js:
--------------------------------------------------------------------------------
1 | const { exec } = require('child_process');
2 |
3 | const nodeExporter = {};
4 |
5 | nodeExporter.start = (req, res, next) => {
6 | if (res.locals.nodeExists === true) return next();
7 | try {
8 | exec('docker run --name node-exporter -p 9100:9100 prom/node-exporter', (error, stdout, stderr) => {
9 | if (error) {
10 | console.log(`error: ${error.message}`);
11 | return next(error);
12 | }
13 | if (stderr) {
14 | console.log(`stderr: ${stderr}`);
15 | return next(stderr);
16 | };
17 | })
18 | } catch (error) {
19 | if (error) {
20 | return next(error);
21 | }
22 | }
23 | return next();
24 | }
25 |
26 | nodeExporter.check = (req, res, next) => {
27 | let running = false;
28 | let toCheck = '';
29 | if (res.locals.running !== undefined) toCheck = '-a';
30 |
31 | exec(`docker ps ${toCheck}`, (error, stdout, stderr) => {
32 |
33 | if (error) {
34 | console.log(`error: ${error.message}`);
35 | return;
36 | }
37 | if (stderr) {
38 | console.log(`stderr: ${stderr}`);
39 | return;
40 | };
41 |
42 | const target = 'prom/node-exporter';
43 | for (let i = 0; i < stdout.length && running === false; i++) {
44 | if (stdout[i] === 'p') {
45 | for (let j = 0; j < target.length; j++) {
46 | const targetLetter = target[j];
47 | const containersLetter = stdout[i];
48 | if (targetLetter === containersLetter && target.length - 1 === j) {
49 | if (res.locals.running === undefined) {
50 | running = true;
51 | res.locals.nodeExists = true;
52 | }
53 | else {
54 | res.locals.nodeExists = true;
55 | }
56 |
57 | }
58 | else if (targetLetter !== containersLetter) {
59 | break;
60 | }
61 | i++;
62 | }
63 | }
64 | }
65 | res.locals.running = running;
66 | return next();
67 | })
68 | }
69 |
70 | nodeExporter.restart = (req, res, next) => {
71 | try {
72 | if (res.locals.nodeExists === true && res.locals.running === false) {
73 | exec('docker restart node-exporter', (error, stdout, stderr) => {
74 | if (error) {
75 | console.log(`error: ${error.message}`);
76 | return;
77 | }
78 | if (stderr) {
79 | console.log(`stderr: ${stderr}`);
80 | return;
81 | };
82 | })
83 | }
84 | return next();
85 | } catch (error) {
86 | if (error) {
87 | return next(error);
88 | }
89 | }
90 | }
91 |
92 | module.exports = nodeExporter;
93 |
--------------------------------------------------------------------------------
/server/controllers/promMetrics.js:
--------------------------------------------------------------------------------
1 | const { exec } = require('child_process');
2 | const path = require('path');
3 |
4 | const promContainerController = {};
5 |
6 | //Standard middleware error handling won't work here
7 | promContainerController.restartProm = async (req, res, next) => {
8 | exec('docker start prometheus');
9 | return next();
10 | }
11 |
12 | promContainerController.startProm = async (req, res, next) => {
13 | if (res.locals.promRunning) return next();
14 | await exec(`docker run --name prometheus -p 9090:9090 -d -v ${path.join(__dirname, '../assets/prometheus.yaml')}:/etc/prometheus/prometheus.yml prom/prometheus`, (error, stdout, stderr) => {});
15 | return next();
16 | }
17 |
18 |
19 | module.exports = promContainerController;
20 |
21 |
--------------------------------------------------------------------------------
/server/controllers/timeConversion.js:
--------------------------------------------------------------------------------
1 |
2 | const timeConversionController = {};
3 |
4 | timeConversionController.unixTime = (req, res, next) => {
5 | try {
6 | let currentTime = new Date().valueOf();
7 | currentTime = currentTime/1000;
8 | const start = currentTime - (req.query.start * 3600);
9 | res.locals.end = currentTime;
10 | res.locals.start = start;
11 | return next();
12 | } catch (error) {
13 | return next(error);
14 | }
15 |
16 | }
17 |
18 | module.exports = timeConversionController;
--------------------------------------------------------------------------------
/server/controllers/userController.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const bcrypt = require('bcryptjs');
3 | const pool = require('../database/dbConnect');
4 | const config = require('../config');
5 | const userController = {};
6 |
7 | userController.createUser = async (req, res, next) => {
8 | const { username, password, email } = req.body;
9 |
10 | let hashedPassword;
11 | try {
12 | hashedPassword = await bcrypt.hash(password, config.bcrypt.saltRounds);
13 | } catch (err) {
14 | return next({
15 | status: 500,
16 | message: err.message,
17 | message2: 'password error',
18 | });
19 | }
20 |
21 | try {
22 | const params = [username, hashedPassword, email];
23 | const query = `INSERT INTO users (username, password, email) VALUES ($1,$2,$3) RETURNING id`;
24 | const { rows } = await pool.query(query, params);
25 | res.locals.id = rows[0].id;
26 |
27 | const token = createJwtToken(res.locals.id);
28 | res.locals.token = token;
29 |
30 | return next();
31 | } catch (err) {
32 | return next({
33 | status: 500,
34 | message: 'Username already exists',
35 | });
36 | }
37 | };
38 |
39 | userController.userLogin = async (req, res, next) => {
40 | const { username, password } = req.body;
41 | let user;
42 |
43 | try {
44 | const params = [username];
45 | const query = `SELECT * FROM users WHERE username=$1`;
46 | const { rows } = await pool.query(query, params);
47 | if (rows.length < 1) {
48 | return next({
49 | status: 401,
50 | message: 'Invalid username or password',
51 | });
52 | }
53 | user = rows[0];
54 | const compared = await bcrypt.compare(password, user.password);
55 | if (!compared)
56 | return next({
57 | status: 401,
58 | message: 'The username or password is not valid.',
59 | });
60 | res.locals.id = user.id;
61 | const token = createJwtToken(res.locals.id);
62 | res.locals.token = token;
63 | return next();
64 | } catch (err) {
65 | return next({
66 | status: 500,
67 | message: err.message,
68 | });
69 | }
70 | };
71 |
72 | userController.me = async (req, res, next) => {
73 | const id = req.userId;
74 | const params = [id];
75 | const query = `SELECT * FROM users WHERE id=$1 `;
76 | const { rows } = await pool.query(query, params);
77 | const user = rows[0];
78 | if (!user) {
79 | return res.status(404).json({ message: 'User not found' });
80 | }
81 | res.locals.id = user.id;
82 | res.locals.token = req.token;
83 | res.locals.username = user.username;
84 | next();
85 | };
86 |
87 | function createJwtToken(id) {
88 | return jwt.sign({ id }, config.jwt.secretKey, {
89 | expiresIn: config.jwt.expiresInSec,
90 | });
91 | }
92 |
93 | module.exports = userController;
94 |
--------------------------------------------------------------------------------
/server/controllers/ymlParser.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const yaml = require('js-yaml');
3 | const path = require('path');
4 | const { exec } = require('child_process');
5 |
6 | const yamlParserController = {};
7 |
8 |
9 | yamlParserController.yamlConfig = (req, res, next) => {
10 | try {
11 | //get default yml
12 | const fileContents = fs.readFileSync(path.resolve(__dirname, '../assets/prometheus.yaml'));
13 | const data = yaml.load(fileContents);
14 |
15 | //update data
16 | const addressesArray = data.scrape_configs[1].static_configs[0].targets;
17 |
18 | for (let i = 0; i < res.locals.ports.length; i++) {
19 | addressesArray.push('host.docker.internal:' + res.locals.ports[i]);
20 | }
21 |
22 | //write data
23 | const yamlStr = yaml.dump(data);
24 | fs.writeFileSync(path.resolve(__dirname, '../assets/promConfigFile.yaml'), yamlStr, 'utf8');
25 | return next();
26 | } catch (error) {
27 | return next(error);
28 | }
29 | }
30 |
31 |
32 | yamlParserController.findPorts = (req, res, next) => {
33 | try {
34 | exec("docker ps", (error, stdout, stderr) => {
35 | if (error) {
36 | return `error: ${error.message}`;
37 | }
38 | if (stderr) {
39 | return `stderr: ${stderr}`;
40 | }
41 | const result = stdout.split('\n');
42 | let values = [];
43 | result.forEach(ele => {
44 | values.push(ele.split(" ").filter(item => item !== ""));
45 | })
46 |
47 | const keys = values.shift();
48 | for (let i = 0; i < keys.length; i++) {
49 | if (keys[i][0] === ' ') {
50 | keys[i] = keys[i].slice(1);
51 | }
52 | }
53 |
54 | values = values.map(function (element) {
55 | const obj = {};
56 | keys.forEach(function (key, index) {
57 | if(element.length) {
58 | obj[key] = element[index];
59 | }
60 | });
61 | return obj;
62 | });
63 | if(JSON.stringify(values[values.length - 1]) === '{}') values.pop();
64 | res.locals.unparsedContainers = values;
65 | return next();
66 | });
67 | } catch (error) {
68 | return next(error);
69 | }
70 | }
71 |
72 |
73 | yamlParserController.portParser = (req, res, next) => {
74 | const values = res.locals.unparsedContainers;
75 | const ports = [];
76 |
77 | for(let i = 0; i < values.length; i += 1) {
78 | if(values[i]['IMAGE'] !== ' prom/prometheus') {
79 | ports.push(values[i]['PORTS']);
80 | }
81 | }
82 |
83 | let parsedPort = "";
84 | for(let i = 0; i < ports.length; i += 1){
85 | let foundColon = false;
86 | let foundDash = false;
87 | let index = 0;
88 | let indexOfString = ports[i][index];
89 |
90 | while(!foundDash) {
91 | if( foundColon === true){
92 | parsedPort += indexOfString;
93 | }
94 | if(indexOfString === ':'){
95 | foundColon = true;
96 | }
97 | indexOfString = ports[i][++index];
98 | if(indexOfString === '-'){
99 | foundDash = true;
100 | }
101 | }
102 | ports[i] = parsedPort;
103 | parsedPort = ''
104 | }
105 | res.locals.ports = ports;
106 | return next();
107 | }
108 |
109 |
110 | module.exports = yamlParserController;
111 |
--------------------------------------------------------------------------------
/server/database/dbConnect.js:
--------------------------------------------------------------------------------
1 | const { Pool } = require('pg');
2 | const config = require('../config');
3 |
4 | const PG_URI = config.db.host;
5 |
6 | // //CREATE A NEW POOL HERE USING THAT CONNECTION STRING ABOVE
7 |
8 | const pool = new Pool({
9 | connectionString: PG_URI,
10 | });
11 |
12 | const createUserTable = `
13 | CREATE TABLE IF NOT EXISTS users (
14 | id SERIAL PRIMARY KEY,
15 | username VARCHAR (50) UNIQUE NOT NULL,
16 | password VARCHAR (150) NOT NULL,
17 | email VARCHAR (50) UNIQUE NOT NULL
18 | )`;
19 |
20 |
21 | pool.query(createUserTable, (err, res) => {
22 | if (err) {
23 | return err;
24 | }
25 | });
26 |
27 | module.exports = pool;
28 |
--------------------------------------------------------------------------------
/server/database/setup.sql:
--------------------------------------------------------------------------------
1 | /*
2 | These are the tables required to run our app.
3 | Please create a separate database for these tables inside of your DB, e.g. CREATE DATABASE wobble_chat;
4 | Then
5 | */
6 |
7 | -- CREATE TABLE IF NOT EXISTS users (
8 | -- id SERIAL PRIMARY KEY NOT NULL,
9 | -- username varchar(50) UNIQUE NOT NULL,
10 | -- password varchar(50) NOT NULL,
11 | -- email varchar(100) UNIQUE NOT NULL,
12 | -- -- isLoggedIn boolean DEFAULT true
13 | -- );
14 |
15 | -- CREATE TABLE IF NOT EXISTS `user` (
16 | -- `id` SERIAL NOT NULL,
17 | -- `username` varchar(50) NOT NULL,
18 | -- `password` varchar(50) NOT NULL,
19 | -- `email` varchar(100) NOT NULL,
20 | -- PRIMARY KEY (`id`),
21 | -- UNIQUE KEY `username` (`username`)
22 | -- UNIQUE KEY `email` (`email`)
23 | -- -- isLoggedIn boolean DEFAULT true
24 | -- );
25 |
26 | -- CREATE TABLE IF NOT EXISTS `user` (
27 | -- `id` SERIAL NOT NULL,
28 | -- `username` varchar(50) NOT NULL,
29 | -- `password` varchar(50) NOT NULL,
30 | -- `email` varchar(100) NOT NULL,
31 | -- PRIMARY KEY (`id`),
32 | -- UNIQUE KEY `id_UNIQUE` (`id`),
33 | -- UNIQUE KEY `username` (`username`)
34 | -- UNIQUE KEY `email` (`email`)
35 | -- -- isLoggedIn boolean DEFAULT true
36 | -- );
37 |
38 | -- CREATE TABLE IF NOT EXISTS users (
39 | -- id SERIAL PRIMARY KEY,
40 | -- username varchar(50) UNIQUE,
41 | -- password varchar(100),
42 | -- isLoggedIn boolean DEFAULT true
43 | -- );
44 |
45 | -- CREATE TABLE IF NOT EXISTS accounts (
46 | -- user_id serial PRIMARY KEY,
47 | -- username VARCHAR ( 50 ) UNIQUE NOT NULL,
48 | -- password VARCHAR ( 50 ) NOT NULL,
49 | -- email VARCHAR ( 255 ) UNIQUE NOT NULL,
50 | -- );
51 |
52 | -- CREATE TABLE IF NOT EXISTS questions (
53 | -- id SERIAL PRIMARY KEY,
54 | -- title varchar(500) NOT NULL,
55 | -- description text NOT NULL,
56 | -- url varchar(100) UNIQUE NOT NULL,
57 | -- isAnswered boolean DEFAULT false NOT NULL,
58 | -- creator integer REFERENCES users(id) NOT NULL,
59 | -- /* Can you do a foreign key reference of a boolean in another table? The boolean is not a key... */
60 | -- isOpen boolean DEFAULT false NOT NULL
61 | -- );
62 | -- CREATE TABLE IF NOT EXISTS messages (
63 | -- id SERIAL PRIMARY KEY,
64 | -- dateCreated date NOT NULL,
65 | -- questionId integer REFERENCES questions(id),
66 | -- content text NOT NULL,
67 | -- senderid varchar,
68 | -- ownedbycurrentuser boolean,
69 | -- body text,
70 | -- num integer
71 | -- );
--------------------------------------------------------------------------------
/server/routers/dContainer.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const conController = require('../controllers/dContainer.js');
3 | const containerRouter = express.Router();
4 | const promContainerController = require('../controllers/promMetrics');
5 | const cadvisorStartController = require('../controllers/cadvisorStart');
6 | const authentication = require('../controllers/authentication');
7 |
8 | containerRouter.get(
9 | '/',
10 | promContainerController.restartProm,
11 | promContainerController.startProm,
12 | cadvisorStartController.restartCadvisor,
13 | cadvisorStartController.startCadvisor,
14 | conController.restartSocat,
15 | conController.startSocat,
16 | conController.throttle,
17 | (req, res) => {
18 | return res.status(200).send('It worked');
19 | }
20 | );
21 |
22 | containerRouter.get(
23 | '/containers',
24 | authentication,
25 | conController.getContainers,
26 | (req, res) => {
27 | const result = res.locals.containers;
28 | return res.status(200).send(result);
29 | }
30 | );
31 |
32 | containerRouter.post('/stats', conController.getStats, (req, res) => {
33 | const result = res.locals.data;
34 | return res.status(200).json(result);
35 | });
36 |
37 | containerRouter.post(
38 | '/start',
39 | authentication,
40 | conController.startContainer,
41 | (req, res) => {
42 | return res.sendStatus(res.locals.status);
43 | }
44 | );
45 |
46 | containerRouter.post(
47 | '/stop',
48 | authentication,
49 | conController.stopContainer,
50 | (req, res) => {
51 | return res.sendStatus(res.locals.status);
52 | }
53 | );
54 |
55 | containerRouter.post(
56 | '/kill',
57 | authentication,
58 | conController.killContainer,
59 | (req, res) => {
60 | return res.sendStatus(res.locals.status);
61 | }
62 | );
63 |
64 | containerRouter.post(
65 | '/restart',
66 | authentication,
67 | conController.restartContainer,
68 | (req, res) => {
69 | return res.sendStatus(res.locals.status);
70 | }
71 | );
72 |
73 | containerRouter.post(
74 | '/pause',
75 | authentication,
76 | conController.pauseContainer,
77 | (req, res) => {
78 | return res.sendStatus(res.locals.status);
79 | }
80 | );
81 |
82 | containerRouter.post(
83 | '/resume',
84 | authentication,
85 | conController.resumeContainer,
86 | (req, res) => {
87 | return res.sendStatus(res.locals.status);
88 | }
89 | );
90 |
91 | containerRouter.post(
92 | '/remove',
93 | authentication,
94 | conController.removeContainer,
95 | (req, res) => {
96 | return res.sendStatus(res.locals.status);
97 | }
98 | );
99 |
100 | module.exports = containerRouter;
101 |
--------------------------------------------------------------------------------
/server/routers/dImage.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const imageController = require('../controllers/dImage.js');
3 | const imageRouter = express.Router();
4 | const authentication = require('../controllers/authentication');
5 |
6 | imageRouter.get('/', authentication, imageController.getImages, (req, res) => {
7 | const result = res.locals.images;
8 | return res.status(200).send(result);
9 | });
10 |
11 | imageRouter.post(
12 | '/start',
13 | authentication,
14 | imageController.startImage,
15 | (req, res) => {
16 | return res.status(200).send('running');
17 | }
18 | );
19 |
20 | imageRouter.post(
21 | '/delete',
22 | authentication,
23 | imageController.deleteImage,
24 | (req, res) => {
25 | return res.status(200).send('deleted');
26 | }
27 | );
28 |
29 | imageRouter.post(
30 | '/pull',
31 | authentication,
32 | imageController.pullImage,
33 | (req, res) => {
34 | return res.status(200);
35 | }
36 | );
37 |
38 | imageRouter.post(
39 | '/build',
40 | authentication,
41 | imageController.buildImage,
42 | (req, res) => {
43 | return res.status(200);
44 | }
45 | );
46 |
47 | module.exports = imageRouter;
48 |
--------------------------------------------------------------------------------
/server/routers/promMetrics.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const promContainerController = require('../controllers/promMetrics');
3 | const yamlParserController = require('../controllers/ymlParser');
4 | const metricQueriesController = require('../controllers/metricQueries');
5 | const timeConversionController = require('../controllers/timeConversion');
6 | const cadvisorStartController = require('../controllers/cadvisorStart');
7 | const nodeExporter = require('../controllers/nodeExporter');
8 | const promMetricsRouter = express.Router();
9 |
10 |
11 | promMetricsRouter.get('/promStart',
12 | promContainerController.restartProm,
13 | promContainerController.startProm,
14 | (req, res) => {
15 | return res.status(200).send('it worked!');
16 | }
17 | );
18 |
19 |
20 | promMetricsRouter.get('/cadvisorStart',
21 | cadvisorStartController.restartCadvisor,
22 | cadvisorStartController.startCadvisor,
23 | (req, res) => {
24 | return res.status(200).send('it worked!');
25 | }
26 | );
27 |
28 | promMetricsRouter.get('/',
29 | timeConversionController.unixTime,
30 | metricQueriesController.getMetrics,
31 | (req, res) => {
32 | return res.status(200).json(res.locals.values);
33 | }
34 | )
35 |
36 | //route for node-exporter
37 | promMetricsRouter.get('/node-exporter',
38 |
39 | nodeExporter.check,
40 | nodeExporter.check,
41 | nodeExporter.restart,
42 | nodeExporter.start,
43 | (req, res) => {
44 | return res.status(200).send('Node-exporter worked');
45 | }
46 | )
47 |
48 | module.exports = promMetricsRouter;
--------------------------------------------------------------------------------
/server/routers/user.js:
--------------------------------------------------------------------------------
1 | const { Router } = require('express');
2 | const conController = require('../controllers/dContainer.js');
3 | const userController = require('../controllers/userController.js');
4 | const isAuth = require('../controllers/isAuth');
5 |
6 |
7 | const userRouter = Router();
8 |
9 | //route handler
10 | userRouter.post('/signup', userController.createUser, (req, res) => {
11 | res.status(200).send({ id: res.locals.id, token: res.locals.token });
12 | });
13 |
14 | userRouter.post('/login',
15 | userController.userLogin,
16 | (req, res) => {
17 | res.status(200).send({
18 | id: res.locals.id,
19 | token: res.locals.token,
20 | });
21 | });
22 |
23 | userRouter.get('/me', isAuth, userController.me,
24 | (req, res) => {
25 | res
26 | .status(200)
27 | .send({
28 | id: res.locals.id,
29 | token: res.locals.token,
30 | username: res.locals.username,
31 | })
32 | });
33 |
34 | userRouter.get('/checkme', isAuth, userController.me, (req, res) => {
35 | res
36 | .status(200)
37 | .send({
38 | id: res.locals.id,
39 | token: res.locals.token,
40 | username: res.locals.username,
41 | });
42 | });
43 |
44 | module.exports = userRouter;
45 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const app = express();
3 | const path = require('path');
4 | const PORT = 3000;
5 | const cors = require('cors');
6 |
7 | //import routers
8 | const containerRouter = require('./routers/dContainer.js');
9 | const promMetricsRouter = require('./routers/promMetrics.js');
10 | const imageRouter = require('./routers/dImage.js');
11 | const userRouter = require('./routers/user.js');
12 |
13 | app.use(express.json());
14 | app.use(express.urlencoded({ extended: true }));
15 | app.use(cors());
16 |
17 | app.get('/', (req, res) => {
18 | return res.sendStatus(200);
19 | });
20 |
21 | //routing routers
22 | app.use('/api/user', userRouter);
23 | app.use('/api/containers', containerRouter);
24 | app.use('/api/images', imageRouter);
25 |
26 | //routing for prometheus metrics
27 | app.use('/api/metrics', promMetricsRouter);
28 |
29 | //unknown path handler
30 | app.use('*', (req, res) => {
31 | res.status(404).send('That is an unknown url');
32 | });
33 |
34 | //global error handlings
35 | app.use((err, req, res, next) => {
36 | const defaultErr = {
37 | log: 'Express error handler caught an unknown middleware error',
38 | status: 500,
39 | message: { err: 'An error occurred' },
40 | };
41 | const errorObj = Object.assign(defaultErr, err);
42 | console.log(errorObj.log);
43 | res.status(errorObj.status).json(errorObj.message);
44 | });
45 |
46 | app.listen(PORT, () => console.log(`Listening on port ${PORT}`));
47 |
48 | module.exports = { app };
49 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "lib": [
6 | "dom",
7 | "es2015",
8 | "es2016",
9 | "es2017"
10 | ],
11 | "allowJs": true,
12 | "jsx": "react",
13 | "sourceMap": true,
14 | "outDir": "./dist",
15 | "strict": true,
16 | "esModuleInterop": true,
17 | }
18 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | module.exports = {
5 | mode: process.env.NODE_ENV,
6 | entry: './client/index.js',
7 | target: 'web',
8 | // "electron-renderer",
9 | devtool: 'inline-source-map',
10 | module: {
11 | rules: [
12 | {
13 | test: /\.(js|jsx|ts|tsx)$/,
14 | exclude: /node_modules/,
15 | loader: 'babel-loader',
16 | options: {
17 | presets: ['@babel/preset-env', '@babel/preset-react'],
18 | plugins: [
19 | '@babel/plugin-transform-runtime',
20 | '@babel/transform-async-to-generator',
21 | ],
22 | },
23 | },
24 | {
25 | test: /\.s[ac]ss$/i,
26 | exclude: /node_modules/,
27 | use: [
28 | 'style-loader',
29 | 'css-loader',
30 | 'resolve-url-loader',
31 | 'sass-loader',
32 | ],
33 | },
34 | {
35 | test: /\.svg$/,
36 | use: [
37 | {
38 | loader: 'svg-url-loader',
39 | options: {
40 | limit: 10000,
41 | },
42 | },
43 | ],
44 | },
45 | {
46 | test: /\.css$/,
47 | use: [
48 | 'style-loader',
49 | {
50 | loader: 'css-loader',
51 | options: {
52 | importLoaders: 1,
53 | modules: true,
54 | },
55 | },
56 | ],
57 | include: /\.module\.css$/,
58 | },
59 | {
60 | test: /\.css$/,
61 | use: ['style-loader', 'css-loader'],
62 | exclude: /\.module\.css$/,
63 | },
64 | ],
65 | },
66 | devServer: {
67 | historyApiFallback: true,
68 | compress: true,
69 | hot: true,
70 | port: 8080,
71 | publicPath: '/build/',
72 | proxy: {
73 | '/api': 'http://localhost:3000',
74 | },
75 | },
76 | output: {
77 | path: path.resolve(__dirname, 'build'),
78 | filename: 'bundle.js',
79 | },
80 | resolve: {
81 | extensions: ['.tsx', '.ts', '.js', '.jsx'],
82 | mainFields: ['main', 'module', 'browser'],
83 | },
84 | plugins: [
85 | new HtmlWebpackPlugin({
86 | template: './index.html',
87 | }),
88 | ],
89 | };
90 |
--------------------------------------------------------------------------------