diff --git a/.DS_Store b/.DS_Store index 396deb67..9b041fd8 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml new file mode 100644 index 00000000..da814397 --- /dev/null +++ b/.github/workflows/jest.yml @@ -0,0 +1,33 @@ +name: Jest +on: + pull_request: + types: [opened, edited, reopened, synchronize] + branches: + - dev +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "18.13.0" + + - name: Cache node modules + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Install Dependencies + run: npm install + + - name: Run the tests + run: npm test diff --git a/.gitignore b/.gitignore index d00dbc12..716c2a67 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,7 @@ src/database/docketeerdb yarn.lock coverage -.history \ No newline at end of file +.history +src/.DS_Store +.gitignore +src/.DS_Store diff --git a/README.md b/README.md index 6dd38d82..cd47aa60 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ root └─ src ``` -4. In the .env file, configure the following environment variables for Twilio API, Slack Webhook, and Postgres URI. Refer to [Twilio](#-Twilio) setup section below. The Postgres URI is the only field that is required, others are optional. +4. In the .env file, configure the following environment variables for Twilio API, Slack Webhook, and Postgres URI. Refer to [Twilio](#-Twilio) setup section below. The Postgres URI is the only field that is required, others are optional. Create your own database to house user information and insert your URI in this file. ```js // .env TWILIO_NUMBER = '' @@ -98,13 +98,30 @@ You are all set! Now just enter the following command to start up Docketeer! npm run dev ``` -To log in as sysadmin, use the following credentials +For now, the sign up function will create a System Admin user. + + +## Returning Users: Version Update +1. Navigate to the project directory and **add upstream** this [repository](https://github.com/open-source-labs/Docketeer.git) to your cloned fork. ``` -username: sysadmin -password: belugas +git remote add upstream https://github.com/open-source-labs/Docketeer.git +``` + +2. Pull down the updates. +``` +git pull origin upstream +``` + +3. Install new dependencies. +``` +npm install +``` + +4. You are all set! Now just enter the following command to start up Docketeer! +``` +npm run dev ``` -To change the system admin password, create a new user with your preferred credentials, then change the role and role_id manually in the database. ## Twilio setup @@ -136,7 +153,7 @@ You can view a list of running and exited containers, available images, volume h ### ➮ Live Metrics Users have real-time access to the total amount of resources (CPU, memory usage) that your containers are using and total block IO bytes by image over specific time periods. -![alt text](assets/docketeer-metrics.gif) +![alt text](assets/metrics.gif) ### ➮ Uploading Within the Image and Docker Compose tab, you pull images from DockerHub by providing `repo:version` or uploading a `.yml` file. @@ -144,7 +161,7 @@ Within the Image and Docker Compose tab, you pull images from DockerHub by provi ### ➮ Process Logs View process logs from any number of running or stopped containers. The table is both exportable and sortable by any parameter. You can filter logs by specifying the number of logs that you wish to receive (tail) as well as time (since). Process logs will help you analyze and debug problems faster by offering insights into what went wrong. -![alt text](assets/docketeer-process-logs.gif) +![alt text](assets/logs.gif)
For a full demo of Docketeer's features, visit [docketeer.org](https://www.docketeer.org/demo). @@ -174,6 +191,10 @@ npm run test Read our [contributing guide](https://github.com/open-source-labs/Docketeer/blob/master/CONTRIBUTING.md) for more information on how to purpose bugfixes and improvements to Docketeer. ### Authors +- Nathan Cho [@nathanycho](https://github.com/nathanycho) | [LinkedIn](https://www.linkedin.com/in/nathanycho/) +- Garima Bhatia [@GarimaB06](https://github.com/GarimaB06) | [LinkedIn](https://www.linkedin.com/in/garimab06/) +- Eshaan Joshi [@eshaan32](https://github.com/eshaan32) | [LinkedIn](https://www.linkedin.com/in/eshaanjoshi/) +- Jonathan Wong [@WongJonathann](https://github.com/WongJonathann) | [LinkedIn](https://www.linkedin.com/in/jon-wong-00/) - Sarah Moosa [@Sbethm](https://github.com/Sbethm) | [LinkedIn](https://www.linkedin.com/in/sarah-moosa-4b05721b6/) - Cedar Cooper [@CedarCooper](https://github.com/CedarCooper) | [LinkedIn](https://www.linkedin.com/in/cedar-cooper/) - Tiffany Chau [@tiffanynchau](https://github.com/tiffanynchau/) | [LinkedIn](https://www.linkedin.com/in/tiffanynchau/) diff --git a/__tests__/ContainersTab.test.js b/__tests__/ContainersTab.test.js index 92bc0e1d..5b3c2d03 100644 --- a/__tests__/ContainersTab.test.js +++ b/__tests__/ContainersTab.test.js @@ -50,7 +50,7 @@ describe('Containers', () => { test('Name of container should properly display', ()=>{ const h3 = screen.getAllByRole('heading', { level: 3 }); const name = h3[0].innerHTML; - expect(name).toEqual('Name: blissful_matsumoto'); + expect(name).toEqual('blissful_matsumoto'); }); test('Stop button is called', async () => { @@ -61,10 +61,10 @@ describe('Containers', () => { test('Toggle Display button works', () => { render(); const button = screen.getAllByRole('button'); - expect(button[4]).toHaveTextContent('Show Details'); - fireEvent.click(button[4]); - expect(button[4]).toHaveTextContent('Hide Details'); - + expect(button[0]).toHaveTextContent('Show Details'); + fireEvent.click(button[0]); + expect(button[0]).toHaveTextContent('Hide Details'); + expect(button[1]).toHaveTextContent('STOP'); }); }); @@ -78,7 +78,7 @@ describe('Containers', () => { test('Name of container should properly display', () => { const name = screen.getAllByText('zealous'); - expect(name).toHaveLength(2); + expect(name).toHaveLength(1); }); test('Run and remove button should fire', async () => { @@ -89,9 +89,7 @@ describe('Containers', () => { await fireEvent.click(removeButton); expect(runButton).toBeCalled; expect(removeButton).toBeCalled; - }); - }); }); diff --git a/__tests__/ImageTab.test.js b/__tests__/ImageTab.test.js index 3b6e8a51..4f64eaf2 100644 --- a/__tests__/ImageTab.test.js +++ b/__tests__/ImageTab.test.js @@ -1,4 +1,5 @@ import React from 'react'; +import * as helper from '../src/components/helper/commands'; import { describe, beforeEach, expect, test, jest } from '@jest/globals'; import Images from '../src/components/tabs/Images'; import { @@ -57,14 +58,15 @@ describe('Images', () => { }); // currently gets stuck at window.runExec method --> reads undefined - // describe('pull button on click', () => { - // test('fires pull button functionality', () => { - // const { container } = render(); - // const pullButton = screen.getByRole('button', { name: 'Pull' }); - // fireEvent.click(pullButton); - // expect(pullButton).toBeCalled; - // }); - // }); + describe('pull button on click', () => { + test('fires pull button functionality', () => { + // const { container } = render(); + const pullButton = screen.getByRole('button', { name: 'PULL' }); + fireEvent.click(pullButton); + expect(pullButton).toBeCalled; + expect(Images.handleClick).toBeCalled; + }); + }); describe('Images', () => { test('Renders an image if one is found', () => { diff --git a/__tests__/ListReducer.test.js b/__tests__/ListReducer.test.js index 64b53896..f0163e54 100644 --- a/__tests__/ListReducer.test.js +++ b/__tests__/ListReducer.test.js @@ -1,127 +1,134 @@ -import containerListReducer from '../src/redux/reducers/containerListReducer'; // import containerList reducer -import imageListReducer from '../src/redux/reducers/imageListReducer'; // import imageListReducer reducer -import {describe, beforeEach, expect, test} from '@jest/globals'; +import containerListReducer from '../src/redux/reducers/containerListReducer' // import containerList reducer +import imageListReducer from '../src/redux/reducers/imageListReducer' // import imageListReducer reducer +import { describe, beforeEach, expect, test } from '@jest/globals' describe('Dockeeter reducer', () => { - let state; + let state beforeEach(() => { state = { imagesList: [], runningList: [], - stoppedList: [] - }; - }); + stoppedList: [], + hostStats: {}, + } + }) describe('Action Types', () => { test('Should return initial state if type is invalid', () => { - expect(containerListReducer(state, { type: 'FakeActionType' })).toBe(state); - }); - }); + expect(containerListReducer(state, { type: 'FakeActionType' })).toBe( + state, + ) + }) + }) + // REFRESH_HOST_DATA + describe('REFRESH_HOST_DATA', () => { + test('Should refresh host data', () => { + expect(state.hostStats).toEqual({}) + let action = { + type: 'REFRESH_HOST_DATA', + payload: { cpuPerc: 24.45, memPerc: 95.08 }, + } + }) + }) describe('REFRESH_RUNNING_CONTAINERS', () => { test('Should return a different state with each reducer invocation', () => { - expect(state.runningList.length).toEqual(0); + expect(state.runningList.length).toEqual(0) let action = { type: 'REFRESH_RUNNING_CONTAINERS', - payload: [{ cid: '123' }, { cid: '456' }] - }; - let newState = containerListReducer(state, action); - expect(newState.runningList.length).toEqual(2); + payload: [{ cid: '123' }, { cid: '456' }], + } + let newState = containerListReducer(state, action) + expect(newState.runningList.length).toEqual(2) action = { type: 'REFRESH_RUNNING_CONTAINERS', - payload: [{ cid: '789' }] - }; - newState = containerListReducer(state, action); - expect(newState.runningList.length).toEqual(1); - expect(newState.runningList[0].cid).toEqual( - '789' - ); - }); - }); + payload: [{ cid: '789' }], + } + newState = containerListReducer(state, action) + expect(newState.runningList.length).toEqual(1) + expect(newState.runningList[0].cid).toEqual('789') + }) + }) describe('REFRESH_STOPPED_CONTAINERS', () => { test('should overwrite the stoppedList array in the state to update it', () => { - expect(state.stoppedList.length).toEqual(0); + expect(state.stoppedList.length).toEqual(0) let action = { type: 'REFRESH_STOPPED_CONTAINERS', - payload: [{ cid: '123' }, { cid: '456' }] - }; - let newState = containerListReducer(state, action); - expect(newState.stoppedList.length).toEqual(2); + payload: [{ cid: '123' }, { cid: '456' }], + } + let newState = containerListReducer(state, action) + expect(newState.stoppedList.length).toEqual(2) action = { type: 'REFRESH_STOPPED_CONTAINERS', - payload: [{ cid: '789' }] - }; - newState = containerListReducer(state, action); - expect(newState.stoppedList.length).toEqual(1); - expect(newState.stoppedList[0].cid).toEqual( - '789' - ); - }); - }); + payload: [{ cid: '789' }], + } + newState = containerListReducer(state, action) + expect(newState.stoppedList.length).toEqual(1) + expect(newState.stoppedList[0].cid).toEqual('789') + }) + }) describe('REFRESH_IMAGES', () => { test('should overwrite the imagesList array in the state to update it', () => { - expect(state.imagesList.length).toEqual(0); + expect(state.imagesList.length).toEqual(0) let action = { type: 'REFRESH_IMAGES', - payload: [{ imgid: '123' }, { imgid: '456' }] - }; - expect(imageListReducer(state, action).imagesList.length).toEqual(2); - action = { type: 'REFRESH_IMAGES', payload: [{ imgid: '789' }] }; - expect(imageListReducer(state, action).imagesList.length).toEqual(1); - expect(imageListReducer(state, action).imagesList[0].imgid).toEqual('789'); - }); - }); + payload: [{ imgid: '123' }, { imgid: '456' }], + } + expect(imageListReducer(state, action).imagesList.length).toEqual(2) + action = { type: 'REFRESH_IMAGES', payload: [{ imgid: '789' }] } + expect(imageListReducer(state, action).imagesList.length).toEqual(1) + expect(imageListReducer(state, action).imagesList[0].imgid).toEqual('789') + }) + }) describe('REMOVE_CONTAINER', () => { test('should remove the specified container from the stoppedList array in the state', () => { const newState = { - stoppedList: [{ ID: '123' }, { ID: '456' }] - }; - const action = { type: 'REMOVE_CONTAINER', payload: '123' }; - const storedValue = containerListReducer(newState,action); - expect(storedValue.stoppedList[0].ID).toEqual( - '456' - ); - }); - }); + stoppedList: [{ ID: '123' }, { ID: '456' }], + } + const action = { type: 'REMOVE_CONTAINER', payload: '123' } + const storedValue = containerListReducer(newState, action) + expect(storedValue.stoppedList[0].ID).toEqual('456') + }) + }) describe('STOP_RUNNING_CONTAINER', () => { test('should remove a specified container from the runningList and add it to the stoppedList', () => { let newState = { runningList: [{ ID: '123' }, { ID: '456' }], - stoppedList: [] - }; - const action = { type: 'STOP_RUNNING_CONTAINER', payload: '123' }; - newState = containerListReducer(newState, action); - expect(newState.runningList[0].ID).toEqual('456'); - }); - }); + stoppedList: [], + } + const action = { type: 'STOP_RUNNING_CONTAINER', payload: '123' } + newState = containerListReducer(newState, action) + expect(newState.runningList[0].ID).toEqual('456') + }) + }) describe('RUN_STOPPED_CONTAINER', () => { test('should remove a specified container from the stoppedList', () => { const newState = { runningList: [], - stoppedList: [{ ID: '123' }, { ID: '456' }] - }; - const action = { type: 'RUN_STOPPED_CONTAINER', payload: '123' }; + stoppedList: [{ ID: '123' }, { ID: '456' }], + } + const action = { type: 'RUN_STOPPED_CONTAINER', payload: '123' } expect(containerListReducer(newState, action).stoppedList[0].ID).toEqual( - '456' - ); - }); - }); + '456', + ) + }) + }) describe('REMOVE_IMAGE', () => { test('should remove a specified image from the imagesList', () => { const newState = { - imagesList: [{ id: '123' }, { id: '456' }] - }; - const action = { type: 'REMOVE_IMAGE', payload: '123' }; - expect(imageListReducer(newState, action).imagesList[0].id).toEqual('456'); - }); - }); -}); \ No newline at end of file + imagesList: [{ id: '123' }, { id: '456' }], + } + const action = { type: 'REMOVE_IMAGE', payload: '123' } + expect(imageListReducer(newState, action).imagesList[0].id).toEqual('456') + }) + }) +}) diff --git a/__tests__/MetricsTab.test.js b/__tests__/MetricsTab.test.js index 3adce68e..f2525e4a 100644 --- a/__tests__/MetricsTab.test.js +++ b/__tests__/MetricsTab.test.js @@ -1,37 +1,66 @@ import React, { Component } from 'react'; import Metrics from '../src/components/tabs/Metrics'; -import {describe, expect, test, jest} from '@jest/globals'; +import {describe, expect, test, beforeEach, afterEach, jest} from '@jest/globals'; import '@testing-library/jest-dom'; +import { MemoryRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import store from '../src/renderer/store'; -import { create } from 'react-test-renderer'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { act } from 'react-test-renderer'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import fetchMock from 'jest-fetch-mock'; const props = { - runningList: [{ BlockIO: '1B/2B', ID: '6f49565a501c', CPUPerc: '20.00%', MemPerc: '0.00%', MemUsage: '5B/6B', Name: 'checkpoint_nginx_1', NetIO: '3B/4B', PIDs: '0' }, { BlockIO: '3B/4B', ID: '6f49565a501c', CPUPerc: '30.00%', MemPerc: '20.00%', MemUsage: '5B/6B', Name: 'checkpoint_nginx_2', NetIO: '5B/6B', PIDs: '0' }] + runningList: [ + { + ID: 'a802306eeac3', + Name: 'blissful_matsumoto', + Image: 'postgres:15', + CPUPerc: '0.17', + MemPerc: '0.11', + MemUsage: '2.19MiB / 1.94MiB', + NetIO: '796kB/0kB', + BlockIO: '34029.57kB / 4.1kB', + PIDs: '5' + } + ], + threshold: [ + 80.00, // state.session.cpu_threshold: + 80.00, // state.session.mem_threshold: + ], }; +fetchMock.enableMocks(); + +// // This test tab needs work as we are unable to render the metrics component for testing // describe('Metrics tab should render', () => { -// beforeEach(()=>{ -// render( -// -// -// -// ); -// }); +// beforeEach( async () => { +// fetch.resetMocks(); -// test('Metrics', ()=>{ -// expect(1).toBe(1); -// }); -// }); +// async function metricsRenderer(){ +// return ; +// } +// const MetricsTab = await metricsRenderer(); +// console.log(MetricsTab); +// await act( () => { +// render( +// +// +// {/* */} +// {MetricsTab} +// +// +// ); +// // console.log(screen); +// screen.debug(); +// }); +// }); +// }); //* Dummy Test describe('dummy test', () => { test('dummy test', () => { expect(2 + 2).toBe(4); }); - -}); - +}); \ No newline at end of file diff --git a/__tests__/ProcessLogHelper.test.js b/__tests__/ProcessLogHelper.test.js index aaddc524..2767502a 100644 --- a/__tests__/ProcessLogHelper.test.js +++ b/__tests__/ProcessLogHelper.test.js @@ -9,18 +9,19 @@ import { import {describe, beforeEach,afterEach, expect, test} from '@jest/globals'; describe('makeArrayOfObjects', () => { - test('returns an array', () => { - const string = `HelloZ from Docker! - - This message shows that your installation appears to be working correctly. - + test('returns a result array with appropriately constructed object elements', () => { + const string2 = `2022-12-22T19:36:44.564948926Z 2022/12/22 19:36:44 [notice] 1#1: start worker process 22\n2022-12-22T20:12:01.081323805Z 2022/12/22 20:12:01 [notice] 22#22: gracefully shutting down `; - const result = makeArrayOfObjects(string); + + const result = makeArrayOfObjects(string2); + console.log(result); expect(result).toBeInstanceOf(Array); expect(result.length).toEqual(2); expect(result.containerName).toBe(undefined); - expect(result[0].logMsg).toEqual('HelloZ from Docker!'); - expect(result[0].timeStamp).toBeUndefined(); + expect(result[0].logMsg).toEqual('1#1: start worker process 22'); + expect(result[0].timeStamp).toBe('12/22/2022, 1:36:44 PM'); + expect(result[1].logMsg).toEqual('22#22: gracefully shutting down'); + expect(result[1].timeStamp).toBe('12/22/2022, 2:12:01 PM'); }); // Can be addressed through TS diff --git a/__tests__/ServerRoutes.test.js b/__tests__/ServerRoutes.test.js index 21adab0c..616ea11c 100644 --- a/__tests__/ServerRoutes.test.js +++ b/__tests__/ServerRoutes.test.js @@ -1,138 +1,93 @@ -const supertest = require('supertest'); -const request = require('supertest'); -const response = require('supertest'); -const express = require('express'); -import {describe, beforeEach, expect, test, jest} from '@jest/globals'; +import request from 'supertest'; +import response from 'supertest'; +import express from 'express'; +import { describe, beforeEach, expect, test, jest } from '@jest/globals'; const app = express(); -const signupRouter = require('../server/routes/signupRouter'); -const loginRouter = require('../server/routes/loginRouter'); -const adminRouter = require('../server/routes/adminRouter'); -const accountRouter = require('../server/routes/accountRouter'); -const apiRouter = require('../server/routes/apiRouter'); -const dbRouter = require('../server/routes/dbRouter'); -const initRouter = require('../server/routes/initRouter'); -const logoutRouter = require('../server/routes/logoutRouter'); -const settingsRouter = require('../server/routes/settingsRouter'); - - +import accountRouter from '../server/routes/accountRouter'; +import adminRouter from '../server/routes/adminRouter'; +import apiRouter from '../server/routes/apiRouter'; +import commandRouter from '../server/routes/commandRouter'; +import signupRouter from '../server/routes/signupRouter'; +import loginRouter from '../server/routes/loginRouter'; +import dbRouter from '../server/routes/dbRouter'; +import initRouter from '../server/routes/initRouter'; +import logoutRouter from '../server/routes/logoutRouter'; +import settingsRouter from '../server/routes/settingsRouter'; app.use('/test', (req, res) => { res.status(200).json({ success: true, }); }); -app.use('/signup', signupRouter); -app.use('/settings', settingsRouter); -app.use('/init', initRouter); -app.use('/login', loginRouter); -app.use('/admin', adminRouter); + app.use('/account', accountRouter); +app.use('/admin', adminRouter); app.use('/api', apiRouter); +app.use('command', commandRouter); app.use('/db', dbRouter); +app.use('/init', initRouter); +app.use('/login', loginRouter); app.use('/logout', logoutRouter); - -xdescribe('/test route', () => { - test('get request to test route', (done) => { - request(app).get('/test').expect('Content-Type', /json/).expect(200, done); - }); - test('post requeust to test route', (done) => { - request(app) - .post('/test') - .send({ random: 'info' }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200, done); - }); - test('put request to test route', (done) => { - request(app) - .put('/test') - .send({ random: 'info' }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200, done); - }); - test('delete request to test route', (done) => { - request(app) - .delete('/test') - .send({ random: 'info' }) - .set('Accept', 'application/json') - .expect('Content-Type', /json/) - .expect(200, done) - .expect(response.locals.users).toEqual(1); - }); -}); +app.use('/settings', settingsRouter); +app.use('/signup', signupRouter); /* all route testing needed */ -// signup route +// Notes from 9.0: There is no testing database / server set up, so post request testing will not work right now. -describe('/signup route', () => { - test('get request', async () => { - await request(app) - .get('/signup') - // .send({ username: 'test', email: 'test@test.com', password: 'password' }) - .expect('Content-Type', 'application/json; charset=utf-8') +// account route +describe('Account Route', () => { + test('Get request', () => { + request(app) + .get('/account') + .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) .expect(response); }); - test('post request', async () => { - await request(app) - .post('/signup') - .send({ - username: 'testwer', - email: 'test@test.com', - password: 'passwqw', - phone: '+1555555555', - }) - .set('Accept', 'application/json') - .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8'); - }); }); -// setting route -describe('Settings route', () =>{ - test('Get request should return empty mem, cpu, stopped', async () => { - await request(app) - .get('/settings') +// admin route +describe('Admin Route', () => { + test('Get request', () => { + request(app) + .get('/admin') .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) .expect(response); }); - xtest('Post request', async () => { - await request(app) - .post('/settings/insert') - .send({ - container: ['test', 'value'], - name: 'testname', - metric: 'hello' - }) +}); + +// api route +describe('Api Route', () => { + test('Get request', () => { + request(app) + .get('/api') .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) .expect(response); }); }); -// logout route -describe('Logout Route', () => { - test('Get request', () => { +// command route +describe('Command Route', () => { + test('Get refreshRunning', () => { request(app) - .get('/logout') + .get('/command/refreshRunning') .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) - .expect(response); + .expect(response); }); }); -// login route -describe('Login Route', () => { +// db route +describe('Db Route', () => { test('Get request', () => { request(app) - .get('/login') + .get('/db') .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) - .expect(response); + .expect(response); }); }); @@ -143,187 +98,77 @@ describe('Init Route', () => { .get('/init') .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) - .expect(response); + .expect(response); }); }); -// db route -describe('Db Route', () => { +// login route +describe('Login Route', () => { test('Get request', () => { request(app) - .get('/db') + .get('/login') .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) - .expect(response); + .expect(response); }); }); -// api route -describe('Api Route', () => { + +// logout route +describe('Logout Route', () => { test('Get request', () => { request(app) - .get('/api') + .get('/logout') .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) - .expect(response); + .expect(response); }); }); -// admin route -describe('Admin Route', () => { - test('Get request', () => { - request(app) - .get('/admin') + +// setting route +describe('Settings Route', () => { + test('Get request should return empty mem, cpu, stopped', async () => { + await request(app) + .get('/settings') + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200) + .expect(response); + }); + xtest('Post request', async () => { + await request(app) + .post('/settings/insert') + .send({ + container: ['test', 'value'], + name: 'testname', + metric: 'hello' + }) .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) - .expect(response); + .expect(response); }); }); -// account route -describe('Account Route', () => { - test('Get request', () => { - request(app) - .get('/account') +// signup route +describe('Signup Route', () => { + test('get request', async () => { + await request(app) + .get('/signup') + // .send({ username: 'test', email: 'test@test.com', password: 'password' }) .expect('Content-Type', 'application/json; charset=utf-8') .expect(200) - .expect(response); + .expect(response); + }); + xtest('post request', async () => { + await request(app) + .post('/signup') + .send({ + username: 'tester', + email: 'test@test.com', + password: 'passwqw', + phone: '+1555555555', + role_id: '1', + }) + .set('Accept', 'application/json') + .expect(201) + .expect('Content-Type', 'application/json; charset=utf-8'); }); }); - -// const server = require('../server/app'); - -// const request = supertest(server); - -// describe('Test Route /test', () => { -// describe('GET request on the test route', () => { -// // Test route to receive a status of 200 and a json object of success:true -// test('Should respond with a 200 status code', async () => { -// const response = await request.get('/test'); -// expect(response.status).toBe(200); -// expect(response.body.success).toBe(true); -// }); -// }); - -// describe('POST request on the test route', () => { -// // Test route to receive a status of 200 and a json object of success:true -// test('Should respond with a 200 status code', async () => { -// const response = await request.get('/test'); -// expect(response.status).toBe(200); -// expect(response.body.success).toBe(true); -// }); -// }); -// }); - -/** Docketeer 7.0 - * We could not figure out how to authenticate to postgres with Super Test so we were unable to confirm if these tests work. - */ - -// describe('Settings Route /settings', () => { -// GET request for default route -// describe('GET request /settings/', () => { -// test('Expect status code 200 and JSON object', async (done) => { -// // return request.get('/settings/').then((response) => { -// // expect(response.statusCode).toBe(200); -// // done(); -// // }); -// const response = await request.get('/settings'); -// expect(response.status).toBe(200); -// done(); -// }); -// }); -// describe('POST request /settings/insert', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/settings/insert') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); -// describe('POST request /settings/delete', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/settings/delete') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); -// describe('POST request /settings/phone', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/settings/phone') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); -// describe('POST request /settings/notification', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/settings/notification') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); -// describe('POST request /settings/monitoring', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/settings/monitoring') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); -// describe('POST request /settings/gitLinks', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/settings/gitLinks') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); -// }); - -// describe('Initiate Metric Database Route /init', () => { -// GET request for default route -// describe('GET request /init/', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server).post('/init/').expect(200).expect('Content/Type', /json/); -// }); -// }); - -// // ! Expect this to fail bc of no json return -// describe('POST request /init/timezone', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/init/timezone') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); - -// describe('POST request /init/github', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/init/github') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); - -// describe('POST request /init/addMetrics', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/init/addMetrics') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); - -// describe('POST request /init/getMetrics', () => { -// test('Expect status code 200 and JSON object', () => { -// request(server) -// .post('/init/getMetrics') -// .expect(200) -// .expect('Content/Type', /json/); -// }); -// }); -// }); - - diff --git a/__tests__/dockerComposeReducer.test.js b/__tests__/dockerComposeReducer.test.js new file mode 100644 index 00000000..0e97f319 --- /dev/null +++ b/__tests__/dockerComposeReducer.test.js @@ -0,0 +1,50 @@ +import dockerComposeReducer from '../src/redux/reducers/dockerComposeReducer' +import { describe, beforeEach, expect, test, jest } from '@jest/globals' +import '@testing-library/jest-dom' + +describe('Docker compose reducer', () => { + let state + + beforeEach(() => { + state = { + runningList: [], + stoppedList: [], + networkList: [], + composeStack: [], + hostStats: {}, + } + }) + + describe('Action Types', () => { + test('Should return initial state if type is invalid', () => { + const nonExistentAction = 'FakeActionType' + expect(dockerComposeReducer(state, { type: nonExistentAction })).toBe( + state, + ) + }) + }) + describe('GET_CONTAINER_STACKS', () => { + test('Should return a different state with each reducer invocation', () => { + expect(state.composeStack.length).toEqual(0) + let action = { + type: 'GET_CONTAINER_STACKS', + payload: [ + { + CreatedAt: '2020-01-0315:34:16.833926458+0000UTC', + Driver: 'bridge', + ID: 'dummyId', + IPv6: 'false', + Internal: 'false', + Labels: + 'com.docker.compose.network=default,com.docker.compose.project=database,com.docker.compose.version=2.13.0', + Name: 'database_default', + Scope: 'local', + }, + ], + } + let newState = dockerComposeReducer(state, action) + expect(newState.composeStack.length).toEqual(1) + expect(newState.composeStack[0].ID).toEqual('dummyId') + }) + }) +}) diff --git a/__tests__/loginPage.test.js b/__tests__/loginPage.test.js index 8759df9b..2253a63a 100644 --- a/__tests__/loginPage.test.js +++ b/__tests__/loginPage.test.js @@ -2,7 +2,7 @@ import '@testing-library/jest-dom'; import React from 'react'; import {render, fireEvent, screen} from '@testing-library/react'; import App from '../src/renderer/App'; -import Login from '../src/components/login/login'; +import Login from '../src/components/Login'; import { MemoryRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import store from '../src/renderer/store'; @@ -11,11 +11,6 @@ import fetchMock from 'jest-fetch-mock'; import { act } from 'react-test-renderer'; import Docketeer from '../assets/docketeer-title.png'; -const mockedUsedNavigate = jest.fn(); -// jest.mock('react-router-dom', () => ({ -// useNavigate: () => mockedUsedNavigate, -// })); - fetchMock.enableMocks(); describe('Login Page Renders', () => { @@ -30,9 +25,9 @@ describe('Login Page Renders', () => { ); }); - screen.debug(); + // screen.debug(); }); - + test('Username accepts input', async () => { const username = document.querySelector('#username'); await fireEvent.change(username, {target: {value:'sysadmin'}}); @@ -45,14 +40,59 @@ describe('Login Page Renders', () => { expect(password.value).toBe('belugas'); }); - test('Login button', async () => { - fetch.mockResponseOnce(JSON.stringify({ username: 'sysadmin', password: 'belugas' })); - const alert = window.alert = jest.fn(); - const loginButton = screen.getByRole('button'); + test('Login button is clicked, throwing error', async () => { + const spy = jest.spyOn(console, 'log'); + + const username = document.querySelector('#username'); + await fireEvent.change(username, { target: { value: '' } }); + + const password = document.querySelector('#password'); + await fireEvent.change(password, {target: {value:''}}); + + // select login button + const loginButton2 = screen.getByRole('login'); + // fire event to click login button await act(()=>{ - fireEvent.click(loginButton); + fireEvent.click(loginButton2); }); - // need to fix issue of localhost/4000 not rendering anything after you login + + // assert the console logs for errors were throw + expect(spy).toHaveBeenCalled(); + }); + + test('Login button is clicked, logging in', async () => { + // test to submit a valid login, and redirect to a new page + + fireEvent.change(document.querySelector('#username'), { target: { value: 'test2' } }); + + fireEvent.change(document.querySelector('#password'), { target: { value: 'codesmith123' } }); + + const loginButton = screen.getByRole('login'); + await act(()=>{ + fireEvent.click(screen.getByRole('login')); + }); + + expect(loginButton).toBeCalled; + + // Needs Completion Docketeam 10.0 + + }); + + test('Register Button navigates to Sign Up Page', async () => { + // select the register button + const registerButton = screen.getByRole('register'); + // fire event to click the button, navigating to new page + await act(() => { + fireEvent.click(registerButton); + }); + // assert that the event happened + expect(registerButton).toBeCalled; + + // on new page, select the title h1 element -> 'Sign Up' + const title = document.querySelector('h1'); + + // assert that the title element has the SIgn Up text + expect(title.textContent).toBe('Sign Up'); }); test('Docketeer Image', async () => { diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 00000000..5925815c Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/archive/.DS_Store b/assets/archive/.DS_Store new file mode 100644 index 00000000..a881d8e6 Binary files /dev/null and b/assets/archive/.DS_Store differ diff --git a/assets/docketeer-metrics.gif b/assets/archive/docketeer-metrics.gif similarity index 100% rename from assets/docketeer-metrics.gif rename to assets/archive/docketeer-metrics.gif diff --git a/assets/docketeer-process-logs.gif b/assets/archive/docketeer-process-logs.gif similarity index 100% rename from assets/docketeer-process-logs.gif rename to assets/archive/docketeer-process-logs.gif diff --git a/assets/docketeer-schema.png b/assets/archive/docketeer-schema.png similarity index 100% rename from assets/docketeer-schema.png rename to assets/archive/docketeer-schema.png diff --git a/assets/archive/docketeer-title.png b/assets/archive/docketeer-title.png new file mode 100644 index 00000000..e138af3d Binary files /dev/null and b/assets/archive/docketeer-title.png differ diff --git a/assets/archive/docketeer-title2.png b/assets/archive/docketeer-title2.png new file mode 100644 index 00000000..579f00e7 Binary files /dev/null and b/assets/archive/docketeer-title2.png differ diff --git a/assets/processlogs.gif b/assets/archive/processlogs.gif similarity index 100% rename from assets/processlogs.gif rename to assets/archive/processlogs.gif diff --git a/assets/compress-icon.svg b/assets/compress-icon.svg new file mode 100644 index 00000000..a96b4714 --- /dev/null +++ b/assets/compress-icon.svg @@ -0,0 +1,7 @@ + diff --git a/assets/docketeer-title.png b/assets/docketeer-title.png index e138af3d..a8f6ee13 100644 Binary files a/assets/docketeer-title.png and b/assets/docketeer-title.png differ diff --git a/assets/docketeer-title2.png b/assets/docketeer-title2.png index 579f00e7..7c848e9b 100644 Binary files a/assets/docketeer-title2.png and b/assets/docketeer-title2.png differ diff --git a/assets/expand-icon.svg b/assets/expand-icon.svg new file mode 100644 index 00000000..e303f64d --- /dev/null +++ b/assets/expand-icon.svg @@ -0,0 +1,7 @@ + diff --git a/assets/logs.gif b/assets/logs.gif new file mode 100644 index 00000000..00ab0add Binary files /dev/null and b/assets/logs.gif differ diff --git a/assets/metrics.gif b/assets/metrics.gif new file mode 100644 index 00000000..c1202b41 Binary files /dev/null and b/assets/metrics.gif differ diff --git a/package.json b/package.json index c5106cb3..dc6b05fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docketeer", - "version": "7.0.0", + "version": "9.0.0", "license": "MIT", "description": "A Docker Visualizer", "author": "Team Docketeer", @@ -8,7 +8,7 @@ "scripts": { "dev:electron": "cross-env NODE_ENV=development webpack --config webpack.electron.config.js --mode development && electron .", "dev:react": "cross-env NODE_ENV=development webpack-dev-server --config webpack.react.config.js --mode development", - "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --config webpack.react.config.js --mode development\" \"nodemon server/server.js\" \"npm run dev:electron\"", + "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --config webpack.react.config.js --mode development\" \"nodemon server/server.ts\" \"npm run dev:electron\"", "buildWeb": "webpack --config=./webpack.react.config.js", "test": "jest --verbose" }, @@ -19,7 +19,7 @@ "@mui/material": "^5.10.8", "@mui/x-data-grid": "^5.17.7", "@testing-library/user-event": "^14.4.3", - "@types/node": "^18.8.3", + "@types/node": "^18.11.18", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", "@types/react-router-dom": "^5.3.3", @@ -27,6 +27,8 @@ "colors": "^1.4.0", "cors": "^2.8.5", "node": "^19.2.0", + "os": "^0.1.2", + "os-utils": "^0.0.14", "pg": "^8.8.0", "react": "^18.2.0", "react-chartjs-2": "^4.3.1", @@ -53,7 +55,11 @@ "@reduxjs/toolkit": "^1.9.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", + "@types/bcryptjs": "^2.4.2", + "@types/cors": "^2.8.13", "@types/jest": "^29.2.3", + "@types/nodemailer": "^6.4.7", + "@types/os-utils": "^0.0.1", "@types/pg": "^8.6.5", "@types/react-csv": "^1.1.3", "axios": "^1.0.0", @@ -106,4 +112,4 @@ "\\.(css|scss)$": "/__mocks__/styleMock.js" } } -} +} \ No newline at end of file diff --git a/server/app.js b/server/app.ts similarity index 50% rename from server/app.js rename to server/app.ts index b2210bc6..cc93c89e 100644 --- a/server/app.js +++ b/server/app.ts @@ -1,43 +1,44 @@ -const express = require('express'); -const path = require('path'); -const cors = require('cors'); -const colors = require('colors'); +import express, { NextFunction, Request, Response } from 'express'; +// import path from'path'; +import cors from 'cors'; +// import colors from'colors'; -const signupRouter = require('./routes/signupRouter'); - -const signupSysAdminRouter = require('./routes/signupSysAdminRouter'); -const loginRouter = require('./routes/loginRouter'); -const adminRouter = require('./routes/adminRouter'); -const accountRouter = require('./routes/accountRouter'); -const apiRouter = require('./routes/apiRouter'); -const dbRouter = require('./routes/dbRouter'); -const initRouter = require('./routes/initRouter'); -const logoutRouter = require('./routes/logoutRouter'); -const settingsRouter = require('./routes/settingsRouter'); +import accountRouter from './routes/accountRouter'; +import adminRouter from './routes/adminRouter'; +import apiRouter from './routes/apiRouter'; +import commandRouter from './routes/commandRouter'; +import dbRouter from './routes/dbRouter'; +import initRouter from './routes/initRouter'; +import loginRouter from './routes/loginRouter'; +import logoutRouter from './routes/logoutRouter'; +import settingsRouter from './routes/settingsRouter'; +import signupRouter from './routes/signupRouter'; +import { ServerError } from '../types'; const app = express(); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); -app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.use(cors()); -app.use('/test', (req, res) => { +app.use('/test', (req: Request, res: Response) => { res.status(200).json({ success: true }); }); -app.use('/settings', settingsRouter); -app.use('/init', initRouter); -app.use('/signup', signupRouter); -app.use('/login', loginRouter); -app.use('/admin', adminRouter); app.use('/account', accountRouter); +app.use('/admin', adminRouter); app.use('/api', apiRouter); +app.use('/command', commandRouter); app.use('/db', dbRouter); +app.use('/init', initRouter); +app.use('/login', loginRouter); app.use('/logout', logoutRouter); +app.use('/settings', settingsRouter); +app.use('/signup', signupRouter); -app.use('/', (req, res) => { +app.use('/', (req: Request, res: Response) => { /* Reads the current URL (explains why electron crashes) const url = new URL(`${req.protocol}://${req.get('host')}${req.originalUrl}`); @@ -47,17 +48,17 @@ app.use('/', (req, res) => { return res.status(404).redirect('/'); }); -app.get('/', (req, res, next, err) => { +app.get('/', (err: ServerError, req: Request, res: Response, next: NextFunction) => { const defaultErr = { log: 'Express error handler caught unknown middleware error', status: 500, message: { err: 'An error occured' } }; - const errorObj = Object.assign(defaultErr, err); + const errorObj: ServerError = Object.assign(defaultErr, err); return res.status(errorObj.status).json(errorObj.message); }); -module.exports = app; +export default app; /* Reads the current URL (explains why electron crashes) diff --git a/server/controllers/apiController.js b/server/controllers/apiController.js deleted file mode 100644 index 8c572567..00000000 --- a/server/controllers/apiController.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @module API Controller - * @description Contains middleware that sends emails to user for container issues and signup information - */ -const nodemailer = require("nodemailer"); -const email = require("../../security/email"); - -const apiController = {}; - -// create transporter object to make sure these values are filled out in email.js -const transporter = nodemailer.createTransport({ - host: email.host, - port: email.port, - secure: true, - auth: { - user: email.username, - pass: email.password, - }, -}); - -// sends notification email when container issue occurs -apiController.sendEmailAlert = (req, res, next) => { - const { email, containerName, time, date, stopped } = req.body; - let emailBody; - - if (stopped === "true") { - emailBody = ` -

