Merge branch 'master' of https://gitlab.com/fyipe-project/app into feature-ruby-sdk

This commit is contained in:
Zadat Olayinka
2021-06-29 11:35:49 +01:00
601 changed files with 148232 additions and 69261 deletions

1
.gitignore vendored
View File

@@ -17,7 +17,6 @@ node_modules
.env.test.local
.env.production.local
env.js
/.vscode
npm-debug.log*
yarn-debug.log*

View File

@@ -120,8 +120,6 @@ include:
- '/ci/spec/ruby-sdk/build-n-test.yaml'
- '/ci/spec/ruby-sdk/deploy.yaml'
# # HARAKA
# - '/ci/spec/haraka/deploy.yaml'
@@ -133,3 +131,9 @@ include:
# # FYIPE-GL-MANAGER
# - '/ci/spec/fyipe-gl-manager/deploy.yaml'
# # Application Scanner
# - '/ci/spec/application-scanner/deploy.yaml'
# # Script Runner
# - '/ci/spec/script-runner/deploy.yaml'

92
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,92 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"address": "0.0.0.0",
"localRoot": "${workspaceFolder}/backend",
"name": "Backend: Debug with Docker",
"port": 9232,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"address": "0.0.0.0",
"localRoot": "${workspaceFolder}/licensing",
"name": "Licensing: Debug with Docker",
"port": 9233,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"address": "0.0.0.0",
"localRoot": "${workspaceFolder}/http-test-server",
"name": "HTTP Test Server: Debug with Docker",
"port": 9234,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"address": "0.0.0.0",
"localRoot": "${workspaceFolder}/home",
"name": "Home: Debug with Docker",
"port": 9235,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"address": "0.0.0.0",
"localRoot": "${workspaceFolder}/script-runnner",
"name": "Script Runner: Debug with Docker",
"port": 9236,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"address": "0.0.0.0",
"localRoot": "${workspaceFolder}/init-script",
"name": "Init Script: Debug with Docker",
"port": 9237,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"address": "0.0.0.0",
"localRoot": "${workspaceFolder}/probe",
"name": "Probe: Debug with Docker",
"port": 9238,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
}
]
}

1541
_test/script-monitor/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "script-monitor",
"version": "1.0.0",
"description": "Test for script monitor",
"main": "index.js",
"scripts": {
"test": "mocha ."
},
"author": "Dave",
"license": "ISC",
"devDependencies": {
"chai": "^4.3.4",
"mocha": "^8.4.0",
"request": "^2.88.2"
},
"dependencies": {
"axios": "^0.21.1",
"express": "^4.17.1",
"vm2": "^3.9.2"
}
}

View File

@@ -0,0 +1,215 @@
const { join } = require("path");
const {performance} = require('perf_hooks');
// TODO - make this configurable from admin-dashboard
const runConfig = {
availableImports: ['axios'], // init allowed modules
maxSyncStatementDuration: 3000,
maxScriptRunTime: 5000,
};
class ScriptMonitorError extends Error {
constructor(errors, message = "Script monitor resource error") {
super();
this.message = message;
this.errors = Array.isArray(errors)
? errors.reduce(
(allErr, err) => [...allErr, err.message].join(','),
[]
)
: (errors.message ?? errors);
}
}
const {
availableImports,
maxScriptRunTime,
maxSyncStatementDuration,
} = runConfig;
const runScript = async (functionCode, isCalled, options = { maxScriptRunTime, maxSyncStatementDuration }) => {
const {
isMainThread,
Worker,
parentPort,
workerData,
} = require('worker_threads');
if (isMainThread) {
// modifiable option in development mode only
const { maxScriptRunTime, maxSyncStatementDuration } = options;
if (!isCalled) return;
const start = performance.now();
return new Promise(resolve => {
const worker = new Worker(__filename, {
workerData: { functionCode },
execArgv: [
...process.execArgv,
'--unhandled-rejections=strict',
], // handle promise rejection warnings
});
const consoleLogs = [];
let lastMessage = null;
worker.on('message', ({type, payload}) => {
switch (type) {
case 'ping': {
lastMessage = Date.now();
break;
}
case 'log':{
consoleLogs.push(payload);
break;
}
default: {
if (type.error) {
resolve({
success: false,
error: type.error,
status: 'error',
executionTime: performance.now() - start,
consoleLogs,
});
}
break;
}
}
});
worker.on('online', () => {
lastMessage = Date.now();
});
worker.on('exit', exitCode => {
switch (exitCode) {
case 0:
resolve({
success: true,
status: 'completed',
executionTime: performance.now() - start,
consoleLogs,
});
break;
case 1: {
const message = statementTimeExceeded
? `Max. synchronous statement execution time exceeded (${maxSyncStatementDuration}ms)`
: scriptTimeExceeded
? `Max. script execution time exceeded (${maxScriptRunTime}ms)`
: 'Script was terminated';
resolve({
success: false,
message,
status: 'timeout',
executionTime: performance.now() - start,
consoleLogs,
});
break;
}
default:
resolve({
success: false,
message: 'Unknown Error: script terminated',
status: 'terminated',
executionTime: performance.now() - start,
consoleLogs,
});
break;
}
clearInterval(checker);
});
worker.on('error', err => {
if (err.errors) {
resolve({
success: false,
message: err.message,
errors: err.errors,
status: 'nonEmptyCallback',
executionTime: performance.now() - start,
consoleLogs,
});
return;
}
resolve({
success: false,
message: err.message,
status: 'error',
executionTime: performance.now() - start,
consoleLogs,
});
clearInterval(checker);
worker.terminate();
});
let totalRuntime = 0,
statementTimeExceeded = false,
scriptTimeExceeded = false;
const checker = setInterval(
() => {
totalRuntime += 1000;
if (totalRuntime > maxScriptRunTime) {
clearInterval(checker);
scriptTimeExceeded = true;
worker.terminate();
}
// Last ping was too long ago, terminate it
if (
lastMessage !== null &&
Date.now() - lastMessage >= maxSyncStatementDuration
) {
clearInterval(checker);
statementTimeExceeded = true;
worker.terminate();
}
},
1000,
maxSyncStatementDuration
);
});
} else {
// worker_threads code
const { NodeVM } = require('vm2');
const vm = new NodeVM({
eval: false,
wasm: false,
require: {
root: './',
external: availableImports,
import: availableImports,
},
console: 'redirect',
});
vm.on('console.log', (log) => {
parentPort.postMessage({type: 'log', payload: `[log]: ${log}`});
});
vm.on('console.error', (error) => {
parentPort.postMessage({type: 'log', payload: `[error]: ${error}`});
});
vm.on('console.warn', (error) => {
parentPort.postMessage({type: 'log', payload: `[warn]: ${error}`});
});
const scriptCompletedCallback = err => {
if (err) {
throw new ScriptMonitorError(err);
}
};
const code = workerData.functionCode;
setInterval(() => parentPort.postMessage({type: 'ping'}), 500);
const sandboxFunction = await vm.run(
`module.exports = ${code}`,
join(process.cwd(), 'node_modules')
);
await sandboxFunction(scriptCompletedCallback);
process.exit();
}
};
module.exports = runScript();
module.exports.runScript = runScript;

View File

@@ -0,0 +1,117 @@
const { expect } = require("chai");
const { runScript } = require("./scriptSandbox");
describe('ScriptMonitor V2', function() {
this.timeout(10000);
describe("runScript function", function(){
let server;
// create a quick express server
before(function(){
const express = require("express");
const app = express();
app.get("/test", (req, res) => res.send("yipee!"));
server = app.listen(5050);
});
// close express server
after(function(){
server.close();
});
it("should return success for a valid script", async function() {
const someFunction = async (done) => {
// make api requests using "axios" or "request"
const axios = require("axios").default;
// const request = require("request-promise");
const res = await axios.get("http://localhost:5050/test");
// const res = await request.get("http://localhost:5050/test");
console.log("hello");
console.log("world!");
done();
}
const result = await runScript(someFunction.toString(), true);
expect(result).to.not.be.undefined;
expect(result.success).to.be.true;
expect(result.status).eq("completed");
expect(result.executionTime).to.be.a('number');
console.log(result.executionTime);
expect(result.consoleLogs.length).eql(2);
expect(result.consoleLogs).to.include('[log]: hello');
expect(result.consoleLogs).to.include('[log]: world!');
});
it("should return false for error thrown in script", async function() {
const someFunction = async (done) => {
console.log('Error log');
console.error('Bad Error');
throw new Error("Bad error");
}
const result = await runScript(someFunction.toString(), true);
expect(result).to.not.be.undefined;
expect(result.success).to.be.false;
expect(result.status).eq("error");
expect(result.executionTime).to.be.a('number');
console.log(result.executionTime);
expect(result.consoleLogs.length).eql(2);
expect(result.consoleLogs).to.include('[error]: Bad Error');
expect(result.consoleLogs).to.include('[log]: Error log');
});
it("should return scriptMonitor error when script returns a value in cb", async function() {
const someFunction = async (done) => {
done("Some Error");
}
const result = await runScript(someFunction.toString(), true);
expect(result).to.be.ok;
expect(result.success).to.be.false;
expect(result.message).to.be.string("Script monitor resource error");
expect(result.errors).to.be.ok;
expect(result.status).eq("nonEmptyCallback");
expect(result.executionTime).to.be.a('number');
console.log(result.executionTime);
});
it("should return timeout error when script takes too long", async function() {
const someFunction = async (done) => {
return new Promise((resolve) => {
setTimeout(() => "All timed out", 7000);
})
}
const result = await runScript(someFunction.toString(), true, {maxScriptRunTime: 1500});
expect(result).to.be.ok;
expect(result.success).to.be.false;
expect(result.message).contains("Max. script execution time exceeded");
expect(result.status).eq("timeout");
expect(result.executionTime).to.be.a('number');
console.log(result.executionTime);
});
it("should return timeout error when statement takes too long", async function() {
const someFunction = async (done) => {
while(true){
// statement stuck in loop or too busy
}
}
const result = await runScript(someFunction.toString(), true, {maxSyncStatementDuration: 300});
expect(result).to.be.ok;
expect(result.success).to.be.false;
expect(result.message).contains("Max. synchronous statement execution time exceeded");
expect(result.status).eq("timeout");
expect(result.executionTime).to.be.a('number');
console.log(result.executionTime);
});
});
});

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM node:15
FROM node:16
#SET ENV Variables
ENV PRODUCTION=true

30
accounts/Dockerfile.dev Normal file
View File

@@ -0,0 +1,30 @@
#
# Accounts Dockerfile
#
# Pull base image nodejs image.
FROM node:16
#SET ENV Variables
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
# Install nodemon
RUN npm install nodemon -g
WORKDIR /usr/src/app
# Copy package.json files
COPY ./package.json /usr/src/app/package.json
COPY ./package-lock.json /usr/src/app/package-lock.json
# Install app dependencies
RUN npm ci
# Expose ports.
# - 3003: accounts
EXPOSE 3003
#Run the app
CMD [ "npm", "run", "dev" ]

17963
accounts/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,20 +4,19 @@
"private": true,
"homepage": "/",
"dependencies": {
"amplitude-js": "^5.8.0",
"amplitude-js": "^8.3.1",
"axios": "^0.21.1",
"card-validator": "^4.3.0",
"cli-table": "^0.3.1",
"card-validator": "^8.1.1",
"cli-table": "^0.3.6",
"compression": "^1.7.4",
"express": "^4.16.4",
"faker": "^4.1.0",
"file-saver": "^2.0.1",
"faker": "^5.5.3",
"file-saver": "^2.0.5",
"history": "^4.7.2",
"jest": "^26.6.3",
"loadable-components": "^2.2.3",
"prop-types": "^15.6.1",
"puppeteer": "^5.5.0",
"puppeteer-cluster": "^0.22.0",
"query-string": "^5.1.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
@@ -33,11 +32,11 @@
"redux-form": "^7.3.0",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0",
"sane-email-validation": "^1.1.0",
"sane-email-validation": "^3.0.1",
"universal-cookie": "^4.0.0",
"uuid": "^8.3.2",
"valid-url": "^1.0.9",
"workbox-build": "^4.3.1"
"workbox-build": "^6.1.5"
},
"scripts": {
"dev": "PORT=3003 react-scripts start",
@@ -54,11 +53,11 @@
},
"devDependencies": {
"chrome-launcher": "^0.13.2",
"commander": "^4.0.1",
"depcheck": "^0.9.2",
"commander": "^7.2.0",
"depcheck": "^1.4.1",
"lighthouse": "^6.2.0",
"npm-force-resolutions": "0.0.3",
"ora": "^4.0.3",
"npm-force-resolutions": "0.0.10",
"ora": "^5.4.1",
"should": "^13.2.3"
},
"resolutions": {},

View File