Alert: ${containerName} has stopped!

-

Container ${containerName} stopped running at ${time} on ${date}.

-

Please login to Docketeer for more details.

-
-

Warmest regards,

-

Team Docketeer

`; - } else { - const { percent, type, threshold } = req.body; - emailBody = ` -

Alert: ${containerName} has breached the ${type} threshold!

-

Container ${containerName} used ${percent}% ${type} at ${time} on ${date}.

-

This exceeds the ${type} threshold of ${threshold}%.

-

Please login to Docketeer for more details.

-
-

Warmest regards,

-

Team Docketeer

`; - } - - const mailDetails = { - from: "team.docketeer@gmail.com", - to: email, - subject: "Docketeer: Container Issue", - html: `${emailBody}`, - }; - - transporter - .sendMail(mailDetails) - .then((info) => { - return next(); - }) - .catch((err) => { - return next({ - log: `Error in apiController sendEmailAlert: ${err}`, - message: { - err: "An error occured creating new user in database. See apiController.sendEmailAlert.", - }, - }); - }); -}; - -// sends email with username/password when user signs up -apiController.signupEmail = (req, res, next) => { - const { email, username, password } = req.body; - - const mailDetails = { - from: "team.docketeer@gmail.com", - to: email, - subject: "Docketeer: Account Details", - html: ` -

Welcome to Docketeer

-

We are so excited to have you onboard!

-

Username: ${username}

-

Password: ${password}

-

For any questions or concerns, please reach out to us at team.docketeer@gmail.com.

-
-

Warmest regards,

-

Team Docketeer

`, - }; - - transporter - .sendMail(mailDetails) - .then((info) => { - return next(); - }) - .catch((err) => { - return next({ - log: `Error in apiController signupEmail: ${err}`, - message: { - err: "An error occured creating new user in database. See apiController.signupEmail.", - }, - }); - }); -}; - -module.exports = apiController; diff --git a/server/controllers/apiController.ts b/server/controllers/apiController.ts new file mode 100644 index 00000000..d89a9d3e --- /dev/null +++ b/server/controllers/apiController.ts @@ -0,0 +1,106 @@ +/** + * @module API Controller + * @description Contains middleware that sends emails to user for container issues and signup information + */ + +import { Request, Response, NextFunction } from 'express'; +import nodemailer from "nodemailer"; +import email from "../../security/email"; +import { ApiController, ServerError } from '../../types'; + +// create transporter object to make sure these values are filled out in email.js +const transporter = nodemailer.createTransport({ + host: email.host, + port: email.port, + secure: true, + auth: { + user: email.username, + pass: email.password, + }, +}); + +const apiController: ApiController = { + + // sends notification email when container issue occurs + sendEmailAlert: (req: Request, res: Response, next: NextFunction) => { + const { email, containerName, time, date, stopped } = req.body; + let emailBody; + + if (stopped === "true") { + emailBody = ` +