@@ -4,7 +4,12 @@ import { history, isServer } from './store';
import { connect } from 'react-redux';
import { allRoutes } from './routes';
import BackboneModals from './containers/BackboneModals';
import { DASHBOARD_URL, ADMIN_DASHBOARD_URL, IS_SAAS_SERVICE } from './config';
import {
DASHBOARD_URL,
ADMIN_DASHBOARD_URL,
IS_SAAS_SERVICE,
User,
} from './config';
import queryString from 'query-string';
import ReactGA from 'react-ga';
import Cookies from 'universal-cookie';
@@ -25,6 +30,7 @@ const isStatusPageLogin =
queryString.parse(window.location.search).statusPage === 'true';
const statusPageURL = queryString.parse(window.location.search).statusPageURL;
const userIsLoggedIn = cookies.get('data') || cookies.get('admin-data');
const redirectTo = queryString.parse(window.location.search).redirectTo;
if (userIsLoggedIn) {
const {
@@ -35,6 +41,8 @@ if (userIsLoggedIn) {
? ADMIN_DASHBOARD_URL
: isStatusPageLogin
? `${statusPageURL}?userId=${userId}&accessToken=${jwtAccessToken}`
: redirectTo
? redirectTo
: DASHBOARD_URL;
}
@@ -43,6 +51,13 @@ const App = ({
checkIfMasterAdminExists,
saveStatusPage,
}) => {
useEffect(() => {
// store initialUrl in sessionStorage
User.setInitialUrl(window.location.href);
// unset initialUrl when unmount
return () => User.removeInitialUrl();
}, []);
useEffect(() => {
if (!IS_SAAS_SERVICE && exists === null) {
checkIfMasterAdminExists();

View File

@@ -56,8 +56,10 @@ export function loginSuccess(user) {
});
}
if (user.redirect) {
if (user.redirect && user?.tokens?.jwtAccessToken) {
return (window.location = `${user.redirect}?accessToken=${user.tokens.jwtAccessToken}`);
} else if (user.redirect) {
return (window.location = user.redirect);
} else if (user.role === 'master-admin') {
window.location = ADMIN_DASHBOARD_URL;
} else {

View File

@@ -91,9 +91,15 @@ export const User = {
setEmail(email) {
localStorage.setItem('email', email);
},
initialUrl() {
return sessionStorage.getItem('initialUrl');
},
setInitialUrl(url) {
sessionStorage.setItem('initialUrl', url);
},
setProject(project) {
localStorage.setItem('project', project);
},
@@ -114,6 +120,10 @@ export const User = {
localStorage.removeItem('token');
},
removeInitialUrl() {
return sessionStorage.removeItem('initialUrl');
},
isLoggedIn() {
return localStorage.getItem('access_token') ? true : false;
},
@@ -241,7 +251,7 @@ export const PricingPlan = {
planId: 'plan_GoWIqpBpStiqQp',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
},
{
category: 'Growth',
@@ -255,21 +265,21 @@ export const PricingPlan = {
planId: 'plan_GoWKiTdQ6NiQFw',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
},
{
category: 'Scale',
planId: 'plan_H9Iox3l2YqLTDR',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IlBKhsFz4hV2',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
},
];
} else {
@@ -286,7 +296,7 @@ export const PricingPlan = {
planId: 'plan_GoVgJu5PKMLRJU',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
},
{
category: 'Growth',
@@ -300,21 +310,21 @@ export const PricingPlan = {
planId: 'plan_GoViZshjqzZ0vv',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
},
{
category: 'Scale',
planId: 'plan_H9Ii6Qj3HLdtty',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IjvX2Flsvlcg',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
},
];
}

View File

@@ -19,10 +19,15 @@ export const history = isServer
? createMemoryHistory({ initialEntries: [url] })
: createBrowserHistory();
export const removeQuery = () => {
export const removeQuery = removeField => {
const location = Object.assign({}, history.location);
const query = queryString.parse(location.search);
if (!query.token) delete location.search;
if (query[removeField]) delete query[removeField];
// remove "token" field - keeping this to prevent regression
if (query['token']) delete query['token'];
location.search = queryString.stringify(query);
history.push(location);
};
const initialState = {};

View File

@@ -14,6 +14,7 @@ const buildSW = () => {
.then(({ count, size }) => {
// Optionally, log any warnings and details.
return `${count} files will be precached, totaling ${size} bytes.`;
});
})
.catch(console.error);
};
buildSW();

View File

@@ -1,13 +1,24 @@
/* eslint-disable */
if ('function' === typeof importScripts) {
importScripts(
'https://storage.googleapis.com/workbox-cdn/releases/3.5.0/workbox-sw.js'
'https://storage.googleapis.com/workbox-cdn/releases/6.1.1/workbox-sw.js'
);
/* global workbox */
if (workbox) {
const { skipWaiting, clientsClaim } = workbox.core;
const { precacheAndRoute, cleanupOutdatedCaches } = workbox.precaching;
// skip waiting and switch to activating stage
skipWaiting();
// control webpage as soon as possible
clientsClaim();
// try to clean up old caches from previous versions
cleanupOutdatedCaches();
/* injection point for manifest files. */
workbox.precaching.precacheAndRoute([], {
cleanURLs: false,
});
precacheAndRoute(self.__WB_MANIFEST, { cleanUrls: false });
} else {
console.log('Workbox could not be loaded. No Offline support');
}
}

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM node:15
FROM node:16
#SET ENV Variables
ENV PRODUCTION=true

View File

@@ -0,0 +1,31 @@
#
# Admin Dashboard Dockerfile
#
# Pull base image nodejs image.
FROM node:16
#SET ENV Variables
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
# Install nodemon
RUN npm install nodemon -g
WORKDIR /usr/src/app
# Copy package.json files
COPY ./package.json /usr/src/app/package.json
COPY ./package-lock.json /usr/src/app/package-lock.json
# Install app dependencies
RUN npm ci
# Expose ports.
# - 3100: Fyipe-admin-dashboard
EXPOSE 3100
#Run the app
CMD [ "npm", "run", "dev" ]

File diff suppressed because it is too large Load Diff

View File

@@ -5,20 +5,18 @@
"dependencies": {
"@trendmicro/react-dropdown": "^1.4.0",
"axios": "^0.21.1",
"card-validator": "^6.2.0",
"clipboard": "^2.0.1",
"card-validator": "^8.1.1",
"clipboard": "^2.0.8",
"express": "^4.16.4",
"file-saver": "^2.0.1",
"file-saver": "^2.0.5",
"font-awesome": "^4.7.0",
"fuzzy-match-utils": "^1.3.0",
"history": "^4.7.2",
"history": "^5.0.0",
"jest": "^25.2.4",
"loadable-components": "^2.2.3",
"mixpanel-browser": "^2.22.3",
"moment": "^2.22.2",
"prop-types": "^15.6.1",
"puppeteer": "^5.5.0",
"puppeteer-cluster": "^0.22.0",
"react": "^16.14.0",
"react-click-outside": "github:tj/react-click-outside",
"react-dom": "^16.14.0",
@@ -34,7 +32,7 @@
"react-select-fyipe": "^2.1.8",
"react-widgets": "^4.4.9",
"redux": "^3.7.2",
"redux-form": "^7.3.0",
"redux-form": "^7.4.3",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0",
"sane-email-validation": "^1.1.0",
@@ -53,9 +51,9 @@
"dep-check": "depcheck ./ --skip-missing=true --ignores='babel-*,loadable-components'"
},
"devDependencies": {
"depcheck": "^0.9.2",
"jest-localstorage-mock": "^2.2.0",
"npm-force-resolutions": "0.0.3",
"depcheck": "^1.4.1",
"jest-localstorage-mock": "^2.4.14",
"npm-force-resolutions": "0.0.10",
"should": "^13.2.3"
},
"resolutions": {},

View File

@@ -381,6 +381,115 @@ export const blockUser = userId => async dispatch => {
}
};
//Enable Admin Mode
export const enableAdminModeRequest = () => {
return {
type: types.ENABLE_ADMIN_MODE_REQUEST,
};
};
export const enableAdminModeSuccess = user => {
return {
type: types.ENABLE_ADMIN_MODE_SUCCESS,
payload: user,
};
};
export const enableAdminModeError = error => {
return {
type: types.ENABLE_ADMIN_MODE_FAILED,
payload: error,
};
};
export const enableAdminModeReset = () => {
return {
type: types.ENABLE_ADMIN_MODE_RESET,
};
};
// Enable admin mode
export const enableAdminMode = (userId, values) => async dispatch => {
dispatch(enableAdminModeRequest());
try {
const response = await postApi(
`user/${userId}/switchToAdminMode`,
values
);
const data = response.data;
dispatch(enableAdminModeSuccess(data));
return response;
} catch (error) {
let errorMsg;
if (error && error.response && error.response.data)
errorMsg = error.response.data;
if (error && error.data) {
errorMsg = error.data;
}
if (error && error.message) {
errorMsg = error.message;
} else {
errorMsg = 'Network Error';
}
dispatch(enableAdminModeError(errors(errorMsg)));
}
};
//Disable Admin Mode
export const disableAdminModeRequest = () => {
return {
type: types.DISABLE_ADMIN_MODE_REQUEST,
};
};
export const disableAdminModeSuccess = user => {
return {
type: types.DISABLE_ADMIN_MODE_SUCCESS,
payload: user,
};
};
export const disableAdminModeError = error => {
return {
type: types.DISABLE_ADMIN_MODE_FAILED,
payload: error,
};
};
export const disableAdminModeReset = () => {
return {
type: types.DISABLE_ADMIN_MODE_RESET,
};
};
// Disable admin mode
export const disableAdminMode = userId => async dispatch => {
dispatch(disableAdminModeRequest());
try {
const response = await postApi(`user/${userId}/exitAdminMode`);
const data = response.data;
dispatch(disableAdminModeSuccess(data));
return response;
} catch (error) {
let errorMsg;
if (error && error.response && error.response.data)
errorMsg = error.response.data;
if (error && error.data) {
errorMsg = error.data;
}
if (error && error.message) {
errorMsg = error.message;
} else {
errorMsg = 'Network Error';
}
dispatch(disableAdminModeError(errors(errorMsg)));
}
};
//Unblock user
export const unblockUserRequest = () => {
return {

View File

@@ -478,7 +478,7 @@ export class CallLogsList extends Component {
</div>
</button>
</div>
{/* <div className="Box-root">
<div className="Box-root">
<button
id="deleteLog"
onClick={this.handleDelete}
@@ -493,7 +493,7 @@ export class CallLogsList extends Component {
</span>
</div>
</button>
</div> */}
</div>
</div>
</div>
</div>

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import uuid from 'uuid';
import ShouldRender from '../basic/ShouldRender';
import { ListLoader } from '../basic/Loader';
import { openModal, closeModal } from '../../actions/modal';
@@ -411,19 +412,25 @@ export class EmailLogsList extends Component {
id="log-count"
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--wrap"
>
{this.props.emailLogs &&
this.props.emailLogs.count
? `Page ${
this.props.page
} of ${numberOfPages} (${this.props
.emailLogs &&
this.props.emailLogs.count} Log${
this.props.emailLogs &&
this.props.emailLogs.count === 1
? ''
: 's'
})`
: null}
<ShouldRender
if={
this.props.emailLogs &&
this.props.emailLogs.count
}
>
Page {this.props.page} of{' '}
{numberOfPages} (
<span id="email-log-count">
{this.props.emailLogs.count}
</span>{' '}
Log
<ShouldRender
if={this.props.emailLogs.count > 0}
>
s
</ShouldRender>
)
</ShouldRender>
</span>
</span>
</span>
@@ -478,7 +485,7 @@ export class EmailLogsList extends Component {
</div>
</button>
</div>
{/* <div className="Box-root">
<div className="Box-root">
<button
id="deleteLog"
onClick={this.handleDelete}
@@ -493,7 +500,7 @@ export class EmailLogsList extends Component {
</span>
</div>
</button>
</div> */}
</div>
</div>
</div>
</div>

View File

@@ -30,7 +30,10 @@ class About extends Component {
return (
<tr key={i}>
<td>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
id="probe-version"
>
{probe.probeName} Version
</span>
</td>
@@ -97,7 +100,10 @@ class About extends Component {
</tr>
<tr>
<td>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
id="server-version"
>
Server Version
</span>
</td>
@@ -120,7 +126,10 @@ class About extends Component {
</tr>
<tr>
<td>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
id="admin-dashboard-version"
>
Admin Dashboard Version
</span>
</td>
@@ -143,7 +152,10 @@ class About extends Component {
</tr>
<tr>
<td>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
id="dashboard-version"
>
Dashboard Version
</span>
</td>
@@ -166,7 +178,10 @@ class About extends Component {
</tr>
<tr>
<td>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
id="docs-version"
>
Docs Version
</span>
</td>
@@ -187,7 +202,10 @@ class About extends Component {
</tr>
<tr>
<td>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
id="helm-version"
>
Helm Chart Version
</span>
</td>

View File

@@ -169,7 +169,8 @@ export class ProjectList extends Component {
(project, index) => {
const projectOwner =
project.users.find(
user => user.role === 'Owner'
user =>
user.projectRole === 'Owner'
) || {};
let usersDetail;
if (project.users.length > 0) {

View File

@@ -93,7 +93,7 @@ const fields = [
label: 'SMTP Secure',
// eslint-disable-next-line react/display-name, react/prop-types
component: ({ input: { value, onChange } }) => (
<label className="Toggler-wrap">
<label className="Toggler-wrap" id="label_smpt_secure">
<input
className="btn-toggler"
checked={value}
@@ -463,6 +463,7 @@ export class Component extends React.Component {
</button>
<button
className="bs-Button bs-Button--blue"
id="save-smpt-settings"
disabled={settings && settings.requesting}
type="submit"
>
@@ -515,7 +516,7 @@ function mapStateToProps(state) {
settings: state.settings,
initialValues: state.settings[settingsType],
smtpForm: state.form['smtp-form'] || {},
smtpTestForm: state.form['smtp-test-form'] || {}
smtpTestForm: state.form['smtp-test-form'] || {},
};
}

View File

@@ -10,6 +10,7 @@ import { openModal, closeModal } from '../../actions/modal';
import DeleteConfirmationModal from './DeleteConfirmationModal';
import SmsLogsContentViewModal from './SmsLogsContentViewModal';
import SmsLogsErrorViewModal from './SmsLogsErrorViewModal';
import ShouldRender from '../basic/ShouldRender';
import { history } from '../../store';
@@ -432,19 +433,25 @@ export class SmsLogsList extends Component {
id="log-count"
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--wrap"
>
{this.props.smsLogs &&
this.props.smsLogs.count
? `Page ${
this.props.page
} of ${numberOfPages} (${this.props
.smsLogs &&
this.props.smsLogs.count} Log${
this.props.smsLogs &&
this.props.smsLogs.count === 1
? ''
: 's'
})`
: null}
<ShouldRender
if={
this.props.smsLogs &&
this.props.smsLogs.count
}
>
Page {this.props.page} of{' '}
{numberOfPages} (
<span id="sms-log-count">
{this.props.smsLogs.count}
</span>{' '}
Log
<ShouldRender
if={this.props.smsLogs.count > 0}
>
s
</ShouldRender>
)
</ShouldRender>
</span>
</span>
</span>
@@ -499,7 +506,7 @@ export class SmsLogsList extends Component {
</div>
</button>
</div>
{/* <div className="Box-root">
<div className="Box-root">
<button
id="deleteLog"
onClick={this.handleDelete}
@@ -514,7 +521,7 @@ export class SmsLogsList extends Component {
</span>
</div>
</button>
</div> */}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,94 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { FormLoader } from '../basic/Loader';
import ShouldRender from '../basic/ShouldRender';
import { disableAdminMode } from '../../actions/user';
export class UserAdminModeDisableBox extends Component {
constructor(props) {
super(props);
}
handleClick = () => {
const { disableAdminMode, userId } = this.props;
disableAdminMode(userId);
};
render() {
const { isRequesting } = this.props;
return (
<div className="Box-root Margin-bottom--12">
<div className="bs-ContentSection Card-root Card-shadow--medium">
<div className="Box-root">
<div className="bs-ContentSection-content Box-root Box-divider--surface-bottom-1 Flex-flex Flex-alignItems--center Flex-justifyContent--spaceBetween Padding-horizontal--20 Padding-vertical--16">
<div className="Box-root">
<span className="Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Disable Admin Mode</span>
</span>
<p>
<span>
Click the button to disable admin mode
and revert to original user password
</span>
</p>
</div>
<div className="bs-ContentSection-footer bs-ContentSection-content Box-root Box-background--white Flex-flex Flex-alignItems--center Flex-justifyContent--spaceBetween Padding-horizontal--0 Padding-vertical--12">
<span className="db-SettingsForm-footerMessage"></span>
<div>
<button
id="block"
className="bs-Button bs-Button--green Box-background--green"
disabled={isRequesting}
onClick={this.handleClick}
>
<ShouldRender if={!isRequesting}>
<span>Dsiable Admin Mode</span>
</ShouldRender>
<ShouldRender if={isRequesting}>
<FormLoader />
</ShouldRender>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
UserAdminModeDisableBox.displayName = 'UserAdminModeDisableBox';
const mapDispatchToProps = dispatch =>
bindActionCreators({ disableAdminMode }, dispatch);
const mapStateToProps = state => {
const userId = state.user.user.user ? state.user.user.user._id : null;
return {
userId,
isRequesting:
state.user &&
state.user.disableAdminMode &&
state.user.disableAdminMode.requesting,
};
};
UserAdminModeDisableBox.propTypes = {
isRequesting: PropTypes.oneOf([null, undefined, true, false]),
disableAdminMode: PropTypes.func.isRequired,
userId: PropTypes.string,
};
UserAdminModeDisableBox.contextTypes = {
mixpanel: PropTypes.object.isRequired,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserAdminModeDisableBox);

View File

@@ -0,0 +1,128 @@
import uuid from 'uuid';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { FormLoader } from '../basic/Loader';
import ShouldRender from '../basic/ShouldRender';
import { enableAdminMode } from '../../actions/user';
import UserAdminModeEnableModal from './UserAdminModeEnableModal';
import { openModal, closeModal } from '../../actions/modal';
export class UserAdminModeEnableBox extends Component {
constructor(props) {
super(props);
this.state = { AdminModeModalId: uuid.v4() };
}
handleClick = () => {
const { enableAdminMode, userId } = this.props;
const { AdminModeModalId } = this.state;
const thisObj = this;
this.props.openModal({
id: AdminModeModalId,
onConfirm: values => {
return enableAdminMode(userId, values).then(() => {
if (window.location.href.indexOf('localhost') <= -1) {
thisObj.context.mixpanel.track('Admin mode enabled');
}
});
},
content: UserAdminModeEnableModal,
});
};
handleKeyBoard = e => {
switch (e.key) {
case 'Escape':
return this.props.closeModal({
id: this.state.AdminModeModalId,
});
default:
return false;
}
};
render() {
const { isRequesting } = this.props;
return (
<div
onKeyDown={this.handleKeyBoard}
className="Box-root Margin-bottom--12"
>
<div className="bs-ContentSection Card-root Card-shadow--medium">
<div className="Box-root">
<div className="bs-ContentSection-content Box-root Box-divider--surface-bottom-1 Flex-flex Flex-alignItems--center Flex-justifyContent--spaceBetween Padding-horizontal--20 Padding-vertical--16">
<div className="Box-root">
<span className="Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Enable Admin Mode</span>
</span>
<p>
<span>
Click the button to enable admin mode
and create a temporary password for this
user
</span>
</p>
</div>
<div className="bs-ContentSection-footer bs-ContentSection-content Box-root Box-background--white Flex-flex Flex-alignItems--center Flex-justifyContent--spaceBetween Padding-horizontal--0 Padding-vertical--12">
<span className="db-SettingsForm-footerMessage"></span>
<div>
<button
id="block"
className="bs-Button bs-Button--red Box-background--red"
disabled={isRequesting}
onClick={this.handleClick}
>
<ShouldRender if={!isRequesting}>
<span>Enable</span>
</ShouldRender>
<ShouldRender if={isRequesting}>
<FormLoader />
</ShouldRender>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
UserAdminModeEnableBox.displayName = 'UserAdminModeEnableBox';
const mapDispatchToProps = dispatch =>
bindActionCreators({ enableAdminMode, openModal, closeModal }, dispatch);
const mapStateToProps = state => {
const userId = state.user.user.user ? state.user.user.user._id : null;
return {
userId,
isRequesting:
state.user &&
state.user.enableAdminMode &&
state.user.enableAdminMode.requesting,
};
};
UserAdminModeEnableBox.propTypes = {
isRequesting: PropTypes.oneOf([null, undefined, true, false]),
enableAdminMode: PropTypes.func.isRequired,
closeModal: PropTypes.func,
openModal: PropTypes.func.isRequired,
userId: PropTypes.string,
};
UserAdminModeEnableBox.contextTypes = {
mixpanel: PropTypes.object.isRequired,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserAdminModeEnableBox);

View File

@@ -0,0 +1,216 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ClickOutside from 'react-click-outside';
import { Spinner } from '../basic/Loader';
import ShouldRender from '../basic/ShouldRender';
import { reduxForm, Field } from 'redux-form';
import { RenderField } from '../basic/RenderField';
import { ValidateField } from '../../config';
const formName = 'UserAdminModeEnableForm';
class UserAdminModeEnableModal extends Component {
componentDidMount() {
window.addEventListener('keydown', this.handleKeyboard);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeyboard);
}
submitForm = values => {
return this.props.confirmThisDialog(values);
};
handleKeyboard = e => {
switch (e.key) {
case 'Escape':
return this.props.closeThisDialog();
case 'Enter':
return document.getElementById('enableAdminMode').click();
default:
return false;
}
};
render() {
const {
isRequesting,
enableAdminModeError,
closeThisDialog,
handleSubmit,
} = this.props;
return (
<div className="ModalLayer-wash Box-root Flex-flex Flex-alignItems--flexStart Flex-justifyContent--center">
<div
className="ModalLayer-contents"
tabIndex={-1}
style={{ marginTop: 40 }}
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<ClickOutside onClickOutside={closeThisDialog}>
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Enable Admin Mode</span>
</span>
</div>
</div>
<form
id={formName}
onSubmit={handleSubmit(this.submitForm)}
>
<div className="bs-Modal-content">
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
Enter a temporary password (min. 6
characters) to use in Admin mode
</span>
<div className="bs-Fieldset-wrapper Box-root Margin-bottom--2">
<fieldset className="Margin-bottom--16">
<div className="bs-Fieldset-row">
<label
className="bs-Fieldset-label Text-align--left"
htmlFor="temporaryPassword"
>
<span>
Temporary Password
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="text"
name={
'temporaryPassword'
}
placeholder="Enter Password"
disabled={
isRequesting
}
validate={
ValidateField.password6
}
/>
</div>
</div>
</fieldset>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender
if={enableAdminModeError}
>
<div className="bs-Tail-copy">
<div
className="Box-root Flex-flex Flex-alignItems--stretch Flex-direction--row Flex-justifyContent--flexStart"
style={{
marginTop: '10px',
}}
>
<div className="Box-root Margin-right--8">
<div
className="Icon Icon--info Icon--color--red Icon--size--14 Box-root Flex-flex"
style={{
marginTop:
'2px',
}}
></div>
</div>
<div className="Box-root">
<span
style={{
color:
'red',
}}
>
{
enableAdminModeError
}
</span>
</div>
</div>
</div>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
<button
id="enableAdminMode"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="submit"
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Enable Admin Mode</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
</div>
</div>
</form>
</ClickOutside>
</div>
</div>
</div>
</div>
);
}
}
UserAdminModeEnableModal.displayName = 'UserAdminModeEnableModal';
const mapStateToProps = state => {
return {
isRequesting:
state.user &&
state.user.enableAdminMode &&
state.user.enableAdminMode.requesting,
enableAdminModeError:
state.user &&
state.user.enableAdminMode &&
state.user.enableAdminMode.error,
};
};
UserAdminModeEnableModal.propTypes = {
isRequesting: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf([null, undefined]),
]),
confirmThisDialog: PropTypes.func.isRequired,
closeThisDialog: PropTypes.func,
enableAdminModeError: PropTypes.oneOfType([
PropTypes.string,
PropTypes.oneOf([null, undefined]),
]),
handleSubmit: PropTypes.func,
};
const UserAdminModeEnableForm = reduxForm({
form: formName,
enableReinitialize: false,
destroyOnUnmount: true,
})(UserAdminModeEnableModal);
export default connect(mapStateToProps)(UserAdminModeEnableForm);

View File

@@ -54,6 +54,12 @@ const UserList = ({ users }) =>
<span>Blocked</span>
</span>
</div>
) : user.isAdminMode ? (
<div className="Badge Badge--color--red Box-root Flex-inlineFlex Flex-alignItems--center Padding-horizontal--8 Padding-vertical--2">
<span className="Badge-text Text-color--red Text-display--inline Text-fontSize--12 Text-fontWeight--bold Text-lineHeight--16 Text-typeface--upper Text-wrap--noWrap">
<span>AdminMode</span>
</span>
</div>
) : (
<div className="Badge Badge--color--green Box-root Flex-inlineFlex Flex-alignItems--center Padding-horizontal--8 Padding-vertical--2">
<span className="Badge-text Text-color--green Text-display--inline Text-fontSize--12 Text-fontWeight--bold Text-lineHeight--16 Text-typeface--upper Text-wrap--noWrap">

View File

@@ -78,6 +78,15 @@ export class UserSetting extends Component {
<span>Blocked</span>
</span>
</div>
) : this.props.user.isAdminMode ? (
<div
className="Badge Badge--color--red Box-root Flex-inlineFlex Flex-alignItems--center Padding-horizontal--8 Padding-vertical--2"
style={{ float: 'right' }}
>
<span className="Badge-text Text-color--red Text-display--inline Text-fontSize--12 Text-fontWeight--bold Text-lineHeight--16 Text-typeface--upper Text-wrap--noWrap">
<span>AdminMode</span>
</span>
</div>
) : (
<div
className="Badge Badge--color--green Box-root Flex-inlineFlex Flex-alignItems--center Padding-horizontal--8 Padding-vertical--2"

View File

@@ -32,8 +32,7 @@ export class UserUnblockBox extends Component {
</span>
<p>
<span>
Click the button to unblock this
project.
Click the button to unblock this user.
</span>
</p>
</div>

View File

@@ -290,6 +290,13 @@ export const ValidateField = {
compare: (text1, text2) =>
text1 === text2 ? undefined : 'These texts donot match',
password6: password =>
!password || !password.length
? 'Password cannot be blank'
: password.length < 6
? 'Password must be a minimum of 6 characters'
: undefined,
};
export const PricingPlan = {
@@ -311,7 +318,7 @@ export const PricingPlan = {
planId: 'plan_GoWIqpBpStiqQp',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
},
{
category: 'Growth',
@@ -325,21 +332,21 @@ export const PricingPlan = {
planId: 'plan_GoWKiTdQ6NiQFw',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
},
{
category: 'Scale',
planId: 'plan_H9Iox3l2YqLTDR',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IlBKhsFz4hV2',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
},
];
} else {
@@ -356,7 +363,7 @@ export const PricingPlan = {
planId: 'plan_GoVgJu5PKMLRJU',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
},
{
category: 'Growth',
@@ -370,21 +377,21 @@ export const PricingPlan = {
planId: 'plan_GoViZshjqzZ0vv',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
},
{
category: 'Scale',
planId: 'plan_H9Ii6Qj3HLdtty',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IjvX2Flsvlcg',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
},
];
}

View File

@@ -50,6 +50,18 @@ export const UNBLOCK_USER_RESET = 'UNBLOCK_USER_RESET';
export const UNBLOCK_USER_SUCCESS = 'UNBLOCK_USER_SUCCESS';
export const UNBLOCK_USER_FAILED = 'UNBLOCK_USER_FAILED';
// Enable Admin Mode
export const ENABLE_ADMIN_MODE_REQUEST = 'ENABLE_ADMIN_MODE_REQUEST';
export const ENABLE_ADMIN_MODE_RESET = 'ENABLE_ADMIN_MODE_RESET';
export const ENABLE_ADMIN_MODE_SUCCESS = 'ENABLE_ADMIN_MODE_SUCCESS';
export const ENABLE_ADMIN_MODE_FAILED = 'ENABLE_ADMIN_MODE_FAILED';
// Disable Admin Mode
export const DISABLE_ADMIN_MODE_REQUEST = 'DISABLE_ADMIN_MODE_REQUEST';
export const DISABLE_ADMIN_MODE_RESET = 'DISABLE_ADMIN_MODE_RESET';
export const DISABLE_ADMIN_MODE_SUCCESS = 'DISABLE_ADMIN_MODE_SUCCESS';
export const DISABLE_ADMIN_MODE_FAILED = 'DISABLE_ADMIN_MODE_FAILED';
// Admin User Note
export const ADD_USER_NOTE_REQUEST = 'ADD_USER_NOTE_REQUEST';
export const ADD_USER_NOTE_RESET = 'ADD_USER_NOTE_RESET';

View File

@@ -14,6 +14,9 @@ import UserUnblockBox from '../components/user/UserUnblockBox';
import AdminNotes from '../components/adminNote/AdminNotes';
import { fetchUserProjects } from '../actions/project';
import { addUserNote, fetchUser, fetchUserloginHistory } from '../actions/user';
import UserAdminModeEnableBox from '../components/user/UserAdminModeEnableBox';
import UserAdminModeDisableBox from '../components/user/UserAdminModeDisableBox';
import { User as LsUser } from '../config';
class User extends Component {
componentDidMount() {
@@ -61,6 +64,39 @@ class User extends Component {
}
/>
</div>
<ShouldRender
if={
LsUser.getUserId() !==
this.props.match.params
.userId
}
>
<div className="Box-root Margin-bottom--12">
<ShouldRender
if={
!this.props
?.user
?.isAdminMode &&
!this.props
?.user
.deleted
}
>
<UserAdminModeEnableBox />
</ShouldRender>
<ShouldRender
if={
this.props?.user
?.isAdminMode &&
!this.props
?.user
.deleted
}
>
<UserAdminModeDisableBox />
</ShouldRender>
</div>
</ShouldRender>
<div className="Box-root Margin-bottom--12">
<UserHistory
history={
@@ -74,49 +110,59 @@ class User extends Component {
</div>
<ShouldRender
if={
this.props.user &&
!this.props.user
.deleted &&
!this.props.user
.isBlocked
LsUser.getUserId() !==
this.props.match.params
.userId
}
>
<div className="Box-root Margin-bottom--12">
<UserBlockBox />
</div>
</ShouldRender>
<ShouldRender
if={
this.props.user &&
!this.props.user
.deleted &&
this.props.user
.isBlocked
}
>
<div className="Box-root Margin-bottom--12">
<UserUnblockBox />
</div>
</ShouldRender>
<ShouldRender
if={
this.props.user &&
!this.props.user.deleted
}
>
<div className="Box-root Margin-bottom--12">
<UserDeleteBox />
</div>
</ShouldRender>
<ShouldRender
if={
this.props.user &&
this.props.user.deleted
}
>
<div className="Box-root Margin-bottom--12">
<UserRestoreBox />
</div>
<ShouldRender
if={
this.props.user &&
!this.props.user
.deleted &&
!this.props.user
.isBlocked
}
>
<div className="Box-root Margin-bottom--12">
<UserBlockBox />
</div>
</ShouldRender>
<ShouldRender
if={
this.props.user &&
!this.props.user
.deleted &&
this.props.user
.isBlocked
}
>
<div className="Box-root Margin-bottom--12">
<UserUnblockBox />
</div>
</ShouldRender>
<ShouldRender
if={
this.props.user &&
!this.props.user
.deleted
}
>
<div className="Box-root Margin-bottom--12">
<UserDeleteBox />
</div>
</ShouldRender>
<ShouldRender
if={
this.props.user &&
this.props.user
.deleted
}
>
<div className="Box-root Margin-bottom--12">
<UserRestoreBox />
</div>
</ShouldRender>
</ShouldRender>
</div>
</div>

View File

@@ -46,6 +46,14 @@ import {
FETCH_USER_LOGIN_HISTORY_FAILURE,
FETCH_USER_LOGIN_HISTORY_SUCCESS,
FETCH_USER_LOGIN_HISTORY_REQUEST,
ENABLE_ADMIN_MODE_SUCCESS,
ENABLE_ADMIN_MODE_REQUEST,
ENABLE_ADMIN_MODE_FAILED,
ENABLE_ADMIN_MODE_RESET,
DISABLE_ADMIN_MODE_SUCCESS,
DISABLE_ADMIN_MODE_REQUEST,
DISABLE_ADMIN_MODE_FAILED,
DISABLE_ADMIN_MODE_RESET,
} from '../constants/user';
const INITIAL_STATE = {
@@ -90,6 +98,11 @@ const INITIAL_STATE = {
requesting: false,
success: false,
},
enableAdminMode: {
error: null,
requesting: false,
success: false,
},
unblockUser: {
error: null,
requesting: false,
@@ -414,6 +427,90 @@ export default function user(state = INITIAL_STATE, action) {
},
});
case ENABLE_ADMIN_MODE_SUCCESS:
return Object.assign({}, state, {
enableAdminMode: {
requesting: false,
success: true,
error: null,
},
user: {
requesting: false,
error: null,
success: true,
user: action.payload,
},
});
case ENABLE_ADMIN_MODE_REQUEST:
return Object.assign({}, state, {
enableAdminMode: {
requesting: true,
success: false,
error: null,
},
});
case ENABLE_ADMIN_MODE_FAILED:
return Object.assign({}, state, {
enableAdminMode: {
requesting: false,
success: false,
error: action.payload,
},
});
case ENABLE_ADMIN_MODE_RESET:
return Object.assign({}, state, {
enableAdminMode: {
requesting: false,
success: false,
error: null,
},
});
case DISABLE_ADMIN_MODE_SUCCESS:
return Object.assign({}, state, {
disableAdminMode: {
requesting: false,
success: true,
error: null,
},
user: {
requesting: false,
error: null,
success: true,
user: action.payload,
},
});
case DISABLE_ADMIN_MODE_REQUEST:
return Object.assign({}, state, {
disableAdminMode: {
requesting: true,
success: false,
error: null,
},
});
case DISABLE_ADMIN_MODE_FAILED:
return Object.assign({}, state, {
disableAdminMode: {
requesting: false,
success: false,
error: action.payload,
},
});
case DISABLE_ADMIN_MODE_RESET:
return Object.assign({}, state, {
disableAdminMode: {
requesting: false,
success: false,
error: null,
},
});
case UNBLOCK_USER_SUCCESS:
return Object.assign({}, state, {
unblockUser: {

View File

@@ -1,5 +1,5 @@
# Pull base image nodejs image.
FROM node:15
FROM node:16
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
@@ -15,7 +15,7 @@ RUN npm ci --only=production
COPY . /usr/src/app
# Expose ports.
# - 1445: Fyipe Dcos
# - 1445: Fyipe Docs
EXPOSE 1445
#Run the app

30
api-docs/Dockerfile.dev Normal file
View File

@@ -0,0 +1,30 @@
#
# Fyipe Docs Dockerfile
#
# Pull base image nodejs image.
FROM node:16
#SET ENV Variables
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
# Install nodemon
RUN npm install nodemon -g
WORKDIR /usr/src/app
# Copy package.json files
COPY ./package.json /usr/src/app/package.json
COPY ./package-lock.json /usr/src/app/package-lock.json
# Install app dependencies
RUN npm ci
# Expose ports.
# - 3000: Fyipe
EXPOSE 3000
#Run the app
CMD [ "npm", "run", "dev" ]

View File

@@ -29,7 +29,6 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
@@ -42,11 +41,6 @@
"node": ">=0.6"
}
},
"node_modules/mocha/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/url-parse-lax": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
@@ -598,12 +592,6 @@
"node": ">=10"
}
},
"node_modules/undefsafe/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"node_modules/balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -1872,11 +1860,6 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"node_modules/express/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -2001,11 +1984,6 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/mocha/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/log-symbols/node_modules/chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
@@ -2136,18 +2114,6 @@
"semver-compare": "^1.0.0"
}
},
"node_modules/wrap-ansi/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -2367,12 +2333,6 @@
"node": ">=0.4.0"
}
},
"node_modules/wrap-ansi/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/normalize-url": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
@@ -2581,14 +2541,6 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"node_modules/wide-align/node_modules/is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"engines": {
"node": ">=4"
}
},
"node_modules/unique-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
@@ -2965,17 +2917,6 @@
"concat-map": "0.0.1"
}
},
"node_modules/wide-align/node_modules/strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dependencies": {
"ansi-regex": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -3155,8 +3096,7 @@
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"node_modules/supports-color": {
"version": "5.5.0",
@@ -3576,14 +3516,6 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"node_modules/wide-align/node_modules/ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"engines": {
"node": ">=4"
}
},
"node_modules/mocha/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -3903,7 +3835,6 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
@@ -4588,11 +4519,6 @@
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
@@ -5282,11 +5208,6 @@
"color-convert": "^2.0.1"
}
},
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -5305,11 +5226,6 @@
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -5931,8 +5847,7 @@
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"statuses": {
"version": "1.5.0",
@@ -6091,12 +6006,6 @@
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
}
}
},
@@ -6256,16 +6165,6 @@
"string-width": "^1.0.2 || 2"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@@ -6274,14 +6173,6 @@
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "^3.0.0"
}
}
}
},
@@ -6318,21 +6209,6 @@
"requires": {
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
}
}
},

View File

@@ -256,8 +256,8 @@
"{{userId}} : Unique identifier for user account.",
"{{projectId}} : Unique identifier for the current project."
],
"emailType": "Subscriber Incident Acknowldeged",
"subject": "{{projectName}}/{{monitorName}}: Incident Acknowldeged",
"emailType": "Subscriber Incident Acknowledged",
"subject": "{{projectName}}/{{monitorName}}: Incident Acknowledged",
"body": "..."
},
{
@@ -661,8 +661,8 @@
"{{userId}} : Unique identifier for user account.",
"{{projectId}} : Unique identifier for the current project."
],
"emailType": "Subscriber Incident Acknowldeged",
"subject": "{{projectName}}/{{monitorName}}: Incident Acknowldeged",
"emailType": "Subscriber Incident Acknowledged",
"subject": "{{projectName}}/{{monitorName}}: Incident Acknowledged",
"body": "..."
},
{
@@ -795,8 +795,8 @@
"{{userId}} : Unique identifier for user account.",
"{{projectId}} : Unique identifier for the current project."
],
"emailType": "Subscriber Incident Acknowldeged",
"subject": "{{projectName}}/{{monitorName}}: Incident Acknowldeged",
"emailType": "Subscriber Incident Acknowledged",
"subject": "{{projectName}}/{{monitorName}}: Incident Acknowledged",
"body": "..."
},
{
@@ -857,8 +857,8 @@
"{{userId}} : Unique identifier for user account.",
"{{projectId}} : Unique identifier for the current project."
],
"emailType": "Subscriber Incident Acknowldeged",
"subject": "{{projectName}}/{{monitorName}}: Incident Acknowldeged",
"emailType": "Subscriber Incident Acknowledged",
"subject": "{{projectName}}/{{monitorName}}: Incident Acknowledged",
"body": "..."
},
{

View File

@@ -237,7 +237,7 @@
"{{projectName}} : Name of the project on which the incident has occured.",
"{{incidentType}} : Type of incident."
],
"smsType": "Subscriber Incident Acknowldeged",
"smsType": "Subscriber Incident Acknowledged",
"body": "{{projectName}}/{{monitorName}} is {{incidentType}} at {{incidentTime}}. You are receiving this message because you subscribed to this monitor."
},
{
@@ -615,7 +615,7 @@
"{{projectName}} : Name of the project on which the incident has occured.",
"{{incidentType}} : Type of incident."
],
"smsType": "Subscriber Incident Acknowldeged",
"smsType": "Subscriber Incident Acknowledged",
"body": "{{projectName}}/{{monitorName}} is {{incidentType}} at {{incidentTime}}. You are receiving this message because you subscribed to this monitor."
},
{
@@ -737,7 +737,7 @@
"{{projectName}} : Name of the project on which the incident has occured.",
"{{incidentType}} : Type of incident."
],
"smsType": "Subscriber Incident Acknowldeged",
"smsType": "Subscriber Incident Acknowledged",
"body": "{{projectName}}/{{monitorName}} is {{incidentType}} at {{incidentTime}}. You are receiving this message because you subscribed to this monitor."
},
{
@@ -782,7 +782,7 @@
"{{projectName}} : Name of the project on which the incident has occured.",
"{{incidentType}} : Type of incident."
],
"smsType": "Subscriber Incident Acknowldeged",
"smsType": "Subscriber Incident Acknowledged",
"body": "{{projectName}}/{{monitorName}} is {{incidentType}} at {{incidentTime}}. You are receiving this message because you subscribed to this monitor."
},
{

View File

@@ -0,0 +1,12 @@
node_modules/
kubernetes/
.vscode/
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
yarn.lock

4
application-scanner/.env Normal file
View File

@@ -0,0 +1,4 @@
CLUSTER_KEY=f414c23b4cdf4e84a6a66ecfd528eff2
APPLICATION_SCANNER_NAME=US
APPLICATION_SCANNER_KEY=33b674ca-9fdd-11e9-a2a3-2a2ae2dbccez
SERVER_URL=http://localhost:3002

1
application-scanner/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.js text eol=lf

3
application-scanner/.gitignore vendored Executable file
View File

@@ -0,0 +1,3 @@
node_modules
kubernetes
debug.log

27
application-scanner/Dockerfile Executable file
View File

@@ -0,0 +1,27 @@
#
# Fyipe-backend Dockerfile
#
# Pull base image nodejs image.
FROM node:16
#SET ENV Variables
ENV PRODUCTION=true
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies
COPY package*.json /usr/src/app/
RUN npm ci --only=production
# Bundle app source
COPY . /usr/src/app
# Expose ports.
# - 3005: Application Scanner
EXPOSE 3005
#Run the app
CMD [ "npm", "start"]

View File

@@ -0,0 +1,26 @@
#
# Fyipe-backend Dockerfile
#
# Pull base image nodejs image.
FROM node:16
WORKDIR /usr/src/app
# Install app dependencies
RUN cd /usr/src/app
# Copy package.json files
COPY ./package.json /usr/src/app/package.json
COPY ./package-lock.json /usr/src/app/package-lock.json
RUN npm ci
# Expose ports.
# - 3005: Application Scanner
EXPOSE 3005
#Run the app
CMD [ "npm", "run", "dev"]

View File

@@ -0,0 +1,75 @@
const { NODE_ENV } = process.env;
if(!NODE_ENV || NODE_ENV === 'development'){
// Load env vars from /backend/.env
require('custom-env').env();
}
process.on('exit', () => {
/* eslint-disable no-console */
console.log('Application Scanner Shutting Shutdown');
});
process.on('unhandledRejection', err => {
/* eslint-disable no-console */
console.error('Unhandled rejection in application scanner process occurred');
/* eslint-disable no-console */
console.error(err);
});
process.on('uncaughtException', err => {
/* eslint-disable no-console */
console.error('Uncaught exception in application scanner process occurred');
/* eslint-disable no-console */
console.error(err);
});
const express = require('express');
const app = express();
const http = require('http').createServer(app);
const cors = require('cors');
const Main = require('./worker/main');
const cron = require('node-cron');
const config = require('./utils/config');
const cronApplicationSecurityStartTime = Math.floor(Math.random() * 50);
app.use(cors());
app.set('port', process.env.PORT || 3005);
http.listen(app.get('port'), function() {
// eslint-disable-next-line
console.log(
`Application Scanner ${config.applicationScannerName} and Application Key ${
config.applicationScannerKey
} Started on port ${app.get('port')}. Fyipe API URL: ${
config.serverUrl
}`
);
});
app.get('/', function(req, res) {
res.setHeader('Content-Type', 'application/json');
res.send(
JSON.stringify({
status: 200,
message: 'Service Status - OK',
serviceType: 'fyipe-applicationScanner',
})
);
});
//App Version
app.get(['/application/version', '/version'], function(req, res) {
res.setHeader('Content-Type', 'application/json');
res.send({ applicationScannerVersion: process.env.npm_package_version });
});
// Run this cron at 3 AM once a day.
cron.schedule('0 3 * * *', () => {
setTimeout(() => {
Main.runApplicationScan();
}, cronApplicationSecurityStartTime * 1000);
});
module.exports = app;

4745
application-scanner/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
{
"name": "applicationscanner",
"version": "3.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node --max-http-header-size=80000 index.js",
"dev": "nodemon --inspect=0.0.0.0 --max-http-header-size=80000 index.js"
},
"author": "David Adewole",
"license": "MIT",
"devDependencies": {
"nodemon": "^2.0.7"
},
"dependencies": {
"axios": "^0.21.1",
"cors": "^2.8.5",
"custom-env": "^2.0.1",
"express": "^4.17.1",
"node-cron": "^3.0.0",
"node-fetch": "^2.6.1",
"node-ssh": "^11.1.1",
"ping": "^0.4.1",
"winston": "^3.3.3",
"winston-slack-transport": "^2.0.0"
}
}

109
application-scanner/utils/api.js Executable file
View File

@@ -0,0 +1,109 @@
const axios = require('axios');
const config = require('./config');
const _this = {
getHeaders: () => {
return {
'Access-Control-Allow-Origin': '*',
Accept: 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
applicationScannerName: config.applicationScannerName,
applicationScannerKey: config.applicationScannerKey,
clusterKey: config.clusterKey,
applicationScannerVersion: config.applicationScannerVersion,
};
},
postApi: (url, data) => {
const headers = _this.getHeaders();
return new Promise((resolve, reject) => {
axios({
method: 'POST',
url: `${config.serverUrl}/${url}`,
headers,
data,
})
.then(function(response) {
resolve(response.data);
})
.catch(function(error) {
if (error && error.response && error.response.data)
error = error.response.data;
if (error && error.data) {
error = error.data;
}
reject(error);
});
});
},
getApi: url => {
const headers = _this.getHeaders();
return new Promise((resolve, reject) => {
axios({
method: 'GET',
url: `${config.serverUrl}/${url}`,
headers,
})
.then(function(response) {
resolve(response.data);
})
.catch(function(error) {
if (error && error.response && error.response.data)
error = error.response.data;
if (error && error.data) {
error = error.data;
}
reject(error);
});
});
},
putApi: (url, data) => {
const headers = _this.getHeaders();
return new Promise((resolve, reject) => {
axios({
method: 'PUT',
url: `${config.serverUrl}/${url}`,
headers,
data,
})
.then(function(response) {
resolve(response.data);
})
.catch(function(error) {
if (error && error.response && error.response.data)
error = error.response.data;
if (error && error.data) {
error = error.data;
}
reject(error);
});
});
},
deleteApi: (url, data) => {
const headers = _this.getHeaders();
return new Promise((resolve, reject) => {
axios({
method: 'DELETE',
url: `${config.serverUrl}/${url}`,
headers,
data,
})
.then(function(response) {
resolve(response.data);
})
.catch(function(error) {
if (error && error.response && error.response.data)
error = error.response.data;
if (error && error.data) {
error = error.data;
}
reject(error);
});
});
},
};
module.exports = _this;

View File

@@ -2,6 +2,6 @@ const { postApi } = require('./api');
module.exports = {
scan: async function(security) {
return await postApi(`probe/scan/git`, { security });
return await postApi(`application/scan/git`, { security });
},
};

View File

@@ -0,0 +1,46 @@
const packageJson = require('../package.json');
const COMMAND = {
linux: {
load: "top -b -n 2 | egrep --color 'load average|%Cpu'",
cpu: "egrep --color 'processor|cores' /proc/cpuinfo",
mem: "egrep --color 'Mem|Swap' /proc/meminfo",
disk: "df -h | egrep --color '/dev/xvda1|/dev/sda7|/dev/nvme0n1p1'",
temp: "sensors | egrep --color 'CPU'",
},
darwin: {
load: "top -l 1 | egrep --color 'Load Avg|CPU usage'",
cpu: 'sysctl -n machdep.cpu.core_count',
mem: {
used: "top -l 1 | egrep --color 'PhysMem'",
total: 'sysctl -n hw.memsize',
swap: 'sysctl -n vm.swapusage',
},
disk: "df -h | egrep --color '/dev/disk1s2'",
temp: 'sysctl -n machdep.xcpm.cpu_thermal_level',
},
win: {
load: 'wmic cpu get loadpercentage',
cpu: 'wmic cpu get numberofcores',
mem: {
free: 'wmic os get freephysicalmemory',
total: 'wmic computersystem get totalphysicalmemory',
totalSwap: 'wmic os get totalvirtualmemorySize',
freeSwap: 'wmic os get freevirtualmemory',
},
disk: {
total: 'wmic logicaldisk get size',
free: 'wmic logicaldisk get freespace',
},
temp: 'wmic computersystem get thermalstate',
},
};
module.exports = {
COMMAND,
serverUrl: process.env['SERVER_URL'],
applicationScannerName: process.env['APPLICATION_SCANNER_NAME'],
applicationScannerKey: process.env['APPLICATION_SCANNER_KEY'],
clusterKey: process.env['CLUSTER_KEY'],
applicationScannerVersion: packageJson.version,
};

View File

@@ -0,0 +1,32 @@
const winston = require('winston');
const Slack = require('winston-slack-transport');
if (
process.env.PORT &&
process.env.SLACK_ERROR_LOG_WEBHOOK &&
process.env.SLACK_ERROR_LOG_CHANNEL
) {
winston.add(Slack, {
webhook_url: process.env.SLACK_ERROR_LOG_WEBHOOK,
channel: '#' + process.env.SLACK_ERROR_LOG_CHANNEL,
username: 'Error Bot',
handleExceptions: true,
});
}
module.exports = {
log: (functionName, error) => {
error = error && error.message ? error.message : error;
winston.error(
JSON.stringify(
{
error: String(error),
functionName: String(functionName),
stack: new Error().stack,
},
0,
2
)
);
},
};

View File

@@ -0,0 +1,58 @@
const fs = require('fs');
const Path = require('path');
const { promisify } = require('util');
const readdir = promisify(fs.readdir);
const rmdir = promisify(fs.rmdir);
const unlink = promisify(fs.unlink);
/**
* @description a promise based utility to read content of a file
* @param {string} filePath path to file
*/
function readFileContent(filePath) {
return new Promise((resolve, reject) => {
if (fs.existsSync(filePath)) {
fs.readFile(filePath, { encoding: 'utf8' }, function(error, data) {
if (error) {
reject(error);
}
resolve(data);
});
}
});
}
/**
* @description an asynchronous function to handle deleting a file
* @param {string} file path to file
*/
async function deleteFile(file) {
if (fs.existsSync(file)) {
await unlink(file);
}
}
/**
* @description a promise based utility to handle deleting a folder and it's content
* @param {string} dir directory with or without file
*/
async function deleteFolderRecursive(dir) {
if (fs.existsSync(dir)) {
const entries = await readdir(dir, { withFileTypes: true });
await Promise.all(
entries.map(entry => {
const fullPath = Path.join(dir, entry.name);
return entry.isDirectory()
? deleteFolderRecursive(fullPath)
: unlink(fullPath);
})
);
await rmdir(dir); // finally remove now empty directory
}
}
module.exports = {
readFileContent,
deleteFile,
deleteFolderRecursive,
};

View File

@@ -0,0 +1,27 @@
/**
*
* Copyright HackerBay, Inc.
*
*/
const getApi = require('../utils/api').getApi;
const ErrorService = require('../utils/errorService');
const ApplicationSecurity = require('./applicationSecurity');
module.exports = {
runApplicationScan: async function() {
try {
const securities = await getApi('application/applicationSecurities');
if (securities && securities.length > 0) {
await Promise.all(
securities.map(security => {
return ApplicationSecurity.scan(security);
})
);
}
return;
} catch (error) {
ErrorService.log('runApplicationScan.getApi', error);
}
},
};

View File

@@ -23,4 +23,8 @@ INTERNAL_SMTP_PORT=2525
INTERNAL_SMTP_FROM=support@fyipe.com
INTERNAL_SMTP_NAME=support
FYIPE_HOST=localhost:3002
BACKEND_PROTOCOL=http
BACKEND_PROTOCOL=http
SCRIPT_RUNNER_URL=http://localhost:3009
SLACK_ERROR_LOG_WEBHOOK=https://hooks.slack.com/services/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
SLACK_ERROR_LOG_CHANNEL=fyipe-logs
PORT=3002

1
backend/.gitignore vendored
View File

@@ -24,6 +24,5 @@ apiTest.rest
application_security_dir
container_security_dir
/greenlock.d
/greenlock.d/config.json
/greenlock.d/config.json.bak

View File

@@ -1 +1 @@
{"configDir":"./greenlock.d/","manager":"fyipe-gl-manager"}
{"manager":"fyipe-gl-manager","configDir":"./greenlock.d"}

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM node:15
FROM node:16
#SET ENV Variables
ENV PRODUCTION=true

33
backend/Dockerfile.dev Normal file
View File

@@ -0,0 +1,33 @@
#
# Fyipe-backend Dockerfile
#
# Pull base image nodejs image.
FROM node:16
#SET ENV Variables
WORKDIR /usr/src/app
# Install trivy for container scanning
RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b /usr/local/bin
# Install app dependencies
RUN cd /usr/src/app
RUN mkdir -p greenlock.d
# Copy package.json files
COPY ./package.json /usr/src/app/package.json
COPY ./package-lock.json /usr/src/app/package-lock.json
RUN npm ci
# Expose ports.
# - 3002: Fyipe-backend
EXPOSE 3002
EXPOSE 9229
#Run the app
CMD [ "npm", "run", "dev"]

View File

@@ -87,17 +87,22 @@ router.get(
projectId,
idNumber,
});
incidentId = incidentId._id;
const skip = req.query.skip || 0;
const limit = req.query.limit || 10;
const alerts = await alertService.findBy({
query: { incidentId: incidentId },
skip,
limit,
});
const count = await alertService.countBy({
incidentId: incidentId,
});
let alerts = [],
count = 0;
if (incidentId) {
incidentId = incidentId._id;
alerts = await alertService.findBy({
query: { incidentId: incidentId },
skip,
limit,
});
count = await alertService.countBy({
incidentId: incidentId,
});
}
return sendListResponse(req, res, alerts, count);
} catch (error) {
return sendErrorResponse(req, res, error);

View File

@@ -0,0 +1,53 @@
/**
*
* Copyright HackerBay, Inc.
*
*/
const express = require('express');
const ApplicationScannerService = require('../services/applicationScannerService');
const ApplicationSecurityService = require('../services/applicationSecurityService');
const router = express.Router();
const isAuthorizedApplicationScanner = require('../middlewares/applicationScannerAuthorization')
.isAuthorizedApplicationScanner;
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const sendItemResponse = require('../middlewares/response').sendItemResponse;
const getUser = require('../middlewares/user').getUser;
const multer = require('multer');
const storage = require('../middlewares/upload');
// Route
// Description: Updating profile setting.
// Params:
// Param 1: req.headers-> {authorization}; req.user-> {id}; req.files-> {profilePic};
// Returns: 200: Success, 400: Error; 500: Server Error.
router.get('/applicationSecurities', isAuthorizedApplicationScanner, async function (
req,
res
) {
try {
const response = await ApplicationSecurityService.getSecuritiesToScan();
return sendItemResponse(req, res, response);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.post('/scan/git', isAuthorizedApplicationScanner, async function (req, res) {
try {
let { security } = req.body;
security = await ApplicationSecurityService.decryptPassword(security);
const securityLog = await ApplicationScannerService.scanApplicationSecurity(
security
);
global.io.emit(`securityLog_${security._id}`, securityLog);
return sendItemResponse(req, res, securityLog);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
module.exports = router;

View File

@@ -4,7 +4,7 @@ const { isAuthorized } = require('../middlewares/authorization');
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const sendItemResponse = require('../middlewares/response').sendItemResponse;
const ApplicationSecurityService = require('../services/applicationSecurityService');
const ProbeService = require('../services/probeService');
const ApplicationScannerService = require('../services/applicationScannerService');
const RealTimeService = require('../services/realTimeService');
const ResourceCategoryService = require('../services/resourceCategoryService');
@@ -311,10 +311,9 @@ router.post(
applicationSecurity
);
const securityLog = await ProbeService.scanApplicationSecurity(
const securityLog = await ApplicationScannerService.scanApplicationSecurity(
applicationSecurity
);
global.io.emit(
`securityLog_${applicationSecurity._id}`,
securityLog

View File

@@ -0,0 +1,270 @@
/**
*
* Copyright HackerBay, Inc.
*
*/
const express = require('express');
const router = express.Router();
const AutomatedScriptService = require('../services/automatedScriptService');
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const sendListResponse = require('../middlewares/response').sendListResponse;
const { sendItemResponse } = require('../middlewares/response');
const { isAuthorized } = require('../middlewares/authorization');
const { getUser } = require('../middlewares/user');
router.get('/:projectId', getUser, isAuthorized, async function(req, res) {
try {
const { projectId } = req.params;
const { skip, limit } = req.query;
const scripts = await AutomatedScriptService.findBy(
{ projectId },
skip,
limit
);
const count = await AutomatedScriptService.countBy({ projectId });
return sendListResponse(req, res, scripts, count);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.get(
'/:projectId/:automatedSlug',
getUser,
isAuthorized,
async (req, res) => {
try {
const { automatedSlug } = req.params;
const { skip, limit } = req.query;
const details = await AutomatedScriptService.findOneBy({
slug: automatedSlug,
});
const logs = await AutomatedScriptService.getAutomatedLogs(
{
automationScriptId: details._id,
},
skip,
limit
);
if (details.successEvent.length > 0) {
details.successEvent = formatEvent(details.successEvent);
}
if (details.failureEvent.length > 0) {
details.failureEvent = formatEvent(details.failureEvent);
}
const count = await AutomatedScriptService.countLogsBy({
automationScriptId: details._id,
});
const response = {
details,
logs,
};
return sendListResponse(req, res, response, count);
} catch (error) {
return sendErrorResponse(req, res, error);
}
}
);
// Route Description: Creates a new script
// req.body -> {name, scriptType, script, successEvent, failureEvent}
// Returns: response new script created
router.post('/:projectId', getUser, isAuthorized, async (req, res) => {
try {
const data = req.body;
data.projectId = req.params.projectId;
if (!data) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Values should not be null',
});
}
if (!data.name || !data.name.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Script name is required',
});
}
if (!data.scriptType || data.scriptType.trim().length === 0) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Script Type is required',
});
}
if (!data.script || data.script.trim().length === 0) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Script is required',
});
}
// check if name already exists
const uniqueName = await AutomatedScriptService.findOneBy({
projectId: data.projectId,
name: data.name,
});
if (uniqueName) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Script name already exists',
});
}
if (data.successEvent.length > 0) {
data.successEvent = formatEvent(data.successEvent, true);
}
if (data.failureEvent.length > 0) {
data.failureEvent = formatEvent(data.failureEvent, true);
}
const response = await AutomatedScriptService.createScript(data);
return sendItemResponse(req, res, response);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
// Route Description: Update a script
// req.body -> {name, scriptType, script, successEvent, failureEvent}
// Returns: response script updated
router.put(
'/:projectId/:automatedScriptId',
getUser,
isAuthorized,
async (req, res) => {
try {
const automatedScriptId = req.params.automatedScriptId;
const data = req.body;
data.projectId = req.params.projectId;
if (!data) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Values should not be null',
});
}
if (!data.name || !data.name.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Script name is required',
});
}
if (!data.scriptType || data.scriptType.trim().length === 0) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Script Type is required',
});
}
if (!data.script || data.script.trim().length === 0) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Script is required',
});
}
// check former name
const formerName = await AutomatedScriptService.findOneBy({
projectId: data.projectId,
_id: automatedScriptId,
});
// check if name already exists
const uniqueName = await AutomatedScriptService.findOneBy({
projectId: data.projectId,
name: data.name,
});
if (data.name !== formerName.name && uniqueName) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Script name already exists',
});
}
if (data.successEvent.length > 0) {
data.successEvent = formatEvent(data.successEvent, true);
}
if (data.failureEvent.length > 0) {
data.failureEvent = formatEvent(data.failureEvent, true);
}
const response = await AutomatedScriptService.updateOne(
{ _id: automatedScriptId },
data
);
return sendItemResponse(req, res, response);
} catch (error) {
return sendErrorResponse(req, res, error);
}
}
);
router.put(
'/:projectId/:automatedScriptId/run',
getUser,
isAuthorized,
async (req, res) => {
try {
const { automatedScriptId } = req.params;
const triggeredId = req.user ? req.user.id : null;
const response = await AutomatedScriptService.runResource({
triggeredId,
triggeredBy: 'user',
resources: { automatedScript: automatedScriptId },
});
return sendItemResponse(req, res, response);
} catch (error) {
return sendErrorResponse(req, res, error);
}
}
);
router.delete(
'/:projectId/:automatedSlug',
getUser,
isAuthorized,
async function(req, res) {
try {
const { automatedSlug } = req.params;
const userId = req.user ? req.user.id : null;
const response = await AutomatedScriptService.deleteBy(
{
slug: automatedSlug,
},
userId
);
return sendItemResponse(req, res, response);
} catch (error) {
return sendErrorResponse(req, res, error);
}
}
);
const formatEvent = (arr, type) => {
const result = [];
for (const item of arr) {
if (type) {
result.push({ [item.type]: item.resource });
} else {
for (const [key, value] of Object.entries(item)) {
if (key !== '_id') {
result.push({
type: String(key),
resource: String(value),
});
}
}
}
}
return result;
};
module.exports = router;

View File

@@ -0,0 +1,60 @@
const express = require('express');
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const sendItemResponse = require('../middlewares/response').sendItemResponse;
const DefaultManagerService = require('../services/defaultManagerService');
const router = express.Router();
// store default details to the db
router.put('/default', async (req, res) => {
try {
const {
store,
challenges,
renewOffset,
renewStagger,
accountKeyType,
serverKeyType,
subscriberEmail,
agreeToTerms,
} = req.body;
if (!subscriberEmail) {
return sendItemResponse(req, res, {});
}
const data = {};
if (store) data.store = store;
if (challenges) data.challenges = challenges;
if (renewOffset) data.renewOffset = renewOffset;
if (renewStagger) data.renewStagger = renewStagger;
if (accountKeyType) data.accountKeyType = accountKeyType;
if (serverKeyType) data.serverKeyType = serverKeyType;
if (subscriberEmail) data.subscriberEmail = subscriberEmail;
if (agreeToTerms) data.agreeToTerms = agreeToTerms;
// if there's no default value
// create a default value
const defaultManager = await DefaultManagerService.updateOneBy(
{ subscriberEmail },
data
);
return sendItemResponse(req, res, defaultManager);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.get('/default', async (req, res) => {
try {
const defaultManager = await DefaultManagerService.findOneBy({
deleted: false,
});
return sendItemResponse(req, res, defaultManager);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
module.exports = router;

View File

@@ -29,9 +29,17 @@ router.put(
verificationToken
);
if (!doesTxtRecordExist) {
const { result, txtRecords } = doesTxtRecordExist;
if (!result) {
const records =
txtRecords.length > 1
? txtRecords.join(', ')
: txtRecords[0];
return sendErrorResponse(req, res, {
message: 'TXT record not found',
message: `Please specify ${verificationToken} in your DNS. Looks like your current ${
txtRecords.length > 1 ? 'records are' : 'record is'
} ${records}`,
code: 400,
});
}

View File

@@ -36,107 +36,107 @@ const Services = require('../utils/services');
// Param 1: req.headers-> {authorization}; req.user-> {id}; req.body-> {monitorId, projectId}
// Returns: 200: Incident, 400: Error; 500: Server Error.
router.post('/:projectId/:monitorId', getUser, isAuthorized, async function(
req,
res
) {
try {
const monitorId = req.params.monitorId;
const projectId = req.params.projectId;
const incidentType = req.body.incidentType;
const incidentPriority = req.body.incidentPriority;
const title = req.body.title;
const description = req.body.description;
const customFields = req.body.customFields;
const userId = req.user ? req.user.id : null;
let oldIncidentsCount = null;
router.post(
'/:projectId/create-incident',
getUser,
isAuthorized,
async function(req, res) {
try {
const projectId = req.params.projectId;
const incidentType = req.body.incidentType;
const incidentPriority = req.body.incidentPriority;
const title = req.body.title;
const description = req.body.description;
const customFields = req.body.customFields;
const monitors = req.body.monitors;
const userId = req.user ? req.user.id : null;
let oldIncidentsCount = null;
if (!monitorId) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Monitor ID must be present.',
});
}
if (typeof monitorId !== 'string') {
return sendErrorResponse(req, res, {
code: 400,
message: 'Monitor ID is not in string type.',
});
}
if (!projectId) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Project ID must be present.',
});
}
if (typeof projectId !== 'string') {
return sendErrorResponse(req, res, {
code: 400,
message: 'Project ID is not in string type.',
});
}
if (!title) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Title must be present.',
});
}
if (incidentType) {
if (!['offline', 'online', 'degraded'].includes(incidentType)) {
// monitors should be an array containing id of monitor(s)
if (monitors && !Array.isArray(monitors)) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Invalid incident type.',
message: 'Monitors is not of type array',
});
}
oldIncidentsCount = await IncidentService.countBy({
projectId,
monitorId,
incidentType,
resolved: false,
deleted: false,
manuallyCreated: true,
});
} else {
return sendErrorResponse(req, res, {
code: 400,
message: 'IncidentType must be present.',
});
}
if (oldIncidentsCount && oldIncidentsCount > 0) {
return sendErrorResponse(req, res, {
code: 400,
message: `An unresolved incident of type ${incidentType} already exists.`,
if (!projectId) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Project ID must be present.',
});
}
if (typeof projectId !== 'string') {
return sendErrorResponse(req, res, {
code: 400,
message: 'Project ID is not in string type.',
});
}
if (!title) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Title must be present.',
});
}
if (incidentType) {
if (!['offline', 'online', 'degraded'].includes(incidentType)) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Invalid incident type.',
});
}
oldIncidentsCount = await IncidentService.countBy({
projectId,
incidentType,
resolved: false,
deleted: false,
manuallyCreated: true,
'monitors.monitorId': { $in: monitors },
});
} else {
return sendErrorResponse(req, res, {
code: 400,
message: 'IncidentType must be present.',
});
}
if (oldIncidentsCount && oldIncidentsCount > 0) {
return sendErrorResponse(req, res, {
code: 400,
message: `An unresolved incident of type ${incidentType} already exists.`,
});
}
// Call the IncidentService
const incident = await IncidentService.create({
projectId,
createdById: userId,
manuallyCreated: true,
incidentType,
title,
description,
incidentPriority,
customFields,
monitors,
});
if (incident) {
for (const monitor of monitors) {
await MonitorStatusService.create({
monitorId: monitor,
incidentId: incident._id,
manuallyCreated: true,
status: incidentType,
});
}
}
return sendItemResponse(req, res, incident);
} catch (error) {
return sendErrorResponse(req, res, error);
}
// Call the IncidentService
const incident = await IncidentService.create({
projectId,
monitorId,
createdById: userId,
manuallyCreated: true,
incidentType,
title,
description,
incidentPriority,
customFields,
});
await MonitorStatusService.create({
monitorId,
incidentId: incident._id,
manuallyCreated: true,
status: incidentType,
});
return sendItemResponse(req, res, incident);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
);
// Route
// Description: Getting all the incidents by monitor Id.
@@ -152,7 +152,7 @@ router.post(
try {
const { startDate, endDate } = req.body;
let query = {
monitorId: req.params.monitorId,
'monitors.monitorId': { $in: [req.params.monitorId] },
projectId: req.params.projectId,
};
@@ -160,7 +160,7 @@ router.post(
const start = moment(startDate).toDate();
const end = moment(endDate).toDate();
query = {
monitorId: req.params.monitorId,
'monitors.monitorId': { $in: [req.params.monitorId] },
projectId: req.params.projectId,
createdAt: { $gte: start, $lte: end },
};
@@ -316,14 +316,14 @@ router.get(
isAuthorized,
async function(req, res) {
try {
const { projectId, incidentId } = req.params;
const { incidentId } = req.params;
const incident = await IncidentService.findOneBy({
projectId,
idNumber: incidentId,
});
// const incident = await IncidentService.findOneBy({
// projectId,
// idNumber: incidentId,
// });
const timeline = await IncidentTimelineService.findBy(
{ incidentId: incident._id },
{ incidentId },
req.query.skip || 0,
req.query.limit || 10
);
@@ -347,9 +347,11 @@ router.get(
: null;
// Call the IncidentService.
const userId = req.user ? req.user.id : null;
const { isHome } = req.query;
const incident = await IncidentService.getUnresolvedIncidents(
subProjectIds,
userId
userId,
isHome
);
return sendItemResponse(req, res, incident);
} catch (error) {
@@ -655,6 +657,9 @@ router.post(
// handle creation or updating
if (!data.id) {
data.createdById = req.user.id;
data.monitors = incident.monitors.map(
monitor => monitor.monitorId
);
incidentMessage = await IncidentMessageService.create(data);
if (data.post_statuspage) {
AlertService.sendInvestigationNoteToSubscribers(
@@ -810,15 +815,19 @@ router.get(
projectId,
idNumber: incidentId,
});
const {
statusPages,
count,
} = await StatusPageService.getStatusPagesForIncident(
incident._id,
parseInt(req.query.skip) || 0,
parseInt(req.query.limit) || 10
);
return sendListResponse(req, res, statusPages, count);
if (incident) {
const {
statusPages,
count,
} = await StatusPageService.getStatusPagesForIncident(
incident._id,
parseInt(req.query.skip) || 0,
parseInt(req.query.limit) || 10
);
return sendListResponse(req, res, statusPages, count);
} else {
return sendListResponse(req, res, [], 0);
}
} catch (error) {
return sendErrorResponse(req, res, error);
}
@@ -934,75 +943,82 @@ router.get(
type = 'internal';
}
try {
let incidentMessages, result;
let incidentMessages,
result = [],
count = 0;
const idNumber = req.params.incidentId;
const projectId = req.params.projectId;
let incidentId = await IncidentService.findOneBy({
projectId,
idNumber,
});
incidentId = incidentId._id;
if (type === 'investigation') {
incidentMessages = await IncidentMessageService.findBy(
{ incidentId, type },
req.query.skip || 0,
req.query.limit || 10
);
} else {
incidentMessages = await IncidentMessageService.findBy({
if (incidentId) {
incidentId = incidentId._id;
if (type === 'investigation') {
incidentMessages = await IncidentMessageService.findBy(
{ incidentId, type },
req.query.skip || 0,
req.query.limit || 10
);
} else {
incidentMessages = await IncidentMessageService.findBy({
incidentId,
type,
});
}
const timeline = await IncidentTimelineService.findBy({
incidentId,
});
const alerts = await AlertService.findBy({
query: { incidentId: incidentId },
});
const subscriberAlerts = await subscriberAlertService.findBy({
incidentId: incidentId,
projectId,
});
count = await IncidentMessageService.countBy({
incidentId,
type,
});
}
const timeline = await IncidentTimelineService.findBy({
incidentId,
});
const alerts = await AlertService.findBy({
query: { incidentId: incidentId },
});
const subscriberAlerts = await subscriberAlertService.findBy({
incidentId: incidentId,
projectId,
});
const count = await IncidentMessageService.countBy({
incidentId,
type,
});
if (type === 'investigation') {
result = incidentMessages;
} else {
const subAlerts = await Services.deduplicate(subscriberAlerts);
let callScheduleStatus = await onCallScheduleStatusService.findBy(
{
query: { incident: incidentId },
}
);
callScheduleStatus = await Services.checkCallSchedule(
callScheduleStatus
);
const timelineAlerts = [
...timeline,
...alerts,
...incidentMessages,
].sort((a, b) => {
return b.createdAt - a.createdAt;
});
incidentMessages = [
...timelineAlerts,
...subAlerts,
...callScheduleStatus,
];
incidentMessages.sort(
(a, b) =>
typeof a.schedule !== 'object' &&
b.createdAt - a.createdAt
);
const filteredMsg = incidentMessages.filter(
a =>
a.status !== 'internal notes added' &&
a.status !== 'internal notes updated'
);
result = await Services.rearrangeDuty(filteredMsg);
if (type === 'investigation') {
result = incidentMessages;
} else {
const subAlerts = await Services.deduplicate(
subscriberAlerts
);
let callScheduleStatus = await onCallScheduleStatusService.findBy(
{
query: { incident: incidentId },
}
);
callScheduleStatus = await Services.checkCallSchedule(
callScheduleStatus
);
const timelineAlerts = [
...timeline,
...alerts,
...incidentMessages,
].sort((a, b) => {
return b.createdAt - a.createdAt;
});
incidentMessages = [
...timelineAlerts,
...subAlerts,
...callScheduleStatus,
];
incidentMessages.sort(
(a, b) =>
typeof a.schedule !== 'object' &&
b.createdAt - a.createdAt
);
const filteredMsg = incidentMessages.filter(
a =>
a.status !== 'internal notes added' &&
a.status !== 'internal notes updated'
);
result = await Services.rearrangeDuty(filteredMsg);
}
}
return sendListResponse(req, res, result, count);
} catch (error) {
@@ -1065,12 +1081,21 @@ router.get(
try {
const userId = req.user ? req.user.id : null;
await IncidentService.resolve(req.params.incidentId, userId);
// get incident properties to build url
const { incidentId, projectId } = req.params;
const incident = await IncidentService.findOneBy({
projectId,
_id: incidentId,
});
const { projectId: project } = incident;
return res.status(200).render('incidentAction.ejs', {
title: 'Incident Resolved',
title_message: 'Incident Resolved',
body_message: 'Your incident is now resolved.',
action: 'resolve',
dashboard_url: global.dashboardHost,
dashboard_url: `${global.dashboardHost}/project/${project.slug}/incidents/${incident.idNumber}`,
apiUrl: global.apiHost,
});
} catch (error) {
@@ -1097,12 +1122,21 @@ router.get(
userId,
req.user.name
);
// get incident properties to build url
const { incidentId, projectId } = req.params;
const incident = await IncidentService.findOneBy({
projectId,
_id: incidentId,
});
const { projectId: project } = incident;
return res.status(200).render('incidentAction.ejs', {
title: 'Incident Acknowledged',
title_message: 'Incident Acknowledged',
body_message: 'Your incident is now acknowledged',
body_message: 'Your incident is now acknowledged.',
action: 'acknowledge',
dashboard_url: global.dashboardHost,
dashboard_url: `${global.dashboardHost}/project/${project.slug}/incidents/${incident.idNumber}`,
apiUrl: global.apiHost,
});
} catch (error) {

View File

@@ -6,7 +6,6 @@ const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const sendListResponse = require('../middlewares/response').sendListResponse;
const sendItemResponse = require('../middlewares/response').sendItemResponse;
const IncidentPrioritiesService = require('../services/incidentPrioritiesService');
const IncidentSettingsService = require('../services/incidentSettingsService');
router.get('/:projectId', getUser, isAuthorized, async function(req, res) {
const { projectId } = req.params;
@@ -126,17 +125,6 @@ router.delete('/:projectId', getUser, isAuthorized, async function(req, res) {
}
try {
const incidentSettings = await IncidentSettingsService.findOne({
projectId,
});
if (`${incidentSettings.incidentPriority}` === `${_id}`)
return sendErrorResponse(req, res, {
code: 400,
message:
'This incident priority is marked as default and cannot be deleted.',
});
const IncidentPriority = await IncidentPrioritiesService.deleteBy({
projectId,
_id,

View File

@@ -4,6 +4,7 @@ const getUser = require('../middlewares/user').getUser;
const { isAuthorized } = require('../middlewares/authorization');
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const sendItemResponse = require('../middlewares/response').sendItemResponse;
const sendListResponse = require('../middlewares/response').sendListResponse;
const IncidentSettingsService = require('../services/incidentSettingsService');
const IncidentPrioritiesService = require('../services/incidentPrioritiesService');
const { variables } = require('../config/incidentDefaultSettings');
@@ -16,75 +17,107 @@ router.get('/variables', async function(req, res) {
}
});
router.get('/:projectId', getUser, isAuthorized, async function(req, res) {
const { projectId } = req.params;
if (!projectId)
return sendErrorResponse(req, res, {
code: 400,
message: 'Project Id must be present',
});
try {
const incidentSettings = await IncidentSettingsService.findOne({
projectId,
});
return sendItemResponse(req, res, incidentSettings);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.put('/:projectId/setDefault', getUser, isAuthorized, async function(
// fetch default incident template in a project
router.get('/:projectId/default', getUser, isAuthorized, async function(
req,
res
) {
const { projectId } = req.params;
const { incidentPriority } = req.body;
if (!projectId)
return sendErrorResponse(req, res, {
code: 400,
message: 'Project Id must be present.',
});
if (!incidentPriority)
return sendErrorResponse(req, res, {
code: 400,
message: 'Incident priority must be present.',
});
try {
// Update Default Incident Priority
const priority = await IncidentPrioritiesService.findOne({
_id: incidentPriority,
});
if (!priority) {
return sendErrorResponse(req, res, {
code: 400,
message: "Incident priority doesn't exist.",
});
const { projectId } = req.params;
if (!projectId) {
const error = new Error('Project Id must be present');
error.code = 400;
throw error;
}
const defaultPrioritySetting = await IncidentSettingsService.updateOne(
{
projectId,
},
{
incidentPriority,
}
);
return sendItemResponse(req, res, defaultPrioritySetting);
const query = { projectId, isDefault: true };
const template = await IncidentSettingsService.findOne(query);
return sendItemResponse(req, res, template);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.put('/:projectId', getUser, isAuthorized, async function(req, res) {
const { projectId } = req.params;
const { title, description, incidentPriority } = req.body;
// fetch all incident template in a project
router.get('/:projectId', getUser, isAuthorized, async function(req, res) {
try {
const { projectId } = req.params;
const { skip, limit } = req.query;
if (!projectId) {
const error = new Error('Project Id must be present');
error.code = 400;
throw error;
}
const query = { projectId };
const templates = await IncidentSettingsService.findBy({
query,
limit,
skip,
});
const count = await IncidentSettingsService.countBy(query);
return sendListResponse(req, res, templates, count);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.put(
'/:projectId/:templateId/setDefault',
getUser,
isAuthorized,
async function(req, res) {
const { projectId, templateId } = req.params;
if (!projectId)
return sendErrorResponse(req, res, {
code: 400,
message: 'Project Id must be present.',
});
try {
const defaultPrioritySetting = await IncidentSettingsService.updateOne(
{
_id: templateId,
projectId,
},
{
isDefault: true,
}
);
return sendItemResponse(req, res, defaultPrioritySetting);
} catch (error) {
return sendErrorResponse(req, res, error);
}
}
);
router.put('/:projectId/:templateId', getUser, isAuthorized, async function(
req,
res
) {
const { projectId, templateId } = req.params;
const { title, description, incidentPriority, isDefault, name } = req.body;
if (!projectId)
return sendErrorResponse(req, res, {
code: 400,
message: 'Project Id must be present.',
});
if (!templateId)
return sendErrorResponse(req, res, {
code: 400,
message: 'Incident settings Id must be present.',
});
if (!name) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Name must be present',
});
}
if (!title)
return sendErrorResponse(req, res, {
code: 400,
@@ -112,11 +145,14 @@ router.put('/:projectId', getUser, isAuthorized, async function(req, res) {
const incidentSettings = await IncidentSettingsService.updateOne(
{
projectId,
_id: templateId,
},
{
title,
description,
incidentPriority,
isDefault,
name,
}
);
return sendItemResponse(req, res, incidentSettings);
@@ -125,4 +161,90 @@ router.put('/:projectId', getUser, isAuthorized, async function(req, res) {
}
});
router.delete('/:projectId/:templateId', getUser, isAuthorized, async function(
req,
res
) {
try {
const { projectId, templateId } = req.params;
if (!projectId) {
const error = new Error('Project Id must be present');
error.code = 400;
throw error;
}
if (!templateId) {
const error = new Error('Incident settings Id must be present.');
error.code = 400;
throw error;
}
const incidentSetting = await IncidentSettingsService.deleteBy({
_id: templateId,
projectId,
});
return sendItemResponse(req, res, incidentSetting);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.post('/:projectId', getUser, isAuthorized, async function(req, res) {
try {
const { projectId } = req.params;
// description is optional
const {
title,
description,
incidentPriority,
isDefault = false,
name,
} = req.body;
if (!projectId) {
const error = new Error('Project Id must be present');
error.code = 400;
throw error;
}
if (!name) {
const error = new Error('Name must be present');
error.code = 400;
throw error;
}
if (!title) {
const error = new Error('Title must be present');
error.code = 400;
throw error;
}
if (!incidentPriority) {
const error = new Error('Incident priority must be present');
error.code = 400;
throw error;
}
const priority = await IncidentPrioritiesService.findOne({
_id: incidentPriority,
});
if (!priority) {
const error = new Error("Incident priority doesn't exist.");
error.code = 400;
throw error;
}
const data = {
projectId,
title,
description,
incidentPriority,
isDefault,
name,
};
const incidentSetting = await IncidentSettingsService.create(data);
return sendItemResponse(req, res, incidentSetting);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
module.exports = router;

View File

@@ -30,6 +30,7 @@ router.post('/', async function(req, res) {
data.country = body.country;
data.message = body.message || null;
data.whitepaperName = body.whitepaper_name || null;
data.source = JSON.parse(body.source) || null;
const lead = await LeadService.create(data);
return sendItemResponse(req, res, lead);
} catch (error) {

View File

@@ -925,4 +925,25 @@ router.post(
}
);
// api to calculate time for monitorInfo (status page)
router.post('/:monitorId/calculate-time', async function(req, res) {
try {
const { monitorId } = req.params;
const { statuses, start, range } = req.body;
const monitor = await MonitorService.findOneBy({ _id: monitorId });
if (!monitor) {
const error = new Error('Monitor not found or does not exist');
error.code = 400;
throw error;
}
const result = await MonitorService.calcTime(statuses, start, range);
result.monitorId = monitor._id;
return sendItemResponse(req, res, result);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
module.exports = router;

View File

@@ -37,30 +37,28 @@ router.get('/:projectId', getUser, isAuthorized, getSubProjects, async function(
}
});
router.put(
'/:projectId/:notificationId/read',
getUser,
isAuthorized,
async function(req, res) {
try {
const notificationId = req.params.notificationId;
const userId = req.user ? req.user.id : null;
router.put('/:projectId/read', getUser, isAuthorized, async function(req, res) {
try {
// const notificationId = req.params.notificationId;
const userId = req.user ? req.user.id : null;
const { notificationIds } = req.body;
const notifications = [];
for (const notificationId of notificationIds) {
const notification = await NotificationService.updateOneBy(
{ _id: notificationId },
{ read: [userId] }
);
if (notification) {
return sendItemResponse(req, res, notification);
} else {
const error = new Error('Notification not found.');
error.code = 400;
return sendErrorResponse(req, res, error);
notifications.push(notificationId);
}
} catch (error) {
return sendErrorResponse(req, res, error);
}
return sendItemResponse(req, res, notifications);
} catch (error) {
return sendErrorResponse(req, res, error);
}
);
});
router.put(
'/:projectId/:notificationId/closed',

View File

@@ -9,7 +9,6 @@ const ProbeService = require('../services/probeService');
const MonitorService = require('../services/monitorService');
const ProjectService = require('../services/projectService');
const LighthouseLogService = require('../services/lighthouseLogService');
const ApplicationSecurityService = require('../services/applicationSecurityService');
const ContainerSecurityService = require('../services/containerSecurityService');
const router = express.Router();
const isAuthorizedAdmin = require('../middlewares/clusterAuthorization')
@@ -75,39 +74,6 @@ router.delete('/:id', getUser, isAuthorizedAdmin, async function(req, res) {
// Params:
// Param 1: req.headers-> {authorization}; req.user-> {id}; req.files-> {profilePic};
// Returns: 200: Success, 400: Error; 500: Server Error.
router.put('/update/image', getUser, async function(req, res) {
try {
const upload = multer({
storage,
}).fields([
{
name: 'probeImage',
maxCount: 1,
},
]);
upload(req, res, async function(error) {
const probeId = req.body.id;
const data = req.body;
if (error) {
return sendErrorResponse(req, res, error);
}
if (
req.files &&
req.files.probeImage &&
req.files.probeImage[0].filename
) {
data.probeImage = req.files.probeImage[0].filename;
}
// Call the ProbeService
const save = await ProbeService.updateOneBy({ _id: probeId }, data);
return sendItemResponse(req, res, save);
});
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.get('/monitors', isAuthorizedProbe, async function(req, res) {
try {
@@ -328,27 +294,8 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
failedReasons: upFailedReasons,
matchedCriterion: matchedUpCriterion,
} = await (monitor && monitor.criteria && monitor.criteria.up
? ProbeService.scriptConditions(
res,
resp,
monitor.criteria.up
)
: { stat: false, reasons: [] });
const {
stat: validDegraded,
successReasons: degradedSuccessReasons,
failedReasons: degradedFailedReasons,
matchedUpCriterion: matchedDegradedCriterion,
} = await (monitor &&
monitor.criteria &&
monitor.criteria.degraded
? ProbeService.scriptConditions(
res,
resp,
monitor.criteria.degraded
)
: { stat: false, reasons: [] });
? ProbeService.scriptConditions(resp, monitor.criteria.up)
: { stat: false, successReasons: [], failedReasons: [] });
const {
stat: validDown,
@@ -356,38 +303,49 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
failedReasons: downFailedReasons,
matchedCriterion: matchedDownCriterion,
} = await (monitor && monitor.criteria && monitor.criteria.down
? ProbeService.scriptConditions(res, resp, [
? ProbeService.scriptConditions(resp, [
...monitor.criteria.down.filter(
criterion => criterion.default !== true
),
])
: { stat: false, reasons: [] });
: { stat: false, successReasons: [], failedReasons: [] });
const {
stat: validDegraded,
successReasons: degradedSuccessReasons,
failedReasons: degradedFailedReasons,
matchedCriterion: matchedDegradedCriterion,
} = await (monitor &&
monitor.criteria &&
monitor.criteria.degraded
? ProbeService.scriptConditions(
resp,
monitor.criteria.degraded
)
: { stat: false, successReasons: [], failedReasons: [] });
if (validUp) {
data.status = 'online';
data.reason = upSuccessReasons;
status = 'online';
reason = upSuccessReasons;
matchedCriterion = matchedUpCriterion;
} else if (validDown) {
status = 'offline';
reason = [...downSuccessReasons, ...upFailedReasons];
matchedCriterion = matchedDownCriterion;
} else if (validDegraded) {
data.status = 'degraded';
data.reason = [
status = 'degraded';
reason = [
...degradedSuccessReasons,
...upFailedReasons,
...downFailedReasons,
];
matchedCriterion = matchedDegradedCriterion;
} else if (validDown) {
data.status = 'offline';
data.reason = [
...downSuccessReasons,
...degradedFailedReasons,
...upFailedReasons,
];
matchedCriterion = matchedDownCriterion;
} else {
data.status = 'offline';
data.reason = [
status = 'offline';
reason = [
...downFailedReasons,
...degradedFailedReasons,
...upFailedReasons,
...degradedFailedReasons,
];
if (monitor.criteria.down) {
matchedCriterion = monitor.criteria.down.find(
@@ -395,7 +353,7 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
);
}
}
resp.status = null;
data.status = status;
data.reason = reason;
}
@@ -583,6 +541,15 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
}
}
if (type === 'script') {
data.scriptMetadata = {
executionTime: resp.executionTime,
consoleLogs: resp.consoleLogs,
error: resp.error,
statusText: resp.statusText,
};
}
data.matchedCriterion = matchedCriterion;
// update monitor to save the last matched criterion
await MonitorService.updateOneBy(
@@ -709,35 +676,7 @@ router.get('/:projectId/probes', getUser, isAuthorized, async function(
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.get('/applicationSecurities', isAuthorizedProbe, async function(
req,
res
) {
try {
const response = await ApplicationSecurityService.getSecuritiesToScan();
return sendItemResponse(req, res, response);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.post('/scan/git', isAuthorizedProbe, async function(req, res) {
try {
let { security } = req.body;
security = await ApplicationSecurityService.decryptPassword(security);
const securityLog = await ProbeService.scanApplicationSecurity(
security
);
global.io.emit(`securityLog_${security._id}`, securityLog);
return sendItemResponse(req, res, securityLog);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
})
router.get('/containerSecurities', isAuthorizedProbe, async function(req, res) {
try {

View File

@@ -9,6 +9,7 @@ const AirtableService = require('../services/airtableService');
const getUser = require('../middlewares/user').getUser;
const isUserMasterAdmin = require('../middlewares/user').isUserMasterAdmin;
const isUserOwner = require('../middlewares/project').isUserOwner;
const isUserAdmin = require('../middlewares/project').isUserAdmin;
const { IS_SAAS_SERVICE } = require('../config/server');
const { isAuthorized } = require('../middlewares/authorization');
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
@@ -256,7 +257,7 @@ router.put(
'/:projectId/renameProject',
getUser,
isAuthorized,
isUserOwner,
isUserAdmin,
async function(req, res) {
try {
const projectId = req.params.projectId;
@@ -856,6 +857,18 @@ router.get('/projects/:slug', getUser, isUserMasterAdmin, async function(
}
});
router.get('/project-slug/:slug', getUser, async function(req, res) {
try {
const { slug } = req.params;
const project = await ProjectService.findOneBy({
slug,
});
return sendItemResponse(req, res, project);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.put(
'/:projectId/blockProject',
getUser,

View File

@@ -10,6 +10,7 @@ const sendItemResponse = require('../middlewares/response').sendItemResponse;
const { getSubProjects } = require('../middlewares/subProject');
const ScheduledEventNoteService = require('../services/scheduledEventNoteService');
const moment = require('moment');
const MonitorService = require('../services/monitorService');
router.post('/:projectId', getUser, isAuthorized, async function(req, res) {
try {
@@ -294,6 +295,19 @@ router.put('/:projectId/:eventId/cancel', getUser, isAuthorized, async function(
});
}
if (fetchEvent && !fetchEvent.monitorDuringEvent) {
for (const monitor of fetchEvent.monitors) {
await MonitorService.updateOneBy(
{
_id: monitor.monitorId._id || monitor.monitorId,
},
{
shouldNotMonitor: false,
}
);
}
}
const event = await ScheduledEventService.updateBy(
{ _id: eventId },
{
@@ -305,20 +319,22 @@ router.put('/:projectId/:eventId/cancel', getUser, isAuthorized, async function(
const scheduledEvent = event[0];
if (scheduledEvent.alertSubscriber) {
// handle this asynchronous operation in the background
AlertService.sendCancelledScheduledEventToSubscribers(
scheduledEvent
);
}
if (scheduledEvent) {
if (scheduledEvent.alertSubscriber) {
// handle this asynchronous operation in the background
AlertService.sendCancelledScheduledEventToSubscribers(
scheduledEvent
);
}
await ScheduledEventNoteService.create({
content: 'THIS SCHEDULED EVENT HAS BEEN CANCELLED',
scheduledEventId: scheduledEvent._id,
createdById: scheduledEvent.createdById._id,
type: 'investigation',
event_state: 'Cancelled',
});
await ScheduledEventNoteService.create({
content: 'THIS SCHEDULED EVENT HAS BEEN CANCELLED',
scheduledEventId: scheduledEvent._id,
createdById: scheduledEvent.createdById._id,
type: 'investigation',
event_state: 'Cancelled',
});
}
return sendItemResponse(req, res, scheduledEvent);
} catch (error) {
@@ -564,8 +580,12 @@ router.post('/:projectId/:eventId/notes', getUser, isAuthorized, async function(
const data = req.body;
data.scheduledEventId = eventId;
data.createdById = userId;
if (!data.scheduledEventId) {
if (
!data.scheduledEventId ||
!data.scheduledEventId.trim() ||
data.scheduledEventId === undefined ||
data.scheduledEventId === 'undefined'
) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Scheduled Event ID is required.',

View File

@@ -289,7 +289,7 @@ const getIncidents = async (projectIds, val, parentProjectId) => {
parentProjectId === String(incident.projectId._id),
projectName: incident.projectId.name,
componentId: incident.monitorId.componentId.slug,
notificationId: incident.notificationId,
notifications: incident.notifications,
incident: incident,
};
}),

View File

@@ -0,0 +1,120 @@
const express = require('express');
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const sendItemResponse = require('../middlewares/response').sendItemResponse;
const SiteManagerService = require('../services/siteManagerService');
const router = express.Router();
// store site details to the db
router.post('/site', async (req, res) => {
try {
const data = req.body;
const site = await SiteManagerService.create(data);
return sendItemResponse(req, res, site);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
// update site details in the db
router.put('/site', async (req, res) => {
try {
const { subject } = req.query;
const site = await SiteManagerService.updateOneBy(
{ subject },
req.body
);
return sendItemResponse(req, res, site);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
// fetch a site detail
router.get('/site', async (req, res) => {
try {
const { servername } = req.query;
const site = await SiteManagerService.findOneBy({
subject: servername,
});
return sendItemResponse(req, res, site);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
// fetch all sites
router.get('/sites', async (req, res) => {
try {
const sites = await SiteManagerService.findBy({});
return sendItemResponse(req, res, sites);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
// fetch all sites by servernames
router.post('/site/servernames', async (req, res) => {
try {
const { servernames = [] } = req.body;
const sites = await SiteManagerService.findBy({
subject: { $in: servernames },
});
return sendItemResponse(req, res, sites);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
// fetch sites base on the options
router.post('/site/opts', async (req, res) => {
try {
const { issuedBefore, expiresBefore, renewBefore } = req.body;
const query = { $or: [] };
if (issuedBefore) {
query.$or.push({
issuedAt: {
$lt: issuedBefore,
},
});
}
if (expiresBefore) {
query.$or.push({
expiresAt: {
$lt: expiresBefore,
},
});
}
if (renewBefore) {
query.$or.push({
renewAt: {
$lt: renewBefore,
},
});
}
query.deleted = false;
const sites = await SiteManagerService.findBy(query);
return sendItemResponse(req, res, sites);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
// delete an site detail
router.delete('/site', async (req, res) => {
try {
const { subject } = req.query;
const site = await SiteManagerService.deleteBy({ subject });
return sendItemResponse(req, res, site);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
module.exports = router;

View File

@@ -37,6 +37,9 @@ router.get('/challenge/authorization/:token', async (req, res) => {
try {
const { token } = req.params;
const acmeChallenge = await SslService.findOneBy({ token });
if (!acmeChallenge) {
return sendItemResponse(req, res, '');
}
return sendItemResponse(req, res, acmeChallenge.keyAuthorization);
} catch (error) {
return sendErrorResponse(req, res, error);

View File

@@ -327,12 +327,14 @@ router.get('/tlsCredential', async function(req, res) {
user
);
let domainObj;
statusPage.domains.forEach(eachDomain => {
if (eachDomain.domain === domain) {
domainObj = eachDomain;
}
});
let domainObj = {};
statusPage &&
statusPage.domains &&
statusPage.domains.forEach(eachDomain => {
if (eachDomain.domain === domain) {
domainObj = eachDomain;
}
});
return sendItemResponse(req, res, {
cert: domainObj.cert,
@@ -705,10 +707,9 @@ router.get('/:statusPageId/rss', checkUser, async function(req, res) {
pubDate: new Date(incident.createdAt).toUTCString(),
description: `<![CDATA[Description: ${
incident.description
}<br>Incident Id: ${incident._id.toString()} <br>Monitor's Name: ${
incident.monitorId.name
}
<br>Monitor's Id: ${incident.monitorId._id.toString()} <br>Acknowledge Time: ${
}<br>Incident Id: ${incident._id.toString()} <br>Monitor Name(s): ${handleMonitorList(
incident.monitors
)}<br>Acknowledge Time: ${
incident.acknowledgedAt
}<br>Resolve Time: ${incident.resolvedAt}<br>${
incident.investigationNote
@@ -768,8 +769,10 @@ router.get(
let result;
const statusPageSlug = req.params.statusPageSlug;
const skip = req.query.skip || 0;
const limit = req.query.limit || 5;
const limit = req.query.limit || 10;
const days = req.query.days || 14;
const newTheme = req.query.newTheme;
try {
// Call the StatusPageService.
const response = await StatusPageService.getNotes(
@@ -780,7 +783,7 @@ router.get(
const notes = response.notes;
const count = response.count;
const updatedNotes = [];
if (parseInt(limit) === 15) {
if (newTheme) {
if (notes.length > 0) {
for (const note of notes) {
const statusPageNote = await StatusPageService.getIncidentNotes(
@@ -792,7 +795,7 @@ router.get(
const sortMsg = statusPageNote.message.reverse();
updatedNotes.push({
...note._doc,
...note,
message: sortMsg,
});
}
@@ -882,7 +885,7 @@ router.get('/:projectId/:monitorId/individualnotes', checkUser, async function(
const skip = req.query.skip || 0;
const limit = req.query.limit || 5;
const query = {
monitorId: req.params.monitorId,
'monitors.monitorId': req.params.monitorId,
deleted: false,
createdAt: { $gte: start, $lt: end },
};
@@ -908,7 +911,7 @@ router.get('/:projectId/:monitorId/individualnotes', checkUser, async function(
const sortMsg = statusPageNote.message.reverse();
updatedNotes.push({
...note._doc,
...note,
message: sortMsg,
});
}
@@ -1445,7 +1448,7 @@ router.get(
if ((theme && typeof theme === 'boolean') || theme === 'true') {
const updatedLogs = [];
for (const log of announcementLogs) {
updatedLogs.push({ ...log._doc });
updatedLogs.push({ ...log });
}
announcementLogs = formatNotes(updatedLogs, 20);
announcementLogs = checkDuplicateDates(announcementLogs);
@@ -1605,4 +1608,21 @@ function checkDuplicateDates(items) {
return result;
}
function handleMonitorList(monitors) {
if (monitors.length === 1) {
return monitors[0].monitorId.name;
}
if (monitors.length === 2) {
return `${monitors[0].monitorId.name} and ${monitors[1].monitorId.name}`;
}
if (monitors.length === 3) {
return `${monitors[0].monitorId.name}, ${monitors[1].monitorId.name} and ${monitors[2].monitorId.name}`;
}
if (monitors.length > 3) {
return `${monitors[0].monitorId.name}, ${
monitors[1].monitorId.name
} and ${monitors.length - 2} others`;
}
}
module.exports = router;

View File

@@ -103,18 +103,23 @@ router.get('/:projectId/incident/:incidentId', async (req, res) => {
projectId,
idNumber,
});
incidentId = incidentId._id;
const skip = req.query.skip || 0;
const limit = req.query.limit || 10;
const subscriberAlerts = await SubscriberAlertService.findBy(
{ incidentId: incidentId, projectId: projectId },
skip,
limit
);
const count = await SubscriberAlertService.countBy({
incidentId: incidentId,
projectId: projectId,
});
let subscriberAlerts = [],
count = 0;
if (incidentId) {
incidentId = incidentId._id;
subscriberAlerts = await SubscriberAlertService.findBy(
{ incidentId: incidentId, projectId: projectId },
skip,
limit
);
count = await SubscriberAlertService.countBy({
incidentId: incidentId,
projectId: projectId,
});
}
return sendListResponse(req, res, subscriberAlerts, count);
} catch (error) {
return sendErrorResponse(req, res, error);

View File

@@ -309,7 +309,7 @@ router.get('/sso/login', async function(req, res) {
message: 'Domain not found.',
});
}
const { 'saml-enabled': samlEnabled, samlSsoUrl } = sso;
const { 'saml-enabled': samlEnabled, remoteLoginUrl } = sso;
if (!samlEnabled) {
return sendErrorResponse(req, res, {
@@ -321,9 +321,8 @@ router.get('/sso/login', async function(req, res) {
const sp = new saml2.ServiceProvider({
entity_id: sso.entityId,
});
const idp = new saml2.IdentityProvider({
sso_login_url: samlSsoUrl,
sso_login_url: remoteLoginUrl,
});
sp.create_login_request_url(idp, {}, function(error, login_url) {
@@ -1316,6 +1315,14 @@ router.get('/users/:userId', getUser, isUserMasterAdmin, async function(
router.delete('/:userId', getUser, isUserMasterAdmin, async function(req, res) {
try {
const userId = req.params.userId;
const authUserId = req.user.id;
if (userId === authUserId) {
const err = new Error(
"Invalid operation! You can't perform this operation on your own account"
);
err.code = 400;
throw err;
}
const masterUserId = req.user.id || null;
const user = await UserService.deleteBy({ _id: userId }, masterUserId);
return sendItemResponse(req, res, user);
@@ -1330,6 +1337,14 @@ router.put('/:userId/restoreUser', getUser, isUserMasterAdmin, async function(
) {
try {
const userId = req.params.userId;
const authUserId = req.user.id;
if (userId === authUserId) {
const err = new Error(
"Invalid operation! You can't perform this operation on your own account"
);
err.code = 400;
throw err;
}
const user = await UserService.restoreBy({
_id: userId,
deleted: true,
@@ -1346,6 +1361,14 @@ router.put('/:userId/blockUser', getUser, isUserMasterAdmin, async function(
) {
try {
const userId = req.params.userId;
const authUserId = req.user.id;
if (userId === authUserId) {
const err = new Error(
"Invalid operation! You can't perform this operation on your own account"
);
err.code = 400;
throw err;
}
const user = await UserService.updateOneBy(
{ _id: userId },
{ isBlocked: true }
@@ -1362,6 +1385,14 @@ router.put('/:userId/unblockUser', getUser, isUserMasterAdmin, async function(
) {
try {
const userId = req.params.userId;
const authUserId = req.user.id;
if (userId === authUserId) {
const err = new Error(
"Invalid operation! You can't perform this operation on your own account"
);
err.code = 400;
throw err;
}
const user = await UserService.updateOneBy(
{ _id: userId },
{ isBlocked: false }
@@ -1372,6 +1403,66 @@ router.put('/:userId/unblockUser', getUser, isUserMasterAdmin, async function(
}
});
// Route
// Description: Switch user account to admin mode.
// Params:
// Param 1: req.body-> {temporaryPassword}; req.headers-> {authorization}; req.user-> {id};
// Returns: 200: Success, 400: Error; 401: Unauthorized; 500: Server Error.
router.post(
'/:userId/switchToAdminMode',
getUser,
isUserMasterAdmin,
async function(req, res) {
try {
const userId = req.params.userId;
const authUserId = req.user.id;
if (userId === authUserId) {
const err = new Error(
"Invalid operation! You can't perform this operation on your own account"
);
err.code = 400;
throw err;
}
const { temporaryPassword } = req.body;
const user = await UserService.switchToAdminMode(
userId,
temporaryPassword
);
return sendItemResponse(req, res, user);
} catch (error) {
return sendErrorResponse(req, res, error);
}
}
);
// Route
// Description: Switch off admin mode for user account
// Params:
// Param 1: req.headers-> {authorization}; req.user-> {id};
// Returns: 200: Success, 400: Error; 401: Unauthorized; 500: Server Error.
router.post(
'/:userId/exitAdminMode',
getUser,
isUserMasterAdmin,
async function(req, res) {
try {
const userId = req.params.userId;
const authUserId = req.user.id;
if (userId === authUserId) {
const err = new Error(
"Invalid operation! You can't perform this operation on your own account"
);
err.code = 400;
throw err;
}
const user = await UserService.exitAdminMode(userId);
return sendItemResponse(req, res, user);
} catch (error) {
return sendErrorResponse(req, res, error);
}
}
);
router.post('/:userId/addNote', getUser, isUserMasterAdmin, async function(
req,
res

View File

@@ -5,7 +5,8 @@
*/
const mongoose = require('mongoose');
const mongoUrl = process.env['MONGO_URL'];
const mongoUrl =
process.env['MONGO_URL'] || 'mongodb://localhost:27017/fyipedb';
mongoose.set('useNewUrlParser', true);
mongoose.set('useUnifiedTopology', true);

View File

@@ -566,7 +566,7 @@ width="500" style="min-width: 500px;margin: 40px 50px;">
'{{length}} : Length of the incident',
'{{unsubscribeUrl}} : URL to unsubscribe from the monitor',
],
emailType: 'Subscriber Incident Acknowldeged',
emailType: 'Subscriber Incident Acknowledged',
subject: '{{projectName}} - {{monitorName}}: Incident Acknowledged',
body: `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@@ -4163,6 +4163,516 @@ Fyipe Team.
</tbody>
</table>
<table class="st-Footer st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--divider" colspan="3" height="20" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr class="st-Divider">
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td bgcolor="#fdfdfd" colspan="2" height="1" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--divider" colspan="3" height="31" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--caption" style="border: 0; margin: 0;padding: 0; color: #8898aa; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 12px; line-height: 16px;">
<span class="st-Delink st-Delink--footer" style="border: 0; margin: 0; padding: 0; color: #8898aa; text-decoration: none;">
© {{year}} HackerBay Inc. | <span><a href={{unsubscribeUrl}}>Unsubscribe</a></span>
</span>
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Spacer st-Spacer--emailEnd" height="64" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<img border="0" style="height:1px; width:1px; border: 0; margin: 0; padding: 0; color: #000000; display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 12px; font-weight: normal;" src="{{trackEmailAsViewedUrl}}">
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--emailEnd" colspan="3" height="64" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!-- /Wrapper -->
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--emailEnd" height="64" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler">&nbsp;</div>
</td>
</tr>
</tbody>
</table>
<!-- /Background -->
</body></html>
`,
},
{
allowedVariables: [
'{{announcementTitle}} : Name of the maintenance event.',
'{{announcementDescription}} : Description of the scheduled event.',
'{{unsubscribeUrl}} : URL to unsubscribe from the monitor',
'{{projectName}} : Name of project on which announcement is created',
'{{resourcesAffected}} : List of monitors affected by scheduled maintenance event',
],
emailType: 'Subscriber Announcement Notification Created',
subject: `Announcement Notification for {{projectName}}`,
body: `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=3Dutf-8">
<meta name="viewport" content="width=3Ddevice-width">
<title>Welcome to Fyipe.</title>
<style>
/**
* IMPORTANT:
* Please read before changing anything, CSS involved in our HTML emails is
* extremely specific and written a certain way for a reason. It might not make
* sense in a normal setting but Outlook loves it this way.
*
* !!! [override] prevents Yahoo Mail breaking media queries. It must be used
* !!! at the beginning of every line of CSS inside a media query.
* !!! Do not remove.
*
* !!! div[style*="margin: 16px 0"] allows us to target a weird margin
* !!! bug in Android's email client.
* !!! Do not remove.
*
* Also, the img files are hosted on S3. Please don't break these URLs!
* The images are also versioned by date, so please update the URLs accordingly
* if you create new versions
*
***/
/**
* # Root
* - CSS resets and general styles go here.
**/
html, body,
a, span, div[style*="margin: 16px 0"] {
border: 0 !important;
margin: 0 !important;
outline: 0 !important;
padding: 0 !important;
text-decoration: none !important;
}
a, span,
td, th {
-webkit-font-smoothing: antialiased !important;
-moz-osx-font-smoothing: grayscale !important;
}
/**
* # Delink
* - Classes for overriding clients which creates links out of things like
* emails, addresses, phone numbers, etc.
**/
span.st-Delink a {
color: #000000 !important;
text-decoration: none !important;
}
/** Modifier: preheader */
span.st-Delink.st-Delink--preheader a {
color: white !important;
text-decoration: none !important;
}
/** */
/** Modifier: title */
span.st-Delink.st-Delink--title a {
color: #000000 !important;
text-decoration: none !important;
}
/** */
/** Modifier: footer */
span.st-Delink.st-Delink--footer a {
color: #8898aa !important;
text-decoration: none !important;
}
/** */
.ii a[href] {
color: #000000;
}
/**
* # Mobile
* - This affects emails views in clients less than 600px wide.
**/
@media all and (max-width: 600px) {
/**
* # Wrapper
**/
body[override] table.st-Wrapper,
body[override] table.st-Width.st-Width--mobile {
min-width: 100% !important;
width: 100% !important;
}
/**
* # Spacer
**/
/** Modifier: gutter */
body[override] td.st-Spacer.st-Spacer--gutter {
width: 32px !important;
}
/** */
/** Modifier: kill */
body[override] td.st-Spacer.st-Spacer--kill {
width: 0 !important;
}
/** */
/** Modifier: emailEnd */
body[override] td.st-Spacer.st-Spacer--emailEnd {
height: 32px !important;
}
/** */
/**
* # Font
**/
/** Modifier: simplified */
body[override] table.st-Header.st-Header--simplified td.st-Header-logo {
width: auto !important;
}
body[override] table.st-Header.st-Header--simplified td.st-Header-spacing{
width: 0 !important;
}
/**
* # Blocks
**/
body[override] table.st-Blocks table.st-Blocks-inner {
border-radius: 0 !important;
}
body[override] table.st-Blocks table.st-Blocks-inner table.st-Blocks-item td.st-Blocks-item-cell {
display: block !important;
}
/**
* # Button
**/
body[override] table.st-Button {
margin: 0 auto !important;
width: 100% !important;
}
body[override] table.st-Button td.st-Button-area,
body[override] table.st-Button td.st-Button-area a.st-Button-link,
body[override] table.st-Button td.st-Button-area span.st-Button-internal{
height: 44px !important;
line-height: 44px !important;
font-size: 18px !important;
vertical-align: middle !important;
}
}
</style>
</head>
<body class="st-Email" bgcolor="f7f7f7" style="border: 0; margin: 0; padding: 0; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; min-width: 100%; width: 100%;" override="fix">
<!-- Background -->
<table class="st-Background" bgcolor="f7f7f7" border="0" cellpadding="0" cellspacing="0" width="100%" style="border: 0; margin: 0; padding: 0;">
<tbody>
<tr>
<td style="border: 0; margin: 0; padding: 0;">
<!-- Wrapper -->
<table class="st-Wrapper" align="center" bgcolor="ffffff" border="0" cellpadding="0" cellspacing="0" width="600" style="border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; margin: 0 auto; min-width: 600px;">
<tbody>
<tr>
<td style="border: 0; margin: 0; padding: 0;">
<table class="st-Header st-Header--simplified st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--divider" colspan="4" height="19" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td align="left" style="height:80px; border: 0; margin: 0; padding: 0;">
<div>
<a style="border: 0; margin: 0; padding: 0; text-decoration: none;" href="https://fyipe.com">
<img alt="Fyipe" border="0" style="height:50px; width:50px; border: 0; margin: 0; padding: 0; color: #000000; display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 12px; font-weight: normal;" src="https://www.dropbox.com/s/dwawm02f1toxnm8/Fyipe-Icon.png?dl=0&raw=1">
</a>
</div>
</td>
<td class="st-Header-spacing" width="423" style="border: 0; margin: 0; padding: 0;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--divider" colspan="4" height="19" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr class="st-Divider">
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td bgcolor="#fdfdfd" colspan="2" height="1" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body" style="color: #000000 !important; border:0;margin:0;padding:0; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Ubuntu,sans-serif;font-size:16px;line-height:24px">
<h3>Announcement notification for {{projectName}}</h3>
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body" style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
Hi.
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body" style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
The following announcement has just been made:
</td>
</tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
width="500" style="min-width: 500px;margin: 40px 50px;">
<tbody>
<tr style="border-collapse:collapse">
<td width="720" align="center" valign="top" style="padding:0;Margin:0"></td>
</tr>
<tr style="border-collapse:collapse">
<td align="left"
style="padding:0;Margin:0;padding-top:0px;padding-left:40px;padding-right:40px;padding-bottom:30px;background-color:#f7f8fa;border-radius: 5px">
<table cellpadding="0" cellspacing="0" width="100%"
style="border-collapse:collapse;border-spacing:0px">
<tbody>
<tr style="border-collapse:collapse">
<td width="720" align="center" valign="top" style="padding:0;Margin:0">
<table cellpadding="0" cellspacing="0" width="100%" role="presentation"
style="border-collapse:collapse;border-spacing:0px">
<tbody>
<tr style="border-collapse:collapse">
<td align="left" style="padding:0;Margin:0;padding-top:30px">
<p
style="Margin:0;font-size:16px;font-family:'inter','helvetica neue',helvetica,arial,sans-serif;line-height:30px;color:#424761">
<strong>Title: </strong>
<span>{{announcementTitle}}</span><br></p>
<p
style="Margin:0;font-size:16px;font-family:'inter','helvetica neue',helvetica,arial,sans-serif;line-height:30px;color:#424761">
<strong>Content: </strong>
<span>"{{announcementDescription}}"</span><br></p>
{{#if resourcesAffected}}
<p style="Margin:0;font-size:16px;font-family:'inter','helvetica neue',helvetica,arial,sans-serif;line-height:30px;color:#424761">
<strong>Resource Affected: </strong>
<span>{{resourcesAffected}}</span><br></p>
{{/if}}
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body" style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
{{#if statusPageUrl}}
You can view the status of the incident here {{statusPageUrl}}
{{/if}}
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body" style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
You are receiving this mail because you are subscribed to this monitor.
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body" style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
Fyipe Team.
</td>
<td class="st-Spacer st-Spacer--gutter" style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;" width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12" style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Footer st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>

View File

@@ -12,7 +12,7 @@ module.exports = {
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
'Subscriber Incident Acknowldeged': [
'Subscriber Incident Acknowledged': [
'{{userName}} : User display name.',
'{{monitorName}} : Name of the monitor on which incident has occured.',
'{{projectName}} : Name of the project on which the incident has occured.',

View File

@@ -29,7 +29,7 @@ module.exports = {
planId: 'plan_GoWIqpBpStiqQp',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
monitorLimit: 5,
userLimit: 1,
extraUserFee: 264,
@@ -51,7 +51,7 @@ module.exports = {
planId: 'plan_GoWKiTdQ6NiQFw',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
monitorLimit: 10,
userLimit: 1,
extraUserFee: 588,
@@ -62,7 +62,7 @@ module.exports = {
planId: 'plan_H9Iox3l2YqLTDR',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
monitorLimit: 9999,
userLimit: 1,
extraUserFee: 99,
@@ -73,7 +73,7 @@ module.exports = {
planId: 'plan_H9IlBKhsFz4hV2',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
monitorLimit: 10,
userLimit: 1,
extraUserFee: 588,
@@ -98,7 +98,7 @@ module.exports = {
planId: 'plan_GoVgJu5PKMLRJU',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
monitorLimit: 5,
userLimit: 1,
extraUserFee: 264,
@@ -120,7 +120,7 @@ module.exports = {
planId: 'plan_GoViZshjqzZ0vv',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
monitorLimit: 10,
userLimit: 1,
extraUserFee: 588,
@@ -131,7 +131,7 @@ module.exports = {
planId: 'plan_H9Ii6Qj3HLdtty',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
monitorLimit: 9999,
userLimit: 1,
extraUserFee: 99,
@@ -142,7 +142,7 @@ module.exports = {
planId: 'plan_H9IjvX2Flsvlcg',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
monitorLimit: 10,
userLimit: 1,
extraUserFee: 588,

View File

@@ -26,7 +26,7 @@ module.exports = [
'{{monitor.customFields.*}} : The value of any monitor custom field',
'{{length}} : Length of the incident',
],
smsType: 'Subscriber Incident Acknowldeged',
smsType: 'Subscriber Incident Acknowledged',
body:
'{{projectName}} - {{incidentType}} incident on {{monitorName}} is acknowledged at {{incidentTime}}. You are receiving this message because you subscribed to this monitor.',
},
@@ -105,4 +105,14 @@ module.exports = [
body:
'Scheduled maintenance event for {{projectName}} - {{eventName}}, has been cancelled.',
},
{
allowedVariables: [
'{{announcementTitle}} : Name of the maintenance event.',
'{{announcementDescription}} : Description of the scheduled event.',
'{{projectName}} : Name of the project on which the event is created.',
],
smsType: 'Subscriber Announcement Notification Created',
body:
'An announcement has been made on {{projectName}}. Title: {{announcementTitle}}, Content: "{{announcementDescription}}."',
},
];

View File

@@ -9,7 +9,7 @@ module.exports = {
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
'Subscriber Incident Acknowldeged': [
'Subscriber Incident Acknowledged': [
'{{incidentTime}} : Time at which this incident occured.',
'{{monitorName}} : Name of the monitor on which incident has occured.',
'{{projectName}} : Name of the project on which the incident has occured.',

View File

@@ -1,6 +1,6 @@
const INCIDENT_MAIL_TEMPLATE_TYPES = {
CREATED_MAIL_TEMPLATE: 'Subscriber Incident Created',
ACKNOWLEDGED_MAIL_TEMPLATE: 'Subscriber Incident Acknowldeged',
ACKNOWLEDGED_MAIL_TEMPLATE: 'Subscriber Incident Acknowledged',
RESOLVED_MAIL_TEMPLATE: 'Subscriber Incident Resolved',
};

View File

@@ -0,0 +1,132 @@
/**
*
* Copyright HackerBay, Inc.
*
*/
const ApplicationScannerService = require('../services/applicationScannerService');
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const ErrorService = require('../services/errorService');
const CLUSTER_KEY = process.env.CLUSTER_KEY;
module.exports = {
isAuthorizedApplicationScanner: async function (req, res, next) {
try {
let applicationScannerKey, applicationScannerName, clusterKey, applicationScannerVersion;
if (req.params.applicationscannerkey) {
applicationScannerKey = req.params.applicationscannerkey;
} else if (req.query.applicationScannerKey) {
applicationScannerKey = req.query.applicationscannerkey;
} else if (req.headers['applicationscannerkey']) {
applicationScannerKey = req.headers['applicationscannerkey'];
} else if (req.headers['applicationscannerkey']) {
applicationScannerKey = req.headers['applicationscannerkey'];
} else if (req.body.applicationScannerKey) {
applicationScannerKey = req.body.applicationScannerKey;
} else {
return sendErrorResponse(req, res, {
code: 400,
message: 'applicationScanner Key not found.',
});
}
if (req.params.applicationscannername) {
applicationScannerName = req.params.applicationscannername;
} else if (req.query.applicationscannername) {
applicationScannerName = req.query.applicationscannername;
} else if (req.headers['applicationscannername']) {
applicationScannerName = req.headers['applicationscannername'];
} else if (req.headers['applicationscannername']) {
applicationScannerName = req.headers['applicationscannername'];
} else if (req.body.applicationscannerName) {
applicationScannerName = req.body.applicationscannername;
} else {
return sendErrorResponse(req, res, {
code: 400,
message: 'applicationScanner Name not found.',
});
}
if (req.params.clusterKey) {
clusterKey = req.params.clusterkey;
} else if (req.query.clusterKey) {
clusterKey = req.query.clusterkey;
} else if (req.headers['clusterKey']) {
clusterKey = req.headers['clusterKey'];
} else if (req.headers['clusterkey']) {
clusterKey = req.headers['clusterkey'];
} else if (req.body.clusterKey) {
clusterKey = req.body.clusterKey;
}
if (req.params.applicationscannerversion) {
applicationScannerVersion = req.params.applicationscannerversion;
} else if (req.query.applicationscannerversion) {
applicationScannerVersion = req.query.applicationscannerversion;
} else if (req.headers['applicationscannerversion']) {
applicationScannerVersion = req.headers['applicationscannerversion'];
} else if (req.body.applicationscannerversion) {
applicationScannerVersion = req.body.applicationscannerversion;
}
let applicationScanner = null;
if (clusterKey && clusterKey === CLUSTER_KEY) {
// if cluster key matches then just query by applicationScanner name,
// because if the applicationScanner key does not match, we can update applicationScanner key later
// without updating mongodb database manually.
applicationScanner = await ApplicationScannerService.findOneBy({ applicationScannerName });
} else {
applicationScanner = await ApplicationScannerService.findOneBy({ applicationScannerKey, applicationScannerName });
}
if (!applicationScanner && (!clusterKey || clusterKey !== CLUSTER_KEY)) {
return sendErrorResponse(req, res, {
code: 400,
message: 'applicationScanner key and applicationScanner name do not match.',
});
}
if (!applicationScanner) {
//create a new applicationScanner.
applicationScanner = await ApplicationScannerService.create({
applicationScannerKey,
applicationScannerName,
applicationScannerVersion,
});
}
if (applicationScanner.applicationScannerKey !== applicationScannerKey) {
//update applicationScanner key becasue it does not match.
await ApplicationScannerService.updateOneBy(
{
applicationScannerName,
},
{ applicationScannerKey }
);
}
req.applicationScanner = {};
req.applicationScanner.id = applicationScanner._id;
await ApplicationScannerService.updateApplicationScannerStatus(applicationScanner._id);
//Update applicationScanner version
const applicationScannerValue = await ApplicationScannerService.findOneBy({
applicationScannerKey,
applicationScannerName,
});
if (!applicationScannerValue.version || applicationScannerValue.version !== applicationScannerVersion) {
await ApplicationScannerService.updateOneBy(
{
applicationScannerName,
},
{ version: applicationScannerVersion }
);
}
next();
} catch (error) {
ErrorService.log('applicationScannerAuthorization.isAuthorizedApplicationScanner', error);
throw error;
}
},
};

View File

@@ -4,7 +4,7 @@
*
*/
const GridFsStorage = require('multer-gridfs-storage');
const { GridFsStorage } = require('multer-gridfs-storage');
const crypto = require('crypto');
const mongoUri = process.env['MONGO_URL'];

View File

@@ -0,0 +1,21 @@
/**
*
* Copyright HackerBay, Inc.
*
*/
const mongoose = require('../config/db');
const Schema = mongoose.Schema;
const applicationScannerSchema = new Schema({
createdAt: { type: Date, default: Date.now },
applicationScannerKey: { type: String },
applicationScannerName: { type: String },
version: { type: String },
lastAlive: { type: Date, default: Date.now },
deleted: { type: Boolean, default: false },
deletedAt: { type: Date },
applicationScannerImage: { type: String },
});
module.exports = mongoose.model('applicationScanner', applicationScannerSchema);

View File

@@ -0,0 +1,58 @@
const mongoose = require('../config/db');
const Schema = mongoose.Schema;
const automatedScriptSchema = new Schema(
{
name: String,
script: String,
scriptType: String,
slug: String,
projectId: {
type: Schema.Types.ObjectId,
ref: 'Project',
index: true,
},
deleted: {
type: Boolean,
default: false,
},
deletedAt: {
type: Date,
},
deletedById: {
type: Schema.Types.ObjectId,
ref: 'User',
index: true,
},
successEvent: [
{
automatedScript: {
type: Schema.Types.ObjectId,
ref: 'AutomationSript',
index: true,
},
callSchedule: {
type: Schema.Types.ObjectId,
ref: 'Schedule',
index: true,
},
},
],
failureEvent: [
{
automatedScript: {
type: Schema.Types.ObjectId,
ref: 'AutomationSript',
index: true,
},
callSchedule: {
type: Schema.Types.ObjectId,
ref: 'Schedule',
index: true,
},
},
],
},
{ timestamps: true }
);
module.exports = mongoose.model('AutomationSript', automatedScriptSchema);

View File

@@ -0,0 +1,46 @@
const mongoose = require('../config/db');
const Schema = mongoose.Schema;
const automationSriptLogSchema = new Schema(
{
automationScriptId: {
type: Schema.Types.ObjectId,
ref: 'AutomationSript',
index: true,
},
triggerByUser: {
type: Schema.Types.ObjectId,
ref: 'User',
},
triggerByScript: {
type: Schema.Types.ObjectId,
ref: 'AutomationSript',
},
triggerByIncident: {
type: Schema.Types.ObjectId,
ref: 'Incident',
},
status: {
type: String,
enum: ['success', 'running', 'failed'],
default: 'running',
},
deleted: {
type: Boolean,
default: false,
},
deletedAt: {
type: Date,
},
deletedById: {
type: Schema.Types.ObjectId,
ref: 'User',
index: true,
},
executionTime: Number,
consoleLogs: [String],
error: String,
},
{ timestamps: true }
);
module.exports = mongoose.model('AutomationSriptLog', automationSriptLogSchema);

View File

@@ -0,0 +1,37 @@
/**
*
* Copyright HackerBay, Inc.
*
*/
const mongoose = require('../config/db');
const Schema = mongoose.Schema;
const defaultManagerSchema = new Schema(
{
store: {
type: Object,
default: {
module: 'fyipe-le-store',
},
},
challenges: {
'http-01': {
type: Object,
default: {
module: 'fyipe-acme-http-01',
},
},
},
renewOffset: { type: String, default: '-45d' },
renewStagger: { type: String, default: '3d' },
accountKeyType: String,
serverKeyType: String,
subscriberEmail: String,
agreeToTerms: { type: Boolean, default: true },
deleted: { type: Boolean, default: false },
deletedAt: Date,
},
{ timestamps: true }
);
module.exports = mongoose.model('DefaultManager', defaultManagerSchema);

View File

@@ -15,13 +15,14 @@ const emailTemplateSchema = new Schema({
type: String,
enum: [
'Subscriber Incident Created',
'Subscriber Incident Acknowldeged',
'Subscriber Incident Acknowledged',
'Subscriber Incident Resolved',
'Investigation note is created',
'Subscriber Scheduled Maintenance Created',
'Subscriber Scheduled Maintenance Note',
'Subscriber Scheduled Maintenance Resolved',
'Subscriber Scheduled Maintenance Cancelled',
'Subscriber Announcement Notification Created',
],
required: true,
},

View File

@@ -22,8 +22,24 @@ const monitorSchema = new Schema({
type: Schema.Types.String,
},
response: Object,
monitorId: { type: String, ref: 'Monitor', index: true }, // which monitor does this incident belongs to.
notificationId: { type: String, ref: 'Notification', index: true },
monitors: [
{
monitorId: {
type: Schema.Types.ObjectId,
ref: 'Monitor',
index: true,
},
},
],
notifications: [
{
notificationId: {
type: String,
ref: 'Notification',
index: true,
},
},
],
incidentPriority: {
type: String,
ref: 'IncidentPriority',
@@ -96,6 +112,7 @@ const monitorSchema = new Schema({
},
deletedById: { type: String, ref: 'User', index: true },
// has this incident breached communication sla
breachedCommunicationSla: { type: Boolean, default: false },
customFields: [
{

Some files were not shown because too many files have changed in this diff Show More