Alert: ${containerName} has stopped!

+

Container ${containerName} stopped running at ${time} on ${date}.

+

Please login to Docketeer for more details.

+
+

Warmest regards,

+

Team Docketeer

`; + } else { + const { percent, type, threshold } = req.body; + emailBody = ` +

Alert: ${containerName} has breached the ${type} threshold!

+

Container ${containerName} used ${percent}% ${type} at ${time} on ${date}.

+

This exceeds the ${type} threshold of ${threshold}%.

+

Please login to Docketeer for more details.

+
+

Warmest regards,

+

Team Docketeer

`; + } + + const mailDetails = { + from: "team.docketeer@gmail.com", + to: email, + subject: "Docketeer: Container Issue", + html: `${emailBody}`, + }; + + transporter + .sendMail(mailDetails) + .then((info: any) => { + return next(); + }) + .catch((err: ServerError) => { + return next({ + log: `Error in apiController sendEmailAlert: ${err}`, + message: { + err: "An error occured creating new user in database. See apiController.sendEmailAlert.", + }, + }); + }); + }, + + // sends email with username/password when user signs up + signupEmail: (req: Request, res: Response, next: NextFunction) => { + const { email, username, password } = req.body; + + const mailDetails = { + from: "team.docketeer@gmail.com", + to: email, + subject: "Docketeer: Account Details", + html: ` +

Welcome to Docketeer

+

We are so excited to have you onboard!

+

Username: ${username}

+

Password: ${password}

+

For any questions or concerns, please reach out to us at team.docketeer@gmail.com.

+
+

Warmest regards,

+

Team Docketeer

`, + }; + + transporter + .sendMail(mailDetails) + .then((info: any) => { + return next(); + }) + .catch((err: ServerError) => { + return next({ + log: `Error in apiController signupEmail: ${err}`, + message: { + err: "An error occured creating new user in database. See apiController.signupEmail.", + }, + }); + }); + } +}; + +export default apiController; diff --git a/server/controllers/bcryptController.js b/server/controllers/bcryptController.js deleted file mode 100644 index dad60ace..00000000 --- a/server/controllers/bcryptController.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @module Bcrypt Controller - * @description Contains middleware that encrypts password before storing in database and compares a user's inputted password to their stored password - */ -const db = require("../models/cloudModel"); -const bcrypt = require("bcryptjs"); - -const bcryptController = {}; - -// Hash user password with bCrypt -bcryptController.hashPassword = (req, res, next) => { - const { password } = req.body; - const saltRounds = 10; - - bcrypt - .hash(password, saltRounds) - .then((hash) => { - res.locals.hash = hash; - return next(); - }) - .catch((err) => { - return next({ - log: `Error in bcryptController hashPassword: ${err}`, - message: { - err: "An error occured creating hash with bcrypt. See bcryptController.hashPassword.", - }, - }); - }); -}; - -// Hash new user password with bCrypt - User updated password -bcryptController.hashNewPassword = async (req, res, next) => { - // if there is an error property on res.locals, return next(). i.e., incorrect password entered - if (Object.prototype.hasOwnProperty.call(res.locals, "error")) { - return next(); - } - // else bCrypt the new password and move to next middleware - const { newPassword } = req.body; - const saltRounds = 10; - - await bcrypt - .hash(newPassword, saltRounds) - .then((hash) => { - res.locals.hash = hash; - return next(); - }) - .catch((err) => { - return next({ - log: `Error in bcryptController hashNewPassword: ${err}`, - message: { - err: "An error occured creating hash with bcrypt. See bcryptController.hashNewPassword.", - }, - }); - }); -}; - -/** - * @description hashes the locals property cookie. Creates a column in the database to store the hashed cookie - */ - -bcryptController.hashCookie = (req, res, next) => { - const { role_id, username } = res.locals.user; - const saltRounds = 10; - if (role_id === 1) { - bcrypt - .hash(res.locals.cookie, saltRounds) - .then((hash) => { - res.locals.user.token = hash; - db.query( - "ALTER TABLE users ADD COLUMN IF NOT EXISTS token varchar(250)" - ); - db.query("UPDATE users SET token=$1 WHERE username=$2", [ - res.locals.user.token, - username, - ]); - return next(); - }) - .catch((err) => { - return next({ - log: `Error in bcryptController hashCookeis: ${err}`, - message: { - err: "An error occured creating hash with bcrypt. See bcryptController.hashCookies.", - }, - }); - }); - } else { - return next(); - } -}; - -module.exports = bcryptController; diff --git a/server/controllers/bcryptController.ts b/server/controllers/bcryptController.ts new file mode 100644 index 00000000..e3ed13e3 --- /dev/null +++ b/server/controllers/bcryptController.ts @@ -0,0 +1,94 @@ +/** + * @module Bcrypt Controller + * @description Contains middleware that encrypts password before storing in database and compares a user's inputted password to their stored password + */ +import db from "../models/cloudModel"; +import bcrypt from "bcryptjs"; +import { Request, Response, NextFunction } from "express"; +import { BcryptController } from "../../types"; + +const bcryptController: BcryptController = { + // Hash user password with bCrypt + hashPassword: (req: Request, res: Response, next: NextFunction) => { + const { password } = req.body; + const saltRounds = 10; + + bcrypt + .hash(password, saltRounds) + .then((hash) => { + res.locals.hash = hash; + return next(); + }) + .catch((err) => { + return next({ + log: `Error in bcryptController hashPassword: ${err}`, + message: { + err: "An error occured creating hash with bcrypt. See bcryptController.hashPassword.", + }, + }); + }); + }, + + // Hash new user password with bCrypt - User updated password + hashNewPassword : async (req: Request, res: Response, next: NextFunction) => { + // if there is an error property on res.locals, return next(). i.e., incorrect password entered + if (Object.prototype.hasOwnProperty.call(res.locals, "error")) { + return next(); + } + // else bCrypt the new password and move to next middleware + const { newPassword } = req.body; + const saltRounds = 10; + + await bcrypt + .hash(newPassword, saltRounds) + .then((hash) => { + res.locals.hash = hash; + return next(); + }) + .catch((err) => { + return next({ + log: `Error in bcryptController hashNewPassword: ${err}`, + message: { + err: "An error occured creating hash with bcrypt. See bcryptController.hashNewPassword.", + }, + }); + }); + }, + + /** + * @description hashes the locals property cookie. Creates a column in the database to store the hashed cookie + */ + + hashCookie : (req: Request, res: Response, next: NextFunction) => { + const { role_id, username } = res.locals.user; + const saltRounds = 10; + if (role_id === 1) { + bcrypt + .hash(res.locals.cookie, saltRounds) + .then((hash) => { + res.locals.user.token = hash; + db.query( + "ALTER TABLE users ADD COLUMN IF NOT EXISTS token varchar(250)" + ); + db.query("UPDATE users SET token=$1 WHERE username=$2", [ + res.locals.user.token, + username, + ]); + return next(); + }) + .catch((err) => { + return next({ + log: `Error in bcryptController hashCookeis: ${err}`, + message: { + err: "An error occured creating hash with bcrypt. See bcryptController.hashCookies.", + }, + }); + }) + } else { + return next(); + } + } +}; + + +export default bcryptController; diff --git a/server/controllers/commandController.ts b/server/controllers/commandController.ts new file mode 100644 index 00000000..17fc7180 --- /dev/null +++ b/server/controllers/commandController.ts @@ -0,0 +1,647 @@ +/** + * @module initDatabase Controller + * @description Contains middleware that creates and runs the local database + */ +import { Request, Response, NextFunction } from "express"; +import { CommandController, ServerError } from "../../types"; +import { exec } from 'child_process'; +import { net } from 'electron'; +import { constants } from 'fs/promises'; +import { cpuUsage, freemem, totalmem, freememPercentage } from 'os-utils'; + +// ========================================================== +// Function: convert +// Purpose: +// ========================================================== +/** + * Parse all the stdout output into array to manipulate data properly. + * + * @param {*} stdout + */ +const convert = (stdout: string) => { + const newArray = stdout.split('\n'); + const result = []; + for (let i = 1; i < newArray.length - 1; i++) { + let removedSpace = newArray[i].replace(/\s+/g, ' '); // remove all spaces and replace it to 1 space + removedSpace = removedSpace.replace(/\s[/]\s/g, '/'); // remove all the space in between slash + const splittedArray = removedSpace.split(' '); + result.push(splittedArray); + } + return result; +}; + +/** + * Use user input to build options object to pass to getLogs() + * Helper function to build options object based on the radio button selected in the process logs tab + * + * @param {string} containerId + * @returns {object} optionsObj + */ +const makeArrayOfObjects = (string: string, containerName: string) => { + const arrayOfObjects = string + .trim() + .split('\n') + .map((element) => { + const obj: { [k: string]: any; } = {}; + const logArray = element.split(' '); + // extract timestamp + if (logArray[0].endsWith('Z')) { + const timeStamp: any = logArray.shift(); + // parse GMT string to be readable local date and time + obj.timeStamp = new Date(Date.parse(timeStamp)).toLocaleString(); + } + // parse remaining array to create readable message + let logMsg = logArray.join(' '); + // messages with duplicate time&date have form: '