Merge branch 'master' into feature-update-sso

This commit is contained in:
Mohamed ABDELLANI
2021-01-25 06:01:44 +01:00
312 changed files with 70283 additions and 49212 deletions

View File

@@ -25,4 +25,6 @@ api-docs/public/assets/*
server-monitor/out/scripts/prettify/*
js-sdk/dist/logger.js
js-sdk/dist/logger.min.js
js-sdk/dist/fyipe.js
js-sdk/dist/fyipe.min.js
_test/*

View File

@@ -3366,11 +3366,6 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
},
"@types/mime-types": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz",
"integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM="
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -3480,6 +3475,15 @@
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz",
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
},
"@types/yauzl": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
"integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
"optional": true,
"requires": {
"@types/node": "*"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "4.11.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.11.1.tgz",
@@ -4907,6 +4911,42 @@
"file-uri-to-path": "1.0.0"
}
},
"bl": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
"integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
},
"dependencies": {
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -6888,6 +6928,11 @@
}
}
},
"devtools-protocol": {
"version": "0.0.818844",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz",
"integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg=="
},
"diff-sequences": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
@@ -8310,31 +8355,36 @@
}
},
"extract-zip": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
"integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"requires": {
"concat-stream": "^1.6.2",
"debug": "^2.6.9",
"mkdirp": "^0.5.4",
"@types/yauzl": "^2.9.1",
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.0.0"
"ms": "2.1.2"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"requires": {
"minimist": "^1.2.5"
"pump": "^3.0.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
@@ -8753,6 +8803,11 @@
"readable-stream": "^2.0.0"
}
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"fs-extra": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
@@ -9485,11 +9540,11 @@
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"ms": {
@@ -12748,6 +12803,11 @@
"minimist": "^1.2.5"
}
},
"mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -12864,6 +12924,11 @@
}
}
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
@@ -14908,64 +14973,61 @@
}
},
"puppeteer": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-2.1.1.tgz",
"integrity": "sha512-LWzaDVQkk1EPiuYeTOj+CZRIjda4k2s5w4MK4xoH2+kgWV/SDlkYHmxatDdtYrciHUKSXTsGgPgPP8ILVdBsxg==",
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-5.5.0.tgz",
"integrity": "sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg==",
"requires": {
"@types/mime-types": "^2.1.0",
"debug": "^4.1.0",
"extract-zip": "^1.6.6",
"devtools-protocol": "0.0.818844",
"extract-zip": "^2.0.0",
"https-proxy-agent": "^4.0.0",
"mime": "^2.0.3",
"mime-types": "^2.1.25",
"node-fetch": "^2.6.1",
"pkg-dir": "^4.2.0",
"progress": "^2.0.1",
"proxy-from-env": "^1.0.0",
"rimraf": "^2.6.1",
"ws": "^6.1.0"
"rimraf": "^3.0.2",
"tar-fs": "^2.0.0",
"unbzip2-stream": "^1.3.3",
"ws": "^7.2.3"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"mime": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"ws": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"requires": {
"async-limiter": "~1.0.0"
"glob": "^7.1.3"
}
}
}
},
"puppeteer-cluster": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/puppeteer-cluster/-/puppeteer-cluster-0.21.0.tgz",
"integrity": "sha512-/x5mei0vXxFPpJ7iUS+xJ3rOcxxYUa2YeEyuWI9m0M5e8ammPiCXjvOsTcni+4ZAop3L2gpZFkxafPvXWOoRfg==",
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/puppeteer-cluster/-/puppeteer-cluster-0.22.0.tgz",
"integrity": "sha512-hmydtMwfVM+idFIDzS8OXetnujHGre7RY3BGL+3njy9+r8Dcu3VALkZHfuBEPf6byKssTCgzxU1BvLczifXd5w==",
"requires": {
"debug": "^4.1.1"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"ms": {
@@ -17881,6 +17943,48 @@
}
}
},
"tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
},
"dependencies": {
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
}
}
},
"tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"requires": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"temp-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",
@@ -18071,8 +18175,7 @@
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"through2": {
"version": "2.0.5",
@@ -18329,6 +18432,26 @@
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
"dev": true
},
"unbzip2-stream": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
"integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
"requires": {
"buffer": "^5.2.1",
"through": "^2.3.8"
},
"dependencies": {
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
}
}
},
"underscore": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",

View File

@@ -19,8 +19,8 @@
"js-uuid": "0.0.6",
"loadable-components": "^2.2.3",
"prop-types": "^15.6.1",
"puppeteer": "^2.1.1",
"puppeteer-cluster": "^0.21.0",
"puppeteer": "^5.5.0",
"puppeteer-cluster": "^0.22.0",
"query-string": "^5.1.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",

File diff suppressed because it is too large Load Diff

View File

@@ -20,14 +20,14 @@
"mixpanel-browser": "^2.22.3",
"moment": "^2.22.2",
"prop-types": "^15.6.1",
"puppeteer": "^2.1.1",
"puppeteer-cluster": "^0.19.0",
"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",
"react-frontload": "^1.0.3",
"react-ga": "^2.5.3",
"react-json-view": "^1.19.1",
"react-json-view": "^1.20.2",
"react-mixpanel": "0.0.11",
"react-redux": "^5.0.7",
"react-router-dom": "^4.2.2",
@@ -90,8 +90,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"resolutions": {
"node-fetch": "2.6.1"
}
}

View File

@@ -139,14 +139,14 @@
text-align: center;
color: #565656;
display: inline-block;
margin-left: 10px;
margin-left: 10px !important;
}
.cancel-btn__keycode {
width: 30px;
height: 20px;
line-height: 15px;
padding: 2px;
padding: 2px !important;
text-transform: lowercase;
}

View File

@@ -3578,12 +3578,23 @@ figure.bs-Number > figcaption {
border-radius: 4px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.07), 0 2px 15px rgba(84, 96, 103, 0.25);
}
.ds-Modal{
position: relative;
z-index: 200;
margin: 0 auto 20px;
max-width: 670px;
background: #f7f7f7;
border-radius: 4px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.07), 0 2px 15px rgba(84, 96, 103, 0.25);
}
@media only screen and (max-width: 470px) {
.bs-Modal {
margin: 0 10px 20px;
width: auto;
}
}
.bs-Modal:focus {
outline: none;
}

View File

@@ -9099,6 +9099,14 @@ html.db-NewChrome .bs-ContentSection .bs-Tail--short {
.db-SideNav-icon--businessSettings {
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDI5MS45NTcgMjkxLjk1NyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjkxLjk1NyAyOTEuOTU3OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4Ij4KPHBhdGggZD0iTTI4My4wNywxNjguODc1bC0xNy4xMDYtOS44NzZjMC40NjEtNC4yNzksMC43MDQtOC42MjIsMC43MDQtMTMuMDJzLTAuMjQzLTguNzQyLTAuNzA0LTEzLjAyMWwxNy4xMDYtOS44NzYgIGMzLjY1NS0yLjExLDYuMjctNS41MTksNy4zNjMtOS41OThjMS4wOTItNC4wNzgsMC41MzEtOC4zMzgtMS41OC0xMS45OTRsLTMyLjkwOC01Ny4wMDFjLTIuODE2LTQuODc4LTguMDY3LTcuOTA3LTEzLjcwNS03LjkwNyAgYy0yLjc1OSwwLTUuNDg1LDAuNzM0LTcuODg3LDIuMTJsLTE3LjE1NSw5LjkwNWMtNi45NzMtNS4xMTQtMTQuNTEtOS40OTctMjIuNTAzLTEzLjAzN1YxNS44MDdDMTk0LjY5NSw3LjA5MSwxODcuNjA0LDAsMTc4Ljg4OSwwICBoLTY1LjgyYy04LjcxNiwwLTE1LjgwNyw3LjA5MS0xNS44MDcsMTUuODA3VjM1LjU3Yy03Ljk5MywzLjU0LTE1LjUzMSw3LjkyNC0yMi41MDMsMTMuMDM4bC0xNy4xNTUtOS45MDQgIGMtMi40MDEtMS4zODctNS4xMjgtMi4xMjEtNy44ODctMi4xMjFjLTUuNjM4LDAtMTAuODg5LDMuMDI5LTEzLjcwNSw3LjkwN0wzLjEwMywxMDEuNDljLTIuMTExLDMuNjU1LTIuNjcyLDcuOTE2LTEuNTgsMTEuOTk0ICBjMS4wOTQsNC4wNzksMy43MDgsNy40ODcsNy4zNjMsOS41OThsMTcuMTA2LDkuODc2Yy0wLjQ2MSw0LjI3OS0wLjcwNCw4LjYyMi0wLjcwNCwxMy4wMjFzMC4yNDMsOC43NDIsMC43MDQsMTMuMDJsLTE3LjEwNiw5Ljg3NiAgYy0zLjY1NSwyLjExLTYuMjY5LDUuNTE4LTcuMzYzLDkuNTk4Yy0xLjA5Miw0LjA3OC0wLjUzMSw4LjMzOSwxLjU4LDExLjk5NGwzMi45MDgsNTcuMDAxYzIuODE2LDQuODc4LDguMDY3LDcuOTA3LDEzLjcwNSw3LjkwNyAgYzIuNzU5LDAsNS40ODUtMC43MzMsNy44ODctMi4xMmwxNy4xNTUtOS45MDVjNi45NzMsNS4xMTQsMTQuNTEsOS40OTcsMjIuNTAzLDEzLjAzN3YxOS43NjRjMCw0LjIyMiwxLjY0NCw4LjE5LDQuNjMxLDExLjE3NiAgYzIuOTg1LDIuOTg1LDYuOTU1LDQuNjMsMTEuMTc2LDQuNjNoNjUuODJjOC43MTUsMCwxNS44MDctNy4wOSwxNS44MDctMTUuODA2di0xOS43NjRjNy45OTItMy41NDEsMTUuNTMtNy45MjMsMjIuNTAyLTEzLjAzNyAgbDE3LjE1Niw5LjkwNGMyLjQwMSwxLjM4Nyw1LjEyOCwyLjEyLDcuODg3LDIuMTJjNS42MzgsMCwxMC44ODktMy4wMjksMTMuNzA1LTcuOTA3bDMyLjkwOC01Ny4wMDEgIGMyLjExMS0zLjY1NSwyLjY3Mi03LjkxNiwxLjU4LTExLjk5NEMyODkuMzQsMTc0LjM5MywyODYuNzI2LDE3MC45ODUsMjgzLjA3LDE2OC44NzV6IE0xNDUuOTc5LDIwMS42NjggIGMtMzAuNzU2LDAtNTUuNjg5LTI0LjkzNC01NS42ODktNTUuNjg5czI0LjkzNC01NS42ODksNTUuNjg5LTU1LjY4OXM1NS42ODksMjQuOTM0LDU1LjY4OSw1NS42ODlTMTc2LjczNCwyMDEuNjY4LDE0NS45NzksMjAxLjY2OHogICIgZmlsbD0iIzkwOWJhZiIvPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K');
}
.db-SideNav-icon--LogsIcon{
filter: invert(66%) sepia(18%) saturate(288%) hue-rotate(175deg) brightness(93%) contrast(87%);
background-image: url('../icons/app-log.svg');
}
.db-SideNav-icon--LogsIcon.db-SideNav-icon--selected{
filter: invert(0%) sepia(100%) saturate(7459%) hue-rotate(48deg) brightness(101%) contrast(110%);
background-image: url('../icons/app-log.svg');
}
.db-SideNav-icon--connect {
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNDg4LjE1MiA0ODguMTUyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0ODguMTUyIDQ4OC4xNTI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMTc3Ljg1NCwyNjkuMzExYzAtNi4xMTUtNC45Ni0xMS4wNjktMTEuMDgtMTEuMDY5aC0zOC42NjVjLTYuMTEzLDAtMTEuMDc0LDQuOTU0LTExLjA3NCwxMS4wNjl2MzguNjYgICAgYzAsNi4xMjMsNC45NjEsMTEuMDc5LDExLjA3NCwxMS4wNzloMzguNjY1YzYuMTIsMCwxMS4wOC00Ljk1NiwxMS4wOC0xMS4wNzlWMjY5LjMxMUwxNzcuODU0LDI2OS4zMTF6IiBmaWxsPSIjOTA5YmFmIi8+CgkJPHBhdGggZD0iTTI3NC40ODMsMjY5LjMxMWMwLTYuMTE1LTQuOTYxLTExLjA2OS0xMS4wNjktMTEuMDY5aC0zOC42N2MtNi4xMTMsMC0xMS4wNzQsNC45NTQtMTEuMDc0LDExLjA2OXYzOC42NiAgICBjMCw2LjEyMyw0Ljk2MSwxMS4wNzksMTEuMDc0LDExLjA3OWgzOC42N2M2LjEwOCwwLDExLjA2OS00Ljk1NiwxMS4wNjktMTEuMDc5VjI2OS4zMTF6IiBmaWxsPSIjOTA5YmFmIi8+CgkJPHBhdGggZD0iTTM3MS4xMTcsMjY5LjMxMWMwLTYuMTE1LTQuOTYxLTExLjA2OS0xMS4wNzQtMTEuMDY5aC0zOC42NjVjLTYuMTIsMC0xMS4wOCw0Ljk1NC0xMS4wOCwxMS4wNjl2MzguNjYgICAgYzAsNi4xMjMsNC45NiwxMS4wNzksMTEuMDgsMTEuMDc5aDM4LjY2NWM2LjExMywwLDExLjA3NC00Ljk1NiwxMS4wNzQtMTEuMDc5VjI2OS4zMTF6IiBmaWxsPSIjOTA5YmFmIi8+CgkJPHBhdGggZD0iTTE3Ny44NTQsMzY1Ljk1YzAtNi4xMjUtNC45Ni0xMS4wNzUtMTEuMDgtMTEuMDc1aC0zOC42NjVjLTYuMTEzLDAtMTEuMDc0LDQuOTUtMTEuMDc0LDExLjA3NXYzOC42NTMgICAgYzAsNi4xMTksNC45NjEsMTEuMDc0LDExLjA3NCwxMS4wNzRoMzguNjY1YzYuMTIsMCwxMS4wOC00Ljk1NiwxMS4wOC0xMS4wNzRWMzY1Ljk1TDE3Ny44NTQsMzY1Ljk1eiIgZmlsbD0iIzkwOWJhZiIvPgoJCTxwYXRoIGQ9Ik0yNzQuNDgzLDM2NS45NWMwLTYuMTI1LTQuOTYxLTExLjA3NS0xMS4wNjktMTEuMDc1aC0zOC42N2MtNi4xMTMsMC0xMS4wNzQsNC45NS0xMS4wNzQsMTEuMDc1djM4LjY1MyAgICBjMCw2LjExOSw0Ljk2MSwxMS4wNzQsMTEuMDc0LDExLjA3NGgzOC42N2M2LjEwOCwwLDExLjA2OS00Ljk1NiwxMS4wNjktMTEuMDc0VjM2NS45NXoiIGZpbGw9IiM5MDliYWYiLz4KCQk8cGF0aCBkPSJNMzcxLjExNywzNjUuOTVjMC02LjEyNS00Ljk2MS0xMS4wNzUtMTEuMDY5LTExLjA3NWgtMzguNjdjLTYuMTIsMC0xMS4wOCw0Ljk1LTExLjA4LDExLjA3NXYzOC42NTMgICAgYzAsNi4xMTksNC45NiwxMS4wNzQsMTEuMDgsMTEuMDc0aDM4LjY3YzYuMTA4LDAsMTEuMDY5LTQuOTU2LDExLjA2OS0xMS4wNzRWMzY1Ljk1TDM3MS4xMTcsMzY1Ljk1eiIgZmlsbD0iIzkwOWJhZiIvPgoJCTxwYXRoIGQ9Ik00NDAuMjU0LDU0LjM1NHY1OS4wNWMwLDI2LjY5LTIxLjY1Miw0OC4xOTgtNDguMzM4LDQ4LjE5OGgtMzAuNDkzYy0yNi42ODgsMC00OC42MjctMjEuNTA4LTQ4LjYyNy00OC4xOThWNTQuMTQyICAgIGgtMTM3LjQ0djU5LjI2MmMwLDI2LjY5LTIxLjkzOCw0OC4xOTgtNDguNjIyLDQ4LjE5OEg5Ni4yMzVjLTI2LjY4NSwwLTQ4LjMzNi0yMS41MDgtNDguMzM2LTQ4LjE5OHYtNTkuMDUgICAgQzI0LjU3Niw1NS4wNTcsNS40MTEsNzQuMzU2LDUuNDExLDk4LjA3N3YzNDYuMDYxYzAsMjQuMTY3LDE5LjU4OCw0NC4wMTUsNDMuNzU1LDQ0LjAxNWgzODkuODIgICAgYzI0LjEzMSwwLDQzLjc1NS0xOS44ODksNDMuNzU1LTQ0LjAxNVY5OC4wNzdDNDgyLjc0MSw3NC4zNTYsNDYzLjU3Nyw1NS4wNTcsNDQwLjI1NCw1NC4zNTR6IE00MjYuMDkxLDQyMi41ODggICAgYzAsMTAuNDQ0LTguNDY4LDE4LjkxNy0xOC45MTYsMTguOTE3SDgwLjE0NGMtMTAuNDQ4LDAtMTguOTE2LTguNDczLTE4LjkxNi0xOC45MTdWMjQzLjgzNWMwLTEwLjQ0OCw4LjQ2Ny0xOC45MjEsMTguOTE2LTE4LjkyMSAgICBoMzI3LjAzYzEwLjQ0OCwwLDE4LjkxNiw4LjQ3MywxOC45MTYsMTguOTIxTDQyNi4wOTEsNDIyLjU4OEw0MjYuMDkxLDQyMi41ODh6IiBmaWxsPSIjOTA5YmFmIi8+CgkJPHBhdGggZD0iTTk2LjEyOCwxMjkuOTQ1aDMwLjE2MmM5LjE1NSwwLDE2LjU3OC03LjQxMiwxNi41NzgtMTYuNTY3VjE2LjU3M0MxNDIuODY4LDcuNDE3LDEzNS40NDUsMCwxMjYuMjksMEg5Ni4xMjggICAgQzg2Ljk3MiwwLDc5LjU1LDcuNDE3LDc5LjU1LDE2LjU3M3Y5Ni44MDVDNzkuNTUsMTIyLjUzMyw4Ni45NzIsMTI5Ljk0NSw5Ni4xMjgsMTI5Ljk0NXoiIGZpbGw9IiM5MDliYWYiLz4KCQk8cGF0aCBkPSJNMzYxLjAzNSwxMjkuOTQ1aDMwLjE2MmM5LjE0OSwwLDE2LjU3Mi03LjQxMiwxNi41NzItMTYuNTY3VjE2LjU3M0M0MDcuNzcsNy40MTcsNDAwLjM0NywwLDM5MS4xOTcsMGgtMzAuMTYyICAgIGMtOS4xNTQsMC0xNi41NzcsNy40MTctMTYuNTc3LDE2LjU3M3Y5Ni44MDVDMzQ0LjQ1OCwxMjIuNTMzLDM1MS44ODEsMTI5Ljk0NSwzNjEuMDM1LDEyOS45NDV6IiBmaWxsPSIjOTA5YmFmIi8+Cgk8L2c+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==')
}
@@ -9157,6 +9165,9 @@ html.db-NewChrome .bs-ContentSection .bs-Tail--short {
.db-SideNav-icon--businessSettings.db-SideNav-icon--highlighted {
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDI5MS45NTcgMjkxLjk1NyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjkxLjk1NyAyOTEuOTU3OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4Ij4KPHBhdGggZD0iTTI4My4wNywxNjguODc1bC0xNy4xMDYtOS44NzZjMC40NjEtNC4yNzksMC43MDQtOC42MjIsMC43MDQtMTMuMDJzLTAuMjQzLTguNzQyLTAuNzA0LTEzLjAyMWwxNy4xMDYtOS44NzYgIGMzLjY1NS0yLjExLDYuMjctNS41MTksNy4zNjMtOS41OThjMS4wOTItNC4wNzgsMC41MzEtOC4zMzgtMS41OC0xMS45OTRsLTMyLjkwOC01Ny4wMDFjLTIuODE2LTQuODc4LTguMDY3LTcuOTA3LTEzLjcwNS03LjkwNyAgYy0yLjc1OSwwLTUuNDg1LDAuNzM0LTcuODg3LDIuMTJsLTE3LjE1NSw5LjkwNWMtNi45NzMtNS4xMTQtMTQuNTEtOS40OTctMjIuNTAzLTEzLjAzN1YxNS44MDdDMTk0LjY5NSw3LjA5MSwxODcuNjA0LDAsMTc4Ljg4OSwwICBoLTY1LjgyYy04LjcxNiwwLTE1LjgwNyw3LjA5MS0xNS44MDcsMTUuODA3VjM1LjU3Yy03Ljk5MywzLjU0LTE1LjUzMSw3LjkyNC0yMi41MDMsMTMuMDM4bC0xNy4xNTUtOS45MDQgIGMtMi40MDEtMS4zODctNS4xMjgtMi4xMjEtNy44ODctMi4xMjFjLTUuNjM4LDAtMTAuODg5LDMuMDI5LTEzLjcwNSw3LjkwN0wzLjEwMywxMDEuNDljLTIuMTExLDMuNjU1LTIuNjcyLDcuOTE2LTEuNTgsMTEuOTk0ICBjMS4wOTQsNC4wNzksMy43MDgsNy40ODcsNy4zNjMsOS41OThsMTcuMTA2LDkuODc2Yy0wLjQ2MSw0LjI3OS0wLjcwNCw4LjYyMi0wLjcwNCwxMy4wMjFzMC4yNDMsOC43NDIsMC43MDQsMTMuMDJsLTE3LjEwNiw5Ljg3NiAgYy0zLjY1NSwyLjExLTYuMjY5LDUuNTE4LTcuMzYzLDkuNTk4Yy0xLjA5Miw0LjA3OC0wLjUzMSw4LjMzOSwxLjU4LDExLjk5NGwzMi45MDgsNTcuMDAxYzIuODE2LDQuODc4LDguMDY3LDcuOTA3LDEzLjcwNSw3LjkwNyAgYzIuNzU5LDAsNS40ODUtMC43MzMsNy44ODctMi4xMmwxNy4xNTUtOS45MDVjNi45NzMsNS4xMTQsMTQuNTEsOS40OTcsMjIuNTAzLDEzLjAzN3YxOS43NjRjMCw0LjIyMiwxLjY0NCw4LjE5LDQuNjMxLDExLjE3NiAgYzIuOTg1LDIuOTg1LDYuOTU1LDQuNjMsMTEuMTc2LDQuNjNoNjUuODJjOC43MTUsMCwxNS44MDctNy4wOSwxNS44MDctMTUuODA2di0xOS43NjRjNy45OTItMy41NDEsMTUuNTMtNy45MjMsMjIuNTAyLTEzLjAzNyAgbDE3LjE1Niw5LjkwNGMyLjQwMSwxLjM4Nyw1LjEyOCwyLjEyLDcuODg3LDIuMTJjNS42MzgsMCwxMC44ODktMy4wMjksMTMuNzA1LTcuOTA3bDMyLjkwOC01Ny4wMDEgIGMyLjExMS0zLjY1NSwyLjY3Mi03LjkxNiwxLjU4LTExLjk5NEMyODkuMzQsMTc0LjM5MywyODYuNzI2LDE3MC45ODUsMjgzLjA3LDE2OC44NzV6IE0xNDUuOTc5LDIwMS42NjggIGMtMzAuNzU2LDAtNTUuNjg5LTI0LjkzNC01NS42ODktNTUuNjg5czI0LjkzNC01NS42ODksNTUuNjg5LTU1LjY4OXM1NS42ODksMjQuOTM0LDU1LjY4OSw1NS42ODlTMTc2LjczNCwyMDEuNjY4LDE0NS45NzksMjAxLjY2OHogICIgZmlsbD0iIzY3NzJlNSIvPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K');
}
.db-SideNav-icon--LogsIcon.db-SideNav-icon--highlighted {
background-image: url('../icons/app-log.svg');
}
.db-SideNav-icon--connect.db-SideNav-icon--highlighted {
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4IiB2aWV3Qm94PSIwIDAgNDg4LjE1MiA0ODguMTUyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0ODguMTUyIDQ4OC4xNTI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMTc3Ljg1NCwyNjkuMzExYzAtNi4xMTUtNC45Ni0xMS4wNjktMTEuMDgtMTEuMDY5aC0zOC42NjVjLTYuMTEzLDAtMTEuMDc0LDQuOTU0LTExLjA3NCwxMS4wNjl2MzguNjYgICAgYzAsNi4xMjMsNC45NjEsMTEuMDc5LDExLjA3NCwxMS4wNzloMzguNjY1YzYuMTIsMCwxMS4wOC00Ljk1NiwxMS4wOC0xMS4wNzlWMjY5LjMxMUwxNzcuODU0LDI2OS4zMTF6IiBmaWxsPSIjNjc3MmU1Ii8+CgkJPHBhdGggZD0iTTI3NC40ODMsMjY5LjMxMWMwLTYuMTE1LTQuOTYxLTExLjA2OS0xMS4wNjktMTEuMDY5aC0zOC42N2MtNi4xMTMsMC0xMS4wNzQsNC45NTQtMTEuMDc0LDExLjA2OXYzOC42NiAgICBjMCw2LjEyMyw0Ljk2MSwxMS4wNzksMTEuMDc0LDExLjA3OWgzOC42N2M2LjEwOCwwLDExLjA2OS00Ljk1NiwxMS4wNjktMTEuMDc5VjI2OS4zMTF6IiBmaWxsPSIjNjc3MmU1Ii8+CgkJPHBhdGggZD0iTTM3MS4xMTcsMjY5LjMxMWMwLTYuMTE1LTQuOTYxLTExLjA2OS0xMS4wNzQtMTEuMDY5aC0zOC42NjVjLTYuMTIsMC0xMS4wOCw0Ljk1NC0xMS4wOCwxMS4wNjl2MzguNjYgICAgYzAsNi4xMjMsNC45NiwxMS4wNzksMTEuMDgsMTEuMDc5aDM4LjY2NWM2LjExMywwLDExLjA3NC00Ljk1NiwxMS4wNzQtMTEuMDc5VjI2OS4zMTF6IiBmaWxsPSIjNjc3MmU1Ii8+CgkJPHBhdGggZD0iTTE3Ny44NTQsMzY1Ljk1YzAtNi4xMjUtNC45Ni0xMS4wNzUtMTEuMDgtMTEuMDc1aC0zOC42NjVjLTYuMTEzLDAtMTEuMDc0LDQuOTUtMTEuMDc0LDExLjA3NXYzOC42NTMgICAgYzAsNi4xMTksNC45NjEsMTEuMDc0LDExLjA3NCwxMS4wNzRoMzguNjY1YzYuMTIsMCwxMS4wOC00Ljk1NiwxMS4wOC0xMS4wNzRWMzY1Ljk1TDE3Ny44NTQsMzY1Ljk1eiIgZmlsbD0iIzY3NzJlNSIvPgoJCTxwYXRoIGQ9Ik0yNzQuNDgzLDM2NS45NWMwLTYuMTI1LTQuOTYxLTExLjA3NS0xMS4wNjktMTEuMDc1aC0zOC42N2MtNi4xMTMsMC0xMS4wNzQsNC45NS0xMS4wNzQsMTEuMDc1djM4LjY1MyAgICBjMCw2LjExOSw0Ljk2MSwxMS4wNzQsMTEuMDc0LDExLjA3NGgzOC42N2M2LjEwOCwwLDExLjA2OS00Ljk1NiwxMS4wNjktMTEuMDc0VjM2NS45NXoiIGZpbGw9IiM2NzcyZTUiLz4KCQk8cGF0aCBkPSJNMzcxLjExNywzNjUuOTVjMC02LjEyNS00Ljk2MS0xMS4wNzUtMTEuMDY5LTExLjA3NWgtMzguNjdjLTYuMTIsMC0xMS4wOCw0Ljk1LTExLjA4LDExLjA3NXYzOC42NTMgICAgYzAsNi4xMTksNC45NiwxMS4wNzQsMTEuMDgsMTEuMDc0aDM4LjY3YzYuMTA4LDAsMTEuMDY5LTQuOTU2LDExLjA2OS0xMS4wNzRWMzY1Ljk1TDM3MS4xMTcsMzY1Ljk1eiIgZmlsbD0iIzY3NzJlNSIvPgoJCTxwYXRoIGQ9Ik00NDAuMjU0LDU0LjM1NHY1OS4wNWMwLDI2LjY5LTIxLjY1Miw0OC4xOTgtNDguMzM4LDQ4LjE5OGgtMzAuNDkzYy0yNi42ODgsMC00OC42MjctMjEuNTA4LTQ4LjYyNy00OC4xOThWNTQuMTQyICAgIGgtMTM3LjQ0djU5LjI2MmMwLDI2LjY5LTIxLjkzOCw0OC4xOTgtNDguNjIyLDQ4LjE5OEg5Ni4yMzVjLTI2LjY4NSwwLTQ4LjMzNi0yMS41MDgtNDguMzM2LTQ4LjE5OHYtNTkuMDUgICAgQzI0LjU3Niw1NS4wNTcsNS40MTEsNzQuMzU2LDUuNDExLDk4LjA3N3YzNDYuMDYxYzAsMjQuMTY3LDE5LjU4OCw0NC4wMTUsNDMuNzU1LDQ0LjAxNWgzODkuODIgICAgYzI0LjEzMSwwLDQzLjc1NS0xOS44ODksNDMuNzU1LTQ0LjAxNVY5OC4wNzdDNDgyLjc0MSw3NC4zNTYsNDYzLjU3Nyw1NS4wNTcsNDQwLjI1NCw1NC4zNTR6IE00MjYuMDkxLDQyMi41ODggICAgYzAsMTAuNDQ0LTguNDY4LDE4LjkxNy0xOC45MTYsMTguOTE3SDgwLjE0NGMtMTAuNDQ4LDAtMTguOTE2LTguNDczLTE4LjkxNi0xOC45MTdWMjQzLjgzNWMwLTEwLjQ0OCw4LjQ2Ny0xOC45MjEsMTguOTE2LTE4LjkyMSAgICBoMzI3LjAzYzEwLjQ0OCwwLDE4LjkxNiw4LjQ3MywxOC45MTYsMTguOTIxTDQyNi4wOTEsNDIyLjU4OEw0MjYuMDkxLDQyMi41ODh6IiBmaWxsPSIjNjc3MmU1Ii8+CgkJPHBhdGggZD0iTTk2LjEyOCwxMjkuOTQ1aDMwLjE2MmM5LjE1NSwwLDE2LjU3OC03LjQxMiwxNi41NzgtMTYuNTY3VjE2LjU3M0MxNDIuODY4LDcuNDE3LDEzNS40NDUsMCwxMjYuMjksMEg5Ni4xMjggICAgQzg2Ljk3MiwwLDc5LjU1LDcuNDE3LDc5LjU1LDE2LjU3M3Y5Ni44MDVDNzkuNTUsMTIyLjUzMyw4Ni45NzIsMTI5Ljk0NSw5Ni4xMjgsMTI5Ljk0NXoiIGZpbGw9IiM2NzcyZTUiLz4KCQk8cGF0aCBkPSJNMzYxLjAzNSwxMjkuOTQ1aDMwLjE2MmM5LjE0OSwwLDE2LjU3Mi03LjQxMiwxNi41NzItMTYuNTY3VjE2LjU3M0M0MDcuNzcsNy40MTcsNDAwLjM0NywwLDM5MS4xOTcsMGgtMzAuMTYyICAgIGMtOS4xNTQsMC0xNi41NzcsNy40MTctMTYuNTc3LDE2LjU3M3Y5Ni44MDVDMzQ0LjQ1OCwxMjIuNTMzLDM1MS44ODEsMTI5Ljk0NSwzNjEuMDM1LDEyOS45NDV6IiBmaWxsPSIjNjc3MmU1Ii8+Cgk8L2c+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==')
}
@@ -9206,6 +9217,9 @@ html.db-NewChrome .bs-ContentSection .bs-Tail--short {
.db-SideNav-icon--businessSettings.db-SideNav-icon--selected {
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDg5Ni4wMjUgODk2LjAyNSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgODk2LjAyNSA4OTYuMDI1OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgY2xhc3M9IiI+PGc+PGc+PHBhdGggaWQ9InNldHRpbmdzXzFfIiBkPSJNODYzLjI0LDM4Mi43NzFsLTg4Ljc1OS0xNC44MDdjLTYuNDUxLTI2LjM3NC0xNS44NTctNTEuNTg1LTI4LjEwNy03NS4wOTlsNTYuODIxLTcwLjQ1MiAgIGMxMi4wODUtMTQuODg5LDExLjUzNi0zNi4zMTItMS4yMDUtNTAuNjgybC0zNS4zMDEtMzkuNzI5Yy0xMi43OTYtMTQuMzU1LTM0LjAxNi0xNy4zOTEtNTAuMjAyLTcuMTY1bC03NS45MDYsNDcuNzE2ICAgYy0zMy4zODYtMjMuMzI2LTcxLjIwNC00MC41NTEtMTEyLTUwLjU0NmwtMTQuODUtODkuMjM1Yy0zLjExNi0xOC44OTUtMTkuNDY3LTMyLjc1OS0zOC42NjEtMzIuNzU5aC01My4xOTggICBjLTE5LjE1NSwwLTM1LjU2MSwxMy44NjQtMzguNjA4LDMyLjc1OWwtMTQuOTMxLDg5LjI2M2MtMzMuNzI5LDguMjU4LTY1LjM1MywyMS41ODgtOTQuMjEzLDM5LjE0NGwtNzIuMTg4LTUxLjUxOCAgIGMtMTUuNTU4LTExLjExNS0zNi45MjctOS4zNzctNTAuNTA0LDQuMTcxbC0zNy41ODMsMzcuNjFjLTEzLjU0OCwxMy41NzctMTUuMjg2LDM0Ljk0Ni00LjE0Miw1MC41MDRsNTEuNjM4LDcyLjMyNiAgIGMtMTcuMzkxLDI4LjY0Mi0zMC41ODQsNjAuMDg2LTM4Ljg0MSw5My41MTVsLTg5Ljc0MywxNC45ODVDMTMuODkxLDM4NS44ODgsMCw0MDIuMjQsMCw0MjEuNDM1djUzLjE1NiAgIGMwLDE5LjE5MywxMy44OTEsMzUuNTQ3LDMyLjc1NywzOC42NjNsODkuNzQzLDE0Ljk4NWM2Ljc4MSwyNy41MDgsMTYuNjI1LDUzLjc4NCwyOS43MDksNzguMTQ3TDk1LjY0Nyw2NzYuNDQgICBjLTEyLjA0NCwxNC44NzUtMTEuNTM4LDM2LjMxMiwxLjIwMyw1MC42NjlsMzUuMjc0LDM5LjczYzEyLjc5NywxNC4zODIsMzQuMDI4LDE3LjM2Myw1MC4yMTYsNy4xNjNsNzctNDguMzcgICBjMzIuNTgxLDIyLjI4NSw2OS40NCwzOC42NjQsMTA4Ljk5Myw0OC4zN2wxNC45MzEsODkuMjVjMy4wNDgsMTguODk2LDE5LjQ1MywzMi43NiwzOC42MDgsMzIuNzZoNTMuMTk4ICAgYzE5LjE5NCwwLDM1LjU0NS0xMy44NjMsMzguNjYxLTMyLjc1OWwxNC44NzUtODkuMjVjMzMuMzA4LTguMTQ3LDY0LjUzMS0yMS4yNDUsOTMuMTM0LTM4LjVsNzUuMTk2LDUzLjcwNSAgIGMxNS41MywxMS4xNTUsMzYuOTE1LDkuNDA1LDUwLjQ3OC00LjE4NmwzNy41OTgtMzcuNTk3YzEzLjUzMi0xMy41MzYsMTUuMzY1LTM0Ljg5Myw0LjEyNy01MC40NzlsLTUzLjUzNi03NS4wNTkgICBjMTcuNDQxLTI4LjczOCwzMC43MDQtNjAuMjM4LDM4LjkwOS05My44MTZsODguNzU4LTE0LjgyYzE4LjkyMS0zLjExNiwzMi43NTYtMTkuNDY5LDMyLjc1Ni0zOC42NjN2LTUzLjE1NiAgIEM4OTUuOTk4LDQwMi4yNCw4ODIuMTYzLDM4NS44ODgsODYzLjI0LDM4Mi43NzF6IE00NDkuNDIsNjE2LjAxM2MtOTIuNzY0LDAtMTY4LTc1LjI1LTE2OC0xNjhjMC05Mi43NjQsNzUuMjM2LTE2OCwxNjgtMTY4ICAgYzkyLjc0OCwwLDE2Ny45OTgsNzUuMjM2LDE2Ny45OTgsMTY4QzYxNy40MTgsNTQwLjc2Myw1NDIuMTY4LDYxNi4wMTMsNDQ5LjQyLDYxNi4wMTN6IiBkYXRhLW9yaWdpbmFsPSIjMDAwMDAwIiBjbGFzcz0iYWN0aXZlLXBhdGgiIHN0eWxlPSJmaWxsOiMwMDAwMDAiIGRhdGEtb2xkX2NvbG9yPSIjMDAwMDAwIj48L3BhdGg+PC9nPjwvZz4gPC9zdmc+');
}
.db-SideNav-icon--LogsIcon.db-SideNav-icon--selected {
background-image: url('../icons/app-log.svg');
}
.db-SideNav-icon--connect.db-SideNav-icon--selected {
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDQ4OC4xNTIgNDg4LjE1MiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDg4LjE1MiA0ODguMTUyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgY2xhc3M9IiI+PGc+PGc+PGc+PHBhdGggZD0iTTE3Ny44NTQsMjY5LjMxMWMwLTYuMTE1LTQuOTYtMTEuMDY5LTExLjA4LTExLjA2OWgtMzguNjY1Yy02LjExMywwLTExLjA3NCw0Ljk1NC0xMS4wNzQsMTEuMDY5djM4LjY2ICAgIGMwLDYuMTIzLDQuOTYxLDExLjA3OSwxMS4wNzQsMTEuMDc5aDM4LjY2NWM2LjEyLDAsMTEuMDgtNC45NTYsMTEuMDgtMTEuMDc5VjI2OS4zMTFMMTc3Ljg1NCwyNjkuMzExeiIgZGF0YS1vcmlnaW5hbD0iIzAwMDAwMCIgY2xhc3M9ImFjdGl2ZS1wYXRoIiBzdHlsZT0iZmlsbDojMDA4MGE4IiBkYXRhLW9sZF9jb2xvcj0iIzAwODBhOCI+PC9wYXRoPjxwYXRoIGQ9Ik0yNzQuNDgzLDI2OS4zMTFjMC02LjExNS00Ljk2MS0xMS4wNjktMTEuMDY5LTExLjA2OWgtMzguNjdjLTYuMTEzLDAtMTEuMDc0LDQuOTU0LTExLjA3NCwxMS4wNjl2MzguNjYgICAgYzAsNi4xMjMsNC45NjEsMTEuMDc5LDExLjA3NCwxMS4wNzloMzguNjdjNi4xMDgsMCwxMS4wNjktNC45NTYsMTEuMDY5LTExLjA3OVYyNjkuMzExeiIgZGF0YS1vcmlnaW5hbD0iIzAwMDAwMCIgY2xhc3M9ImFjdGl2ZS1wYXRoIiBzdHlsZT0iZmlsbDojMDA4MGE4IiBkYXRhLW9sZF9jb2xvcj0iIzAwODBhOCI+PC9wYXRoPjxwYXRoIGQ9Ik0zNzEuMTE3LDI2OS4zMTFjMC02LjExNS00Ljk2MS0xMS4wNjktMTEuMDc0LTExLjA2OWgtMzguNjY1Yy02LjEyLDAtMTEuMDgsNC45NTQtMTEuMDgsMTEuMDY5djM4LjY2ICAgIGMwLDYuMTIzLDQuOTYsMTEuMDc5LDExLjA4LDExLjA3OWgzOC42NjVjNi4xMTMsMCwxMS4wNzQtNC45NTYsMTEuMDc0LTExLjA3OVYyNjkuMzExeiIgZGF0YS1vcmlnaW5hbD0iIzAwMDAwMCIgY2xhc3M9ImFjdGl2ZS1wYXRoIiBzdHlsZT0iZmlsbDojMDA4MGE4IiBkYXRhLW9sZF9jb2xvcj0iIzAwODBhOCI+PC9wYXRoPjxwYXRoIGQ9Ik0xNzcuODU0LDM2NS45NWMwLTYuMTI1LTQuOTYtMTEuMDc1LTExLjA4LTExLjA3NWgtMzguNjY1Yy02LjExMywwLTExLjA3NCw0Ljk1LTExLjA3NCwxMS4wNzV2MzguNjUzICAgIGMwLDYuMTE5LDQuOTYxLDExLjA3NCwxMS4wNzQsMTEuMDc0aDM4LjY2NWM2LjEyLDAsMTEuMDgtNC45NTYsMTEuMDgtMTEuMDc0VjM2NS45NUwxNzcuODU0LDM2NS45NXoiIGRhdGEtb3JpZ2luYWw9IiMwMDAwMDAiIGNsYXNzPSJhY3RpdmUtcGF0aCIgc3R5bGU9ImZpbGw6IzAwODBhOCIgZGF0YS1vbGRfY29sb3I9IiMwMDgwYTgiPjwvcGF0aD48cGF0aCBkPSJNMjc0LjQ4MywzNjUuOTVjMC02LjEyNS00Ljk2MS0xMS4wNzUtMTEuMDY5LTExLjA3NWgtMzguNjdjLTYuMTEzLDAtMTEuMDc0LDQuOTUtMTEuMDc0LDExLjA3NXYzOC42NTMgICAgYzAsNi4xMTksNC45NjEsMTEuMDc0LDExLjA3NCwxMS4wNzRoMzguNjdjNi4xMDgsMCwxMS4wNjktNC45NTYsMTEuMDY5LTExLjA3NFYzNjUuOTV6IiBkYXRhLW9yaWdpbmFsPSIjMDAwMDAwIiBjbGFzcz0iYWN0aXZlLXBhdGgiIHN0eWxlPSJmaWxsOiMwMDgwYTgiIGRhdGEtb2xkX2NvbG9yPSIjMDA4MGE4Ij48L3BhdGg+PHBhdGggZD0iTTM3MS4xMTcsMzY1Ljk1YzAtNi4xMjUtNC45NjEtMTEuMDc1LTExLjA2OS0xMS4wNzVoLTM4LjY3Yy02LjEyLDAtMTEuMDgsNC45NS0xMS4wOCwxMS4wNzV2MzguNjUzICAgIGMwLDYuMTE5LDQuOTYsMTEuMDc0LDExLjA4LDExLjA3NGgzOC42N2M2LjEwOCwwLDExLjA2OS00Ljk1NiwxMS4wNjktMTEuMDc0VjM2NS45NUwzNzEuMTE3LDM2NS45NXoiIGRhdGEtb3JpZ2luYWw9IiMwMDAwMDAiIGNsYXNzPSJhY3RpdmUtcGF0aCIgc3R5bGU9ImZpbGw6IzAwODBhOCIgZGF0YS1vbGRfY29sb3I9IiMwMDgwYTgiPjwvcGF0aD48cGF0aCBkPSJNNDQwLjI1NCw1NC4zNTR2NTkuMDVjMCwyNi42OS0yMS42NTIsNDguMTk4LTQ4LjMzOCw0OC4xOThoLTMwLjQ5M2MtMjYuNjg4LDAtNDguNjI3LTIxLjUwOC00OC42MjctNDguMTk4VjU0LjE0MiAgICBoLTEzNy40NHY1OS4yNjJjMCwyNi42OS0yMS45MzgsNDguMTk4LTQ4LjYyMiw0OC4xOThIOTYuMjM1Yy0yNi42ODUsMC00OC4zMzYtMjEuNTA4LTQ4LjMzNi00OC4xOTh2LTU5LjA1ICAgIEMyNC41NzYsNTUuMDU3LDUuNDExLDc0LjM1Niw1LjQxMSw5OC4wNzd2MzQ2LjA2MWMwLDI0LjE2NywxOS41ODgsNDQuMDE1LDQzLjc1NSw0NC4wMTVoMzg5LjgyICAgIGMyNC4xMzEsMCw0My43NTUtMTkuODg5LDQzLjc1NS00NC4wMTVWOTguMDc3QzQ4Mi43NDEsNzQuMzU2LDQ2My41NzcsNTUuMDU3LDQ0MC4yNTQsNTQuMzU0eiBNNDI2LjA5MSw0MjIuNTg4ICAgIGMwLDEwLjQ0NC04LjQ2OCwxOC45MTctMTguOTE2LDE4LjkxN0g4MC4xNDRjLTEwLjQ0OCwwLTE4LjkxNi04LjQ3My0xOC45MTYtMTguOTE3VjI0My44MzVjMC0xMC40NDgsOC40NjctMTguOTIxLDE4LjkxNi0xOC45MjEgICAgaDMyNy4wM2MxMC40NDgsMCwxOC45MTYsOC40NzMsMTguOTE2LDE4LjkyMUw0MjYuMDkxLDQyMi41ODhMNDI2LjA5MSw0MjIuNTg4eiIgZGF0YS1vcmlnaW5hbD0iIzAwMDAwMCIgY2xhc3M9ImFjdGl2ZS1wYXRoIiBzdHlsZT0iZmlsbDojMDA4MGE4IiBkYXRhLW9sZF9jb2xvcj0iIzAwODBhOCI+PC9wYXRoPjxwYXRoIGQ9Ik05Ni4xMjgsMTI5Ljk0NWgzMC4xNjJjOS4xNTUsMCwxNi41NzgtNy40MTIsMTYuNTc4LTE2LjU2N1YxNi41NzNDMTQyLjg2OCw3LjQxNywxMzUuNDQ1LDAsMTI2LjI5LDBIOTYuMTI4ICAgIEM4Ni45NzIsMCw3OS41NSw3LjQxNyw3OS41NSwxNi41NzN2OTYuODA1Qzc5LjU1LDEyMi41MzMsODYuOTcyLDEyOS45NDUsOTYuMTI4LDEyOS45NDV6IiBkYXRhLW9yaWdpbmFsPSIjMDAwMDAwIiBjbGFzcz0iYWN0aXZlLXBhdGgiIHN0eWxlPSJmaWxsOiMwMDgwYTgiIGRhdGEtb2xkX2NvbG9yPSIjMDA4MGE4Ij48L3BhdGg+PHBhdGggZD0iTTM2MS4wMzUsMTI5Ljk0NWgzMC4xNjJjOS4xNDksMCwxNi41NzItNy40MTIsMTYuNTcyLTE2LjU2N1YxNi41NzNDNDA3Ljc3LDcuNDE3LDQwMC4zNDcsMCwzOTEuMTk3LDBoLTMwLjE2MiAgICBjLTkuMTU0LDAtMTYuNTc3LDcuNDE3LTE2LjU3NywxNi41NzN2OTYuODA1QzM0NC40NTgsMTIyLjUzMywzNTEuODgxLDEyOS45NDUsMzYxLjAzNSwxMjkuOTQ1eiIgZGF0YS1vcmlnaW5hbD0iIzAwMDAwMCIgY2xhc3M9ImFjdGl2ZS1wYXRoIiBzdHlsZT0iZmlsbDojMDA4MGE4IiBkYXRhLW9sZF9jb2xvcj0iIzAwODBhOCI+PC9wYXRoPjwvZz48L2c+PC9nPiA8L3N2Zz4=')
}
@@ -10635,6 +10649,26 @@ table {
overflow: auto;
}
/********************** CallLogsContentViewModal ***********************/
.db-CallLogsContentViewModal-ContentViewerWrapper {
display: flex;
}
.db-CallLogsContentViewModal-ContentViewerWrapper > div {
flex: 1;
overflow: hidden;
}
.db-CallLogsContentViewModal-ContentViewerContainer:first-child {
margin: 0 10px;
}
.db-CallLogsContentViewModal-ContentViewer {
min-height: 300px;
max-height: 400px;
overflow: auto;
}
.price-list-3c {
display: grid;
grid-template-columns: auto auto auto;

View File

@@ -0,0 +1,262 @@
import { getApi, postApi, deleteApi } from '../api';
import * as types from '../constants/callLogs';
import errors from '../errors';
// Fetch All Call Logs
export const fetchCallLogsRequest = () => {
return {
type: types.FETCH_CALLLOGS_REQUEST,
};
};
export const fetchCallLogsSuccess = callLogs => {
return {
type: types.FETCH_CALLLOGS_SUCCESS,
payload: callLogs,
};
};
export const fetchCallLogsError = error => {
return {
type: types.FETCH_CALLLOGS_FAILURE,
payload: error,
};
};
export const fetchCallLogs = (skip, limit) => async dispatch => {
skip = skip ? parseInt(skip) : 0;
limit = limit ? parseInt(limit) : 10;
dispatch(fetchCallLogsRequest());
try {
const response = await getApi(`call-logs?skip=${skip}&limit=${limit}`);
const data = response.data;
dispatch(fetchCallLogsSuccess(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(fetchCallLogsError(errors(errorMsg)));
}
};
// Search Call Logs.
export const searchCallLogsRequest = () => {
return {
type: types.SEARCH_CALLLOGS_REQUEST,
};
};
export const searchCallLogsSuccess = callLogs => {
return {
type: types.SEARCH_CALLLOGS_SUCCESS,
payload: callLogs,
};
};
export const searchCallLogsError = error => {
return {
type: types.SEARCH_CALLLOGS_FAILURE,
payload: error,
};
};
export const searchCallLogs = (filter, skip, limit) => async dispatch => {
const values = {
filter,
};
dispatch(searchCallLogsRequest());
try {
const response = await postApi(
`call-logs/search?skip=${skip}&limit=${limit}`,
values
);
const data = response.data;
dispatch(searchCallLogsSuccess(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(searchCallLogsError(errors(errorMsg)));
}
};
// Delete All Call Logs
export const deleteCallLogsRequest = () => {
return {
type: types.DELETE_ALL_CALLLOGS_REQUEST,
};
};
export const deleteCallLogsSuccess = message => {
return {
type: types.DELETE_ALL_CALLLOGS_SUCCESS,
payload: message,
};
};
export const deleteCallLogsError = error => {
return {
type: types.DELETE_ALL_CALLLOGS_FAILURE,
payload: error,
};
};
export const deleteCallLogs = () => async dispatch => {
dispatch(deleteCallLogsRequest());
try {
const response = await deleteApi(`call-logs`);
const message = response.data.message;
dispatch(deleteCallLogsSuccess(message));
} 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(deleteCallLogsError(errors(errorMsg)));
}
};
// fetch callLogStatus
export function fetchCallLogStatusRequest(promise) {
return {
type: types.FETCH_CALLLOG_STATUS_REQUEST,
payload: promise,
};
}
export function fetchCallLogStatusError(error) {
return {
type: types.FETCH_CALLLOG_STATUS_FAILED,
payload: error,
};
}
export function fetchCallLogStatusSuccess(callLogStatus) {
return {
type: types.FETCH_CALLLOG_STATUS_SUCCESS,
payload: callLogStatus,
};
}
export const resetFetchCallLogStatus = () => {
return {
type: types.FETCH_CALLLOG_STATUS_RESET,
};
};
// Calls the API to fetch callLogStatus
export const fetchCallLogStatus = () => async dispatch => {
dispatch(fetchCallLogStatusRequest());
try {
const response = await getApi('globalConfig/callLogMonitoringStatus');
dispatch(fetchCallLogStatusSuccess(response.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(fetchCallLogStatusError(errors(errorMsg)));
return 'error';
}
};
// change callLogStatus
export function changeCallLogStatusRequest(promise) {
return {
type: types.CHANGE_CALLLOG_STATUS_REQUEST,
payload: promise,
};
}
export function changeCallLogStatusError(error) {
return {
type: types.CHANGE_CALLLOG_STATUS_FAILED,
payload: error,
};
}
export function changeCallLogStatusSuccess(callLogStatus) {
return {
type: types.CHANGE_CALLLOG_STATUS_SUCCESS,
payload: callLogStatus,
};
}
export const resetConfirmCallLogStatus = () => {
return {
type: types.CHANGE_CALLLOG_STATUS_RESET,
};
};
// Calls the API to change callLogStatus
export const callLogStatusChange = values => async dispatch => {
dispatch(changeCallLogStatusRequest());
try {
const response = await postApi('globalConfig/', [
{ name: 'callLogMonitoringStatus', value: values.status },
]);
const data = response.data;
dispatch(changeCallLogStatusSuccess(data));
return data;
} 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(changeCallLogStatusError(errors(errorMsg)));
return 'error';
}
};

View File

@@ -94,7 +94,6 @@ export class DashboardApp extends Component {
return (
<Fragment>
{/* <ClickOutside onClickOutside={this.closeModal} /> */}
<ClickOutside onClickOutside={this.hideProfileMenu}>
<ProfileMenu visible={this.props.profile.menuVisible} />
</ClickOutside>

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ShouldRender from '../basic/ShouldRender';
import ReactJson from 'react-json-view';
import ClickOutside from 'react-click-outside';
class AuditLogsJsonViewModal extends Component {
componentDidMount() {
@@ -40,98 +41,111 @@ class AuditLogsJsonViewModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>API Request and Response</span>
</span>
<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--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>
API Request and Response
</span>
</span>
</div>
</div>
</div>
<div className="bs-Modal-content">
<div className="jsonViwer Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<div className="db-AuditLogsJsonViewModal-JsonViewerWrapper">
<div className="db-AuditLogsJsonViewModal-JsonViewerContainer">
<div className="Text-fontWeight--medium">
Request
<div className="bs-Modal-content">
<div className="jsonViwer Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<div className="db-AuditLogsJsonViewModal-JsonViewerWrapper">
<div className="db-AuditLogsJsonViewModal-JsonViewerContainer">
<div className="Text-fontWeight--medium">
Request
</div>
<div className="db-AuditLogsJsonViewModal-JsonViewer">
<ReactJson
src={reqLog}
name="Request"
enableClipboard={false}
displayObjectSize={
false
}
displayDataTypes={false}
indentWidth={2}
collapsed={1}
style={{
fontSize: '12px',
}}
/>
</div>
</div>
<div className="db-AuditLogsJsonViewModal-JsonViewer">
<ReactJson
src={reqLog}
name="Request"
enableClipboard={false}
displayObjectSize={false}
displayDataTypes={false}
indentWidth={2}
collapsed={1}
style={{
fontSize: '12px',
}}
/>
</div>
</div>
<div className="AuditLogsJsonViewModal-JsonViewerContainer">
<div className="Text-fontWeight--medium">
Response
</div>
<div className="db-AuditLogsJsonViewModal-JsonViewer">
<ReactJson
src={resLog}
name="Response"
enableClipboard={false}
displayObjectSize={false}
displayDataTypes={false}
indentWidth={2}
collapsed={1}
style={{
fontSize: '12px',
}}
/>
<div className="AuditLogsJsonViewModal-JsonViewerContainer">
<div className="Text-fontWeight--medium">
Response
</div>
<div className="db-AuditLogsJsonViewModal-JsonViewer">
<ReactJson
src={resLog}
name="Response"
enableClipboard={false}
displayObjectSize={
false
}
displayDataTypes={false}
indentWidth={2}
collapsed={1}
style={{
fontSize: '12px',
}}
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</div>
</div>
</div>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
import { closeModal } from '../../actions/modal';
import { deleteAuditLogs } from '../../actions/auditLogs';
@@ -47,79 +48,86 @@ class DeleteConfirmationModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<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>Delete Audit Log</span>
<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>Delete Audit Log</span>
</span>
</div>
</div>
<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">
Do you want to delete all the logs?
</span>
</div>
</div>
<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">
Do you want to delete all the logs?
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</div>
</div>
</div>
</ShouldRender>
<button
id="cancelAuditDelete"
className={`bs-Button btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={deleteRequest}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
<button
id="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
onClick={this.handleDelete}
disabled={deleteRequest}
autoFocus={true}
>
<ShouldRender if={!deleteRequest}>
<span>Delete Logs</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</ShouldRender>
<ShouldRender if={deleteRequest}>
<span>
<FormLoader />
<button
id="cancelAuditDelete"
className={`bs-Button btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={deleteRequest}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</ShouldRender>
</button>
</button>
<button
id="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
onClick={this.handleDelete}
disabled={deleteRequest}
autoFocus={true}
>
<ShouldRender if={!deleteRequest}>
<span>Delete Logs</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</ShouldRender>
<ShouldRender if={deleteRequest}>
<span>
<FormLoader />
</span>
</ShouldRender>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -0,0 +1,146 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
class CallLogsContentViewModal extends Component {
componentDidMount() {
window.addEventListener('keydown', this.handleKeyboard);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeyboard);
}
handleKeyboard = e => {
switch (e.key) {
case 'Escape':
return this.props.closeThisDialog();
default:
return false;
}
};
render() {
const { isRequesting, error, closeThisDialog, content } = this.props;
return (
<div className="db-CallLogsContentViewModal 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">
<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--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Content</span>
</span>
</div>
</div>
<div className="bs-Modal-content">
<div className="jsonViwer Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<div className="db-CallLogsContentViewModal-ContentViewerWrapper">
<div className="db-CallLogsContentViewModal-ContentViewerContainer">
{content ? (
<div
dangerouslySetInnerHTML={{
__html: content,
}}
></div>
) : (
<span>
The Call Body is Empty.
</span>
)}
</div>
</div>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</div>
</div>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>
</div>
);
}
}
CallLogsContentViewModal.displayName = 'CallLogsContentViewModal';
const mapStateToProps = state => {
return {
isRequesting:
state.callLogs &&
state.callLogs.callLogs &&
state.callLogs.callLogs.requesting,
error:
state.callLogs &&
state.callLogs.callLogs &&
state.callLogs.callLogs.error,
};
};
CallLogsContentViewModal.propTypes = {
isRequesting: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf([null, undefined]),
]),
closeThisDialog: PropTypes.func,
error: PropTypes.oneOfType([
PropTypes.string,
PropTypes.oneOf([null, undefined]),
]),
content: PropTypes.string,
};
export default connect(mapStateToProps)(CallLogsContentViewModal);

View File

@@ -0,0 +1,125 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ShouldRender from '../basic/ShouldRender';
class CallLogsErrorViewModal extends Component {
componentDidMount() {
window.addEventListener('keydown', this.handleKeyboard);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeyboard);
}
handleKeyboard = e => {
switch (e.key) {
case 'Escape':
return this.props.closeThisDialog();
default:
return false;
}
};
render() {
const { isRequesting, error, closeThisDialog, content } = this.props;
return (
<div className="db-CallLogsContentViewModal 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="ds-Modal">
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Error</span>
</span>
</div>
</div>
<div className="bs-Modal-content">
<div className="db-CallLogsContentViewModal-ContentViewerWrapper">
<div className="db-CallLogsContentViewModal-ContentViewerContainer">
<span>{content}</span>
</div>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
</div>
</div>
</div>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
CallLogsErrorViewModal.displayName = 'CallLogsErrorViewModal';
const mapStateToProps = state => {
return {
isRequesting:
state.callLogs &&
state.callLogs.callLogs &&
state.callLogs.callLogs.requesting,
error:
state.callLogs &&
state.callLogs.callLogs &&
state.callLogs.callLogs.error,
};
};
CallLogsErrorViewModal.propTypes = {
isRequesting: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf([null, undefined]),
]),
closeThisDialog: PropTypes.func,
content: PropTypes.string,
error: PropTypes.oneOfType([
PropTypes.string,
PropTypes.oneOf([null, undefined]),
]),
};
export default connect(mapStateToProps)(CallLogsErrorViewModal);

View File

@@ -0,0 +1,478 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import uuid from 'uuid';
import { ListLoader } from '../basic/Loader';
import { openModal, closeModal } from '../../actions/modal';
import CallLogsContentViewModal from './CallLogsContentViewModal';
import CallLogsErrorViewModal from './CallLogsErrorViewModal';
import DeleteConfirmationModal from './DeleteConfirmationModal';
import { history } from '../../store';
export class CallLogsList extends Component {
constructor(props) {
super(props);
this.state = { deleteModalId: uuid.v4() };
}
handleDelete = () => {
const { openModal } = this.props;
const { deleteModalId } = this.state;
openModal({
id: deleteModalId,
content: DeleteConfirmationModal,
});
};
handleKeyBoard = e => {
switch (e.key) {
case 'Escape':
return this.props.closeModal({ id: this.state.deleteModalId });
default:
return false;
}
};
render() {
if (
this.props.callLogs &&
this.props.callLogs.skip &&
typeof this.props.callLogs.skip === 'string'
) {
this.props.callLogs.skip = parseInt(this.props.callLogs.skip, 10);
}
if (
this.props.callLogs &&
this.props.callLogs.limit &&
typeof this.props.callLogs.limit === 'string'
) {
this.props.callLogs.limit = parseInt(this.props.callLogs.limit, 10);
}
if (!this.props.callLogs.skip) this.props.callLogs.skip = 0;
if (!this.props.callLogs.limit) this.props.callLogs.limit = 0;
let canNext =
this.props.callLogs &&
this.props.callLogs.count &&
this.props.callLogs.count >
this.props.callLogs.skip + this.props.callLogs.limit
? true
: false;
let canPrev =
this.props.callLogs && this.props.callLogs.skip <= 0 ? false : true;
if (
this.props.callLogs &&
(this.props.requesting || !this.props.callLogs.callLogs)
) {
canNext = false;
canPrev = false;
}
return (
<div onKeyDown={this.handleKeyBoard}>
<div style={{ overflow: 'hidden', overflowX: 'auto' }}>
<table className="Table">
<thead className="Table-body">
<tr className="Table-row db-ListViewItem db-ListViewItem-header">
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--dark Text-display--inline Text-fontSize--13 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--upper Text-wrap--wrap">
<span>Status</span>
</span>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--dark Text-display--inline Text-fontSize--13 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--upper Text-wrap--wrap">
<span>Project Name</span>
</span>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-align--left Text-color--dark Text-display--block Text-fontSize--13 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--upper Text-wrap--wrap">
<span>From</span>
</span>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--dark Text-display--inline Text-fontSize--13 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--upper Text-wrap--wrap">
<span>To</span>
</span>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--dark Text-display--inline Text-fontSize--13 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--upper Text-wrap--wrap">
<span>Actions</span>
</span>
</div>
</td>
</tr>
</thead>
<tbody className="Table-body">
{this.props.requesting ? (
<Fragment>
<tr className="Table-row db-ListViewItem bs-ActionsParent db-ListViewItem--hasLink">
<td
colSpan={7}
className="Table-cell Table-cell--align--right Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-link">
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root">
<ListLoader />
</div>
</span>
</div>
</div>
</td>
</tr>
</Fragment>
) : this.props.callLogs &&
this.props.callLogs.callLogs &&
this.props.callLogs.callLogs.length > 0 ? (
this.props.callLogs.callLogs.map(callLog => {
return (
<tr
key={callLog._id}
className="Table-row db-ListViewItem bs-ActionsParent"
>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--wrap db-ListViewItem-cell db-ListViewItem-cell--breakWord"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--cyan Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root Margin-right--16">
<div
className={`Badge Badge--color--${
callLog.status ===
'Success'
? 'green'
: 'red'
} Box-root Flex-inlineFlex Flex-alignItems--center Padding-horizontal--8 Padding-vertical--2`}
>
<span
className={`Badge-text Text-color--${
callLog.status ===
'Success'
? 'green'
: 'red'
} Text-display--inline Text-fontSize--12 Text-fontWeight--bold Text-lineHeight--16 Text-typeface--upper Text-wrap--noWrap`}
>
<span>
{callLog.status
? callLog.status
: 'N/A'}
</span>
</span>
</div>
</div>
</span>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--wrap db-ListViewItem-cell"
style={{
height: '1px',
cursor: 'pointer',
textDecoration: callLog.projectId
? 'underline'
: null,
}}
onClick={() => {
history.push(
'/admin/projects/' +
callLog.projectId
._id
);
}}
>
<div className="db-ListViewItem-link">
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root">
<span>
{callLog.projectId
? callLog
.projectId
.name
: 'N/A'}
</span>
</div>
</span>
</div>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--wrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-link">
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root">
<span>
{callLog.from
? callLog.from
: 'N/A'}
</span>
</div>
</span>
</div>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--wrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-link">
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root Flex-flex">
<span>
{callLog.to
? callLog.to
: 'N/A'}
</span>
</div>
</span>
</div>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-link">
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root">
<span>
<button
onClick={() => {
this.props.openModal(
{
id: uuid.v4(),
onConfirm: () => {
return Promise.resolve();
},
content: props => (
<CallLogsContentViewModal
{...props}
content={
callLog.content
}
/>
),
}
);
}}
id="view"
className="bs-Button"
>
<span>
View
Content
</span>
</button>
{callLog.error ? (
<button
onClick={() => {
this.props.openModal(
{
id: uuid.v4(),
onConfirm: () => {
return Promise.resolve();
},
content: props => (
<CallLogsErrorViewModal
{...props}
content={
callLog.error
}
/>
),
}
);
}}
id="view"
className="bs-Button"
>
<span>
View
Error
</span>
</button>
) : null}
</span>
</div>
</span>
</div>
</div>
</td>
</tr>
);
})
) : (
<tr></tr>
)}
</tbody>
</table>
</div>
<div
id="logsStatus"
style={{ textAlign: 'center', marginTop: '10px' }}
>
{this.props.callLogs &&
(!this.props.callLogs.callLogs ||
!this.props.callLogs.callLogs.length) &&
!this.props.requesting &&
!this.props.callLogs.error
? "We don't have any logs yet"
: null}
{this.props.callLogs && this.props.callLogs.error
? this.props.callLogs.error
: null}
</div>
<div className="Box-root Flex-flex Flex-alignItems--center Flex-justifyContent--spaceBetween">
<div className="Box-root Flex-flex Flex-alignItems--center Padding-all--20">
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<span>
<span
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.callLogs &&
this.props.callLogs.count
? this.props.callLogs.count +
(this.props.callLogs &&
this.props.callLogs.count > 1
? ' Logs'
: ' Log')
: null}
</span>
</span>
</span>
</div>
<div className="Box-root Padding-horizontal--20 Padding-vertical--16">
<div className="Box-root Flex-flex Flex-alignItems--stretch Flex-direction--row Flex-justifyContent--flexStart">
<div className="Box-root Margin-right--8">
<button
id="btnPrev"
onClick={() => {
this.props.prevClicked(
this.props.callLogs.skip,
this.props.callLogs.limit
);
}}
className={
'Button bs-ButtonLegacy' +
(canPrev ? '' : 'Is--disabled')
}
disabled={!canPrev}
data-db-analytics-name="list_view.pagination.previous"
type="button"
>
<div className="Button-fill bs-ButtonLegacy-fill Box-root Box-background--white Flex-inlineFlex Flex-alignItems--center Flex-direction--row Padding-horizontal--8 Padding-vertical--4">
<span className="Button-label Text-color--default Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--noWrap">
<span>Previous</span>
</span>
</div>
</button>
</div>
<div className="Box-root Margin-right--8">
<button
id="btnNext"
onClick={() => {
this.props.nextClicked(
this.props.callLogs.skip,
this.props.callLogs.limit
);
}}
className={
'Button bs-ButtonLegacy' +
(canNext ? '' : 'Is--disabled')
}
disabled={!canNext}
data-db-analytics-name="list_view.pagination.next"
type="button"
>
<div className="Button-fill bs-ButtonLegacy-fill Box-root Box-background--white Flex-inlineFlex Flex-alignItems--center Flex-direction--row Padding-horizontal--8 Padding-vertical--4">
<span className="Button-label Text-color--default Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--noWrap">
<span>Next</span>
</span>
</div>
</button>
</div>
{/* <div className="Box-root">
<button
id="deleteLog"
onClick={this.handleDelete}
className={'Button bs-ButtonLegacy'}
// data-db-analytics-name="list_view.pagination.next"
type="button"
disabled={this.props.requesting}
>
<div className="Button-fill bs-ButtonLegacy-fill Box-root Box-background--white Flex-inlineFlex Flex-alignItems--center Flex-direction--row Padding-horizontal--8 Padding-vertical--4">
<span className="Button-label Text-color--default Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--noWrap">
<span>Delete All Logs</span>
</span>
</div>
</button>
</div> */}
</div>
</div>
</div>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return bindActionCreators({ openModal, closeModal }, dispatch);
};
function mapStateToProps(state) {
return {
users: state.user.users.users,
deleteRequest: state.callLogs.callLogs.deleteRequest,
};
}
CallLogsList.displayName = 'ProjectList';
CallLogsList.propTypes = {
nextClicked: PropTypes.func.isRequired,
prevClicked: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired,
callLogs: PropTypes.oneOfType([
PropTypes.object,
PropTypes.oneOf([null, undefined]),
]),
requesting: PropTypes.bool,
openModal: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(CallLogsList);

View File

@@ -0,0 +1,165 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
import { closeModal } from '../../actions/modal';
import { deleteCallLogs } from '../../actions/callLogs';
import { FormLoader } from '../basic/Loader';
class DeleteConfirmationModal extends Component {
componentDidMount() {
window.addEventListener('keydown', this.handleKeyboard);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeyboard);
}
handleKeyboard = e => {
switch (e.key) {
case 'Escape':
return this.props.closeThisDialog();
case 'Enter':
return this.handleDelete();
default:
return false;
}
};
handleDelete = () => {
const { error, deleteCallLogs, closeModal, modalId } = this.props;
deleteCallLogs().then(() => {
if (!error) {
return closeModal({ id: modalId });
}
});
};
render() {
const { closeThisDialog, deleteRequest, error } = 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>Delete Call Log</span>
</span>
</div>
</div>
<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">
Do you want to delete all the logs?
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</div>
</div>
</ShouldRender>
<button
id="cancelCallDelete"
className={`bs-Button btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={deleteRequest}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
<button
id="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
onClick={this.handleDelete}
disabled={deleteRequest}
autoFocus={true}
>
<ShouldRender if={!deleteRequest}>
<span>Delete Logs</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</ShouldRender>
<ShouldRender if={deleteRequest}>
<span>
<FormLoader />
</span>
</ShouldRender>
</button>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
deleteRequest: state.callLogs.callLogs.deleteRequest,
error: state.callLogs.callLogs.error,
modalId: state.modal.modals[0].id,
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ closeModal, deleteCallLogs }, dispatch);
DeleteConfirmationModal.displayName = 'Delete Confirmation Modal';
DeleteConfirmationModal.propTypes = {
closeThisDialog: PropTypes.func,
deleteRequest: PropTypes.bool,
error: PropTypes.oneOfType([
PropTypes.string,
PropTypes.oneOf([null, undefined]),
]),
closeModal: PropTypes.func,
deleteCallLogs: PropTypes.func,
modalId: PropTypes.string,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(DeleteConfirmationModal);

View File

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
import { closeModal } from '../../actions/modal';
import { deleteEmailLogs } from '../../actions/emailLogs';
@@ -47,79 +48,86 @@ class DeleteConfirmationModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<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>Delete Email Log</span>
<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>Delete Email Log</span>
</span>
</div>
</div>
<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">
Do you want to delete all the logs?
</span>
</div>
</div>
<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">
Do you want to delete all the logs?
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</div>
</div>
</div>
</ShouldRender>
<button
id="cancelEmailDelete"
className={`bs-Button btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={deleteRequest}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
<button
id="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
onClick={this.handleDelete}
disabled={deleteRequest}
autoFocus={true}
>
<ShouldRender if={!deleteRequest}>
<span>Delete Logs</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</ShouldRender>
<ShouldRender if={deleteRequest}>
<span>
<FormLoader />
<button
id="cancelEmailDelete"
className={`bs-Button btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={deleteRequest}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</ShouldRender>
</button>
</button>
<button
id="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
onClick={this.handleDelete}
disabled={deleteRequest}
autoFocus={true}
>
<ShouldRender if={!deleteRequest}>
<span>Delete Logs</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</ShouldRender>
<ShouldRender if={deleteRequest}>
<span>
<FormLoader />
</span>
</ShouldRender>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
class EmailLogsContentViewModal extends Component {
componentDidMount() {
@@ -31,74 +32,81 @@ class EmailLogsContentViewModal extends Component {
style={{ marginTop: 40 }}
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--large">
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Content</span>
</span>
<div className="ds-Modal">
<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--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Content</span>
</span>
</div>
</div>
</div>
<div className="bs-Modal-content">
<div className="jsonViwer Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<div className="db-EmailLogsContentViewModal-ContentViewerWrapper">
<div className="db-EmailLogsContentViewModal-ContentViewerContainer">
{content ? (
<div
dangerouslySetInnerHTML={{
__html: content,
}}
></div>
) : (
<span>
The Email Body is Empty.
</span>
)}
<div className="bs-Modal-content">
<div className="jsonViwer Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<div className="db-EmailLogsContentViewModal-ContentViewerWrapper">
<div className="db-EmailLogsContentViewModal-ContentViewerContainer">
{content ? (
<div
dangerouslySetInnerHTML={{
__html: content,
}}
></div>
) : (
<span>
The Email Body is Empty.
</span>
)}
</div>
</div>
</div>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</div>
</div>
</div>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -0,0 +1,125 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ShouldRender from '../basic/ShouldRender';
class EmailLogsErrorViewModal extends Component {
componentDidMount() {
window.addEventListener('keydown', this.handleKeyboard);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeyboard);
}
handleKeyboard = e => {
switch (e.key) {
case 'Escape':
return this.props.closeThisDialog();
default:
return false;
}
};
render() {
const { isRequesting, error, closeThisDialog, content } = this.props;
return (
<div className="db-EmailLogsContentViewModal 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="ds-Modal">
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Error</span>
</span>
</div>
</div>
<div className="bs-Modal-content">
<div className="db-EmailLogsContentViewModal-ContentViewerWrapper">
<div className="db-EmailLogsContentViewModal-ContentViewerContainer">
<span>{content}</span>
</div>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
</div>
</div>
</div>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
EmailLogsErrorViewModal.displayName = 'EmailLogsErrorViewModal';
const mapStateToProps = state => {
return {
isRequesting:
state.emailLogs &&
state.emailLogs.emailLogs &&
state.emailLogs.emailLogs.requesting,
error:
state.emailLogs &&
state.emailLogs.emailLogs &&
state.emailLogs.emailLogs.error,
};
};
EmailLogsErrorViewModal.propTypes = {
isRequesting: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf([null, undefined]),
]),
closeThisDialog: PropTypes.func,
content: PropTypes.string,
error: PropTypes.oneOfType([
PropTypes.string,
PropTypes.oneOf([null, undefined]),
]),
};
export default connect(mapStateToProps)(EmailLogsErrorViewModal);

View File

@@ -7,6 +7,7 @@ import uuid from 'uuid';
import { ListLoader } from '../basic/Loader';
import { openModal, closeModal } from '../../actions/modal';
import EmailLogsContentViewModal from './EmailLogsContentViewModal';
import EmailLogsErrorViewModal from './EmailLogsErrorViewModal';
import DeleteConfirmationModal from './DeleteConfirmationModal';
export class EmailLogsList extends Component {
@@ -129,6 +130,14 @@ export class EmailLogsList extends Component {
</span>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--dark Text-display--inline Text-fontSize--13 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--upper Text-wrap--wrap"></span>
</div>
</td>
</tr>
</thead>
<tbody className="Table-body">
@@ -292,6 +301,50 @@ export class EmailLogsList extends Component {
</div>
</div>
</td>
{emailLog.error ? (
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-link">
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root">
<span>
<button
onClick={() => {
this.props.openModal(
{
id: uuid.v4(),
onConfirm: () => {
return Promise.resolve();
},
content: props => (
<EmailLogsErrorViewModal
{...props}
content={
emailLog.error
}
/>
),
}
);
}}
id="view"
className="bs-Button"
>
<span>
View
Error
</span>
</button>
</span>
</div>
</span>
</div>
</div>
</td>
) : null}
</tr>
);
})

View File

@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ClickOutside from 'react-click-outside';
class About extends Component {
componentDidMount() {
window.addEventListener('keydown', this.handleKeyBoard);
@@ -22,8 +22,36 @@ class About extends Component {
};
render() {
const { versions } = this.props;
const { versions, closeThisDialog, probes } = this.props;
const currentYear = new Date().getFullYear();
let probeVersion = null;
if (probes && probes.length > 0) {
probeVersion = probes.map((probe, i) => {
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">
{probe.probeName} Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{probe.version ? (
<strong id="probe-version">
{probe.version}
</strong>
) : null}
</span>
</td>
</tr>
);
});
}
return (
<div className="ModalLayer-wash Box-root Flex-flex Flex-alignItems--flexStart Flex-justifyContent--center">
@@ -33,247 +61,260 @@ class About extends Component {
style={{ marginTop: 40 }}
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>About</span>
</span>
</div>
</div>
<div className="bs-Modal-content">
<table>
<tbody>
<tr>
<td
style={{
paddingBottom: '10px',
}}
colSpan={2}
>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
Fyipe is a product of{' '}
<a
href="https://hackerbay.io"
rel="noopener noreferrer"
target="_blank"
>
HackerBay, Inc.
</a>
. HackerBay, Inc. is a
United States Delaware C
Corporation.
</span>
</td>
</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">
Server Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.server ? (
<strong id="server-version">
{versions.server}
</strong>
) : null}
</span>
</td>
</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">
Admin Dashboard Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.adminDashboard ? (
<strong id="admin-dashboard-version">
{
versions.adminDashboard
}
</strong>
) : null}
</span>
</td>
</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">
Dashboard Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.dashboard ? (
<strong id="dashboard-version">
{versions.dashboard}
</strong>
) : null}
</span>
</td>
</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">
Docs Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.docs ? (
<strong id="docs-version">
{versions.docs}
</strong>
) : null}
</span>
</td>
</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">
Helm chart version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.helm ? (
<strong id="helm-version">
{versions.helm}
</strong>
) : null}
</span>
</td>
</tr>
<tr>
<td
style={{ paddingTop: '20px' }}
colSpan={2}
>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<a
href="https://fyipe.com/legal"
rel="noopener noreferrer"
target="_blank"
>
Legal Center
</a>
<span
style={{
paddingLeft: '10px',
}}
>
|
</span>
</span>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{
paddingLeft: '10px',
}}
>
<a
href="https://fyipe.com/legal/terms"
rel="noopener noreferrer"
target="_blank"
>
Terms of Use
</a>
<span
style={{
paddingLeft: '10px',
}}
>
|
</span>
</span>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{
paddingLeft: '10px',
}}
>
<a
href="https://fyipe.com/legal/privacy"
rel="noopener noreferrer"
target="_blank"
>
Privacy Policy
</a>
<span
style={{
paddingLeft: '10px',
}}
>
|
</span>
</span>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{
paddingLeft: '10px',
}}
>
<a
href="https://fyipe.com/legal/sla"
rel="noopener noreferrer"
target="_blank"
>
SLA
</a>
</span>
</td>
</tr>
</tbody>
</table>
</div>
<div className="bs-Modal-footer">
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
Copyright © {currentYear} HackerBay, Inc.
</span>
<div className="bs-Modal-footer-actions">
<button
className="bs-Button bs-DeprecatedButton bs-Button--grey btn__modal"
type="button"
onClick={this.props.closeThisDialog}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
<ClickOutside onClickOutside={closeThisDialog}>
<div className="bs-Modal bs-Modal--medium">
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>About</span>
</span>
</button>
</div>
</div>
<div className="bs-Modal-content">
<table>
<tbody>
<tr>
<td
style={{
paddingBottom: '10px',
}}
colSpan={2}
>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
Fyipe is a product of{' '}
<a
href="https://hackerbay.io"
rel="noopener noreferrer"
target="_blank"
>
HackerBay, Inc.
</a>
. HackerBay, Inc. is a
United States Delaware C
Corporation.
</span>
</td>
</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">
Server Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.server ? (
<strong id="server-version">
{
versions.server
}
</strong>
) : null}
</span>
</td>
</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">
Admin Dashboard Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.adminDashboard ? (
<strong id="admin-dashboard-version">
{
versions.adminDashboard
}
</strong>
) : null}
</span>
</td>
</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">
Dashboard Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.dashboard ? (
<strong id="dashboard-version">
{
versions.dashboard
}
</strong>
) : null}
</span>
</td>
</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">
Docs Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.docs ? (
<strong id="docs-version">
{versions.docs}
</strong>
) : null}
</span>
</td>
</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">
Helm Chart Version
</span>
</td>
<td>
<span
style={{
paddingLeft: '15px',
}}
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
>
{versions.helm ? (
<strong id="helm-version">
{versions.helm}
</strong>
) : null}
</span>
</td>
</tr>
{probeVersion}
<tr>
<td
style={{
paddingTop: '20px',
}}
colSpan={2}
>
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<a
href="https://fyipe.com/legal"
rel="noopener noreferrer"
target="_blank"
>
Legal Center
</a>
<span
style={{
paddingLeft:
'10px',
}}
>
|
</span>
</span>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{
paddingLeft: '10px',
}}
>
<a
href="https://fyipe.com/legal/terms"
rel="noopener noreferrer"
target="_blank"
>
Terms of Use
</a>
<span
style={{
paddingLeft:
'10px',
}}
>
|
</span>
</span>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{
paddingLeft: '10px',
}}
>
<a
href="https://fyipe.com/legal/privacy"
rel="noopener noreferrer"
target="_blank"
>
Privacy Policy
</a>
<span
style={{
paddingLeft:
'10px',
}}
>
|
</span>
</span>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{
paddingLeft: '10px',
}}
>
<a
href="https://fyipe.com/legal/sla"
rel="noopener noreferrer"
target="_blank"
>
SLA
</a>
</span>
</td>
</tr>
</tbody>
</table>
</div>
<div className="bs-Modal-footer">
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
Copyright © {currentYear} HackerBay,
Inc.
</span>
<div className="bs-Modal-footer-actions">
<button
className="bs-Button bs-DeprecatedButton bs-Button--grey btn__modal"
type="button"
onClick={this.props.closeThisDialog}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</div>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>
@@ -286,12 +327,17 @@ About.displayName = 'AboutModal';
const mapStateToProps = state => {
return {
versions: state.version.versions,
probes: state.probe.probes.data,
};
};
About.propTypes = {
closeThisDialog: PropTypes.func.isRequired,
versions: PropTypes.object,
probes: PropTypes.oneOfType([
PropTypes.object,
PropTypes.oneOf([null, undefined]),
]),
};
export default connect(mapStateToProps)(About);

View File

@@ -7,11 +7,13 @@ import { openNotificationMenu } from '../../actions/notification';
import { API_URL, User } from '../../config';
import { openSideNav } from '../../actions/page';
import { getVersion } from '../../actions/version';
import { getProbes } from '../../actions/probe';
class TopContent extends Component {
componentDidMount() {
const { getVersion } = this.props;
getVersion();
this.props.getProbes(0, 10);
}
showProfileMenu = e => {
this.props.showProfileMenu(e.clientX);
@@ -135,7 +137,13 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch =>
bindActionCreators(
{ showProfileMenu, openNotificationMenu, openSideNav, getVersion },
{
showProfileMenu,
openNotificationMenu,
openSideNav,
getVersion,
getProbes,
},
dispatch
);
@@ -154,6 +162,7 @@ TopContent.propTypes = {
]),
length: PropTypes.number,
map: PropTypes.func,
getProbes: PropTypes.func.isRequired,
};
TopContent.contextTypes = {

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Field, reduxForm } from 'redux-form';
import ClickOutside from 'react-click-outside';
import { FormLoader } from '../basic/Loader';
import ShouldRender from '../basic/ShouldRender';
import { RenderField } from '../basic/RenderField';
@@ -60,138 +61,155 @@ class ProbeAddModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<div className="bs-Modal-header">
<div
className="bs-Modal-header-copy"
style={{
marginBottom: '10px',
marginTop: '10px',
}}
>
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Add New Probe</span>
</span>
<ClickOutside onClickOutside={closeThisDialog}>
<div className="bs-Modal-header">
<div
className="bs-Modal-header-copy"
style={{
marginBottom: '10px',
marginTop: '10px',
}}
>
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Add New Probe</span>
</span>
</div>
</div>
</div>
<form
id="frmIncident"
onSubmit={handleSubmit(this.submitForm)}
>
<div className="bs-Modal-content bs-u-paddingless">
<div className="bs-Modal-block bs-u-paddingless">
<div className="bs-Modal-content">
<span className="bs-Fieldset">
<div className="bs-Fieldset-rows">
<div className="bs-Fieldset-row">
<label className="bs-Fieldset-label">
<span>Probe Name</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="text"
name="probe_name"
id="probe_name"
placeholder="US WEST"
disabled={disabled}
validate={
ValidateField.text
}
autoFocus={true}
/>
<form
id="frmIncident"
onSubmit={handleSubmit(this.submitForm)}
>
<div className="bs-Modal-content bs-u-paddingless">
<div className="bs-Modal-block bs-u-paddingless">
<div className="bs-Modal-content">
<span className="bs-Fieldset">
<div className="bs-Fieldset-rows">
<div className="bs-Fieldset-row">
<label className="bs-Fieldset-label">
<span>
Probe Name
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="text"
name="probe_name"
id="probe_name"
placeholder="US WEST"
disabled={
disabled
}
validate={
ValidateField.text
}
autoFocus={true}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label className="bs-Fieldset-label">
<span>
Probe Key
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="text"
name="probe_key"
id="probe_key"
placeholder="abcde-qw345-awqert-456yu"
disabled={
disabled
}
validate={
ValidateField.text
}
/>
</div>
</div>
</div>
<div className="bs-Fieldset-row">
<label className="bs-Fieldset-label">
<span>Probe Key</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="text"
name="probe_key"
id="probe_key"
placeholder="abcde-qw345-awqert-456yu"
disabled={disabled}
validate={
ValidateField.text
}
/>
</div>
</div>
</div>
</span>
</span>
</div>
</div>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender
if={
addProbeState && addProbeState.error
}
>
<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"></div>
</div>
<div className="Box-root">
<span
style={{ color: 'red' }}
>
{addProbeState.error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender
if={
addProbeState &&
addProbeState.error
}
>
<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"></div>
</div>
<div className="Box-root">
<span
style={{
color: 'red',
}}
>
{
addProbeState.error
}
</span>
</div>
</div>
</div>
</div>
</ShouldRender>
<button
className="bs-Button bs-DeprecatedButton btn__modal"
type="button"
onClick={() => {
resetAddProbe();
closeThisDialog();
}}
disabled={disabled}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
<button
id="add_probe"
className="bs-Button bs-DeprecatedButton bs-Button--blue btn__modal"
disabled={disabled}
type="submit"
>
{addProbeState &&
!addProbeState.requesting && (
<>
<span>Create</span>
<span className="create-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</>
)}
{addProbeState &&
addProbeState.requesting && (
<FormLoader />
)}
</button>
</ShouldRender>
<button
className="bs-Button bs-DeprecatedButton btn__modal"
type="button"
onClick={() => {
resetAddProbe();
closeThisDialog();
}}
disabled={disabled}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
<button
id="add_probe"
className="bs-Button bs-DeprecatedButton bs-Button--blue btn__modal"
disabled={disabled}
type="submit"
>
{addProbeState &&
!addProbeState.requesting && (
<>
<span>Create</span>
<span className="create-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</>
)}
{addProbeState &&
addProbeState.requesting && (
<FormLoader />
)}
</button>
</div>
</div>
</div>
</form>
</form>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
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 { closeModal } from '../../actions/modal';
@@ -48,74 +49,82 @@ class ProbeDeleteModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<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>Delete Probe</span>
<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>Delete Probe</span>
</span>
</div>
</div>
<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">
Are you sure you want to delete this
probe?
</span>
</div>
</div>
<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">
Are you sure you want to delete this probe?
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={this.handleDelete}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Delete Probe</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
<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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={this.handleDelete}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Delete Probe</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
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';
@@ -41,74 +42,82 @@ class ProjectBlockModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<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>Block Project</span>
<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>Block Project</span>
</span>
</div>
</div>
<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">
Are you sure you want to block this
project?
</span>
</div>
</div>
<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">
Are you sure you want to block this project?
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={confirmThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Block Project</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
<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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={confirmThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Block Project</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
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';
@@ -41,75 +42,82 @@ class ProjectDeleteModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<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>Delete Project</span>
<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>Delete Project</span>
</span>
</div>
</div>
<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">
Are you sure you want to delete this
project?
</span>
</div>
</div>
<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">
Are you sure you want to delete this
project?
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={confirmThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Delete Project</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
<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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={confirmThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Delete Project</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -158,26 +158,28 @@ export class ProjectList extends Component {
project.users.find(
user => user.role === 'Owner'
) || {};
const usersDetail =
project.users.length - 1 > 0
? project.users.length - 1 > 1
? `${
projectOwner.name
} and ${project.users
.length - 1} others`
: `${projectOwner.name} and 1 other`
: 'Not Added Yet';
let usersDetail;
if (project.users.length > 0) {
if (project.users.length === 1) {
usersDetail = `${projectOwner.name}`;
} else if (
project.users.length === 2
) {
usersDetail = `${projectOwner.name} and 1 other`;
} else {
usersDetail = `${
projectOwner.name
} and ${project.users.length -
1} others`;
}
} else {
usersDetail = 'Not Added Yet';
}
return (
<tr
key={project._id}
className="Table-row db-ListViewItem bs-ActionsParent db-ListViewItem--hasLink"
onClick={() => {
history.push(
'/admin/projects/' +
project._id
);
}}
id={`project_${index}`}
>
<td
@@ -185,6 +187,14 @@ export class ProjectList extends Component {
style={{
height: '1px',
minWidth: '270px',
textDecoration:
'underline',
}}
onClick={() => {
history.push(
'/admin/projects/' +
project._id
);
}}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
@@ -201,7 +211,21 @@ export class ProjectList extends Component {
</td>
<td
className="Table-cell Table-cell--align--right Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
style={{
height: '1px',
textDecoration:
'underline',
}}
onClick={() => {
if (
projectOwner.userId
) {
history.push(
'/admin/users/' +
projectOwner.userId
);
}
}}
>
<div className="db-ListViewItem-link">
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { PropTypes } from 'prop-types';
import { connect } from 'react-redux';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
const MessageModal = props => {
@@ -21,48 +22,50 @@ const MessageModal = props => {
className="bs-Modal bs-Modal--medium"
style={{ minWidth: '400px' }}
>
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>
{testError
? 'Test Failed'
: 'Test Email Sent'}
<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--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>
{testError
? 'Test Failed'
: 'Test Email Sent'}
</span>
</span>
</span>
</div>
</div>
</div>
<div className="Flex-flex bs-Modal-content">
<ShouldRender if={testError}>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{ flex: 1 }}
>
{testError}
</span>
</ShouldRender>
<ShouldRender if={!testError}>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{ flex: 1 }}
>
We&#39;ve successfully sent a test email to{' '}
{email}. If you do not see it, please check
spam.
</span>
</ShouldRender>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<button
id="confirmDelete"
className="bs-Button"
onClick={closeThisDialog}
>
<span>Ok</span>
</button>
<div className="Flex-flex bs-Modal-content">
<ShouldRender if={testError}>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{ flex: 1 }}
>
{testError}
</span>
</ShouldRender>
<ShouldRender if={!testError}>
<span
className="Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap"
style={{ flex: 1 }}
>
We&#39;ve successfully sent a test email
to {email}. If you do not see it, please
check spam.
</span>
</ShouldRender>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<button
id="confirmDelete"
className="bs-Button"
onClick={closeThisDialog}
>
<span>Ok</span>
</button>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -0,0 +1,183 @@
import React, { Component } from 'react';
import { FormLoader } from '../basic/Loader';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import {
fetchCallLogStatus,
callLogStatusChange,
} from '../../actions/callLogs';
class CallLog extends Component {
async componentDidMount() {
await this.props.fetchCallLogStatus();
}
toggleComponent = ({ input: { value, onChange } }) => (
<label className="Toggler-wrap">
<input
className="btn-toggler"
checked={value}
onChange={onChange}
type="checkbox"
name="callStatusToggler"
id="callStatusToggler"
/>
<span className="TogglerBtn-slider round"></span>
</label>
);
submitForm = values => {
this.props.callLogStatusChange({ status: values.callStatusToggler });
};
render() {
const { changeCallLogStatus, handleSubmit } = this.props;
return (
<div
id="fyipeCallLog"
onKeyDown={this.handleKeyBoard}
className="bs-ContentSection Card-root Card-shadow--medium"
>
<div className="Box-root">
<div className="bs-ContentSection-content Box-root Box-divider--surface-bottom-1 Padding-horizontal--20 Padding-vertical--16">
<div className="Box-root">
<div className="Flex-flex Flex-alignItems-center Flex-justifyContent--spaceBetween">
<span className="Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Call Logs Settings</span>
</span>
</div>
<p>
<span>
Here you can enable or disable call logs
being monitored on your Fyipe projects.
</span>
</p>
</div>
</div>
<form
id="call-log-toggle-form"
onSubmit={handleSubmit(this.submitForm)}
>
<div className="bs-ContentSection-content Box-root Box-background--offset Box-divider--surface-bottom-1 Padding-horizontal--8 Padding-vertical--2">
<div>
<div className="bs-Fieldset-wrapper Box-root Margin-bottom--2">
<fieldset className="bs-Fieldset">
<div className="bs-Fieldset-rows">
<div className="bs-Fieldset-row">
<label className="bs-Fieldset-label">
Enable Call Logs
</label>
<div
className="bs-Fieldset-fields"
style={{
paddingTop: 3,
}}
>
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
name="callStatusToggler"
id="callStatusToggler"
component={
this.toggleComponent
}
disabled={
changeCallLogStatus &&
changeCallLogStatus.requesting
}
/>
</div>
</div>
</div>
</fieldset>
</div>
</div>
</div>
<div className="bs-ContentSection-footer bs-ContentSection-content Box-root Box-background--white Flex-flex Flex-alignItems--center Flex-justifyContent--spaceBetween Padding-horizontal--20 Padding-vertical--12">
<span className="db-SettingsForm-footerMessage">
{!changeCallLogStatus.requesting &&
changeCallLogStatus.error && (
<div
id="errors"
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"></div>
</div>
<div className="Box-root">
<span
style={{ color: 'red' }}
>
{
changeCallLogStatus.error
}
</span>
</div>
</div>
</div>
)}
</span>
<div>
<button
className="bs-Button bs-Button--blue"
disabled={
changeCallLogStatus &&
changeCallLogStatus.requesting
}
type="submit"
id="callLogSubmit"
>
{changeCallLogStatus.requesting ? (
<FormLoader />
) : (
<span>Save Settings</span>
)}
</button>
</div>
</div>
</form>
</div>
</div>
);
}
}
CallLog.displayName = 'CallLog';
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{ fetchCallLogStatus, callLogStatusChange },
dispatch
);
};
function mapStateToProps(state) {
const callLogStatus = state.callLogs.callLogStatus;
const changeCallLogStatus = state.callLogs.changeCallLogStatus;
return {
settings: state.settings,
callLogStatus,
changeCallLogStatus,
initialValues: {
callStatusToggler: callLogStatus.data
? callLogStatus.data.value
: false,
},
};
}
const ReduxFormComponent = reduxForm({
form: 'call-log-toggle-form',
enableReinitialize: true,
})(CallLog);
CallLog.propTypes = {
changeCallLogStatus: PropTypes.object,
handleSubmit: PropTypes.func,
fetchCallLogStatus: PropTypes.func,
callLogStatusChange: PropTypes.func,
};
export default connect(mapStateToProps, mapDispatchToProps)(ReduxFormComponent);

View File

@@ -140,15 +140,6 @@ export class Component extends React.Component {
await this.props.fetchSettings(settingsType);
}
componentDidUpdate() {
const { settings } = this.props;
if (settings && settings.testSuccess) {
if (window.location.href.indexOf('localhost') <= -1) {
this.context.mixpanel.track('Sent SMTP settings');
}
}
}
handleTestSmtp = e => {
e.preventDefault();
const { testSmtp, smtpForm } = this.props;

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import { reduxForm, Field } from 'redux-form';
import PropTypes from 'prop-types';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
import { Validate } from '../../config';
import { RenderField } from '../basic/RenderField';
@@ -26,112 +27,119 @@ const SmtpTestModal = ({
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Test SMTP Settings</span>
</span>
<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--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Test SMTP Settings</span>
</span>
</div>
<div className="bs-Modal-header-copy Margin-top--8">
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
If your SMTP settings are correct, a
test email will be sent to this email
address.
</span>
</div>
</div>
<div className="bs-Modal-header-copy Margin-top--8">
<span className="Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
If your SMTP settings are correct, a test
email will be sent to this email address.
</span>
<div className="bs-ContentSection-content Box-root Box-background--offset Box-divider--surface-bottom-1 Padding-horizontal--8 Padding-vertical--2">
<div>
<div className="bs-Fieldset-wrapper Box-root Margin-bottom--2">
<fieldset className="bs-Fieldset">
<div className="bs-Fieldset-rows">
<div className="Margin-bottom--20 Margin-top--20">
<div
style={{
paddingTop: 3,
}}
>
<Field
className="db-BusinessSettings-input-wide TextInput bs-TextInput"
type="text"
name="test-email"
id="testEmail"
placeholder="Enter an email"
component={
RenderField
}
disabled={testing}
/>
</div>
</div>
</div>
</fieldset>
</div>
</div>
</div>
</div>
<div className="bs-ContentSection-content Box-root Box-background--offset Box-divider--surface-bottom-1 Padding-horizontal--8 Padding-vertical--2">
<div>
<div className="bs-Fieldset-wrapper Box-root Margin-bottom--2">
<fieldset className="bs-Fieldset">
<div className="bs-Fieldset-rows">
<div className="Margin-bottom--20 Margin-top--20">
<div
style={{
paddingTop: 3,
}}
>
<Field
className="db-BusinessSettings-input-wide TextInput bs-TextInput"
type="text"
name="test-email"
id="testEmail"
placeholder="Enter an email"
component={RenderField}
disabled={testing}
/>
<div
className="bs-Modal-footer"
style={{ wordBreak: 'break-word' }}
>
<div className="Flex-flex Flex-direction--row Flex-justifyContent--flexEnd Table-cell--width--maximized">
<ShouldRender if={testError}>
<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 Margin-right--8">
<span
style={{
color: 'red',
}}
>
{testError}
</span>
</div>
</div>
</div>
</fieldset>
</ShouldRender>
<button
id="cancelSmtpTest"
className={`bs-Button ${testing &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={testing}
style={{ height: '33px' }}
>
<span>Cancel</span>
</button>
<button
id="confirmSmtpTest"
className={`bs-Button bs-Button--blue ${testing &&
'bs-is-disabled'}`}
onClick={() => {
// prevent form submission if form field is empty or invalid
if (!smtp.values) return;
if (
smtp.syncErrors &&
smtp.syncErrors['test-email']
)
return;
confirmThisDialog(smtp.values);
}}
disabled={testing}
style={{ height: '33px' }}
>
{testing ? (
<FormLoader />
) : (
<span>Test</span>
)}
</button>
</div>
</div>
</div>
<div
className="bs-Modal-footer"
style={{ wordBreak: 'break-word' }}
>
<div className="Flex-flex Flex-direction--row Flex-justifyContent--flexEnd Table-cell--width--maximized">
<ShouldRender if={testError}>
<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 Margin-right--8">
<span
style={{
color: 'red',
}}
>
{testError}
</span>
</div>
</div>
</div>
</ShouldRender>
<button
id="cancelSmtpTest"
className={`bs-Button ${testing &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={testing}
style={{ height: '33px' }}
>
<span>Cancel</span>
</button>
<button
id="confirmSmtpTest"
className={`bs-Button bs-Button--blue ${testing &&
'bs-is-disabled'}`}
onClick={() => {
// prevent form submission if form field is empty or invalid
if (!smtp.values) return;
if (
smtp.syncErrors &&
smtp.syncErrors['test-email']
)
return;
confirmThisDialog(smtp.values);
}}
disabled={testing}
style={{ height: '33px' }}
>
{testing ? (
<FormLoader />
) : (
<span>Test</span>
)}
</button>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
import { closeModal } from '../../actions/modal';
import { deleteSmsLogs } from '../../actions/smsLogs';
@@ -47,79 +48,86 @@ class DeleteConfirmationModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<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>Delete SMS Log</span>
<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>Delete SMS Log</span>
</span>
</div>
</div>
<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">
Do you want to delete all the logs?
</span>
</div>
</div>
<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">
Do you want to delete all the logs?
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</div>
</div>
</div>
</ShouldRender>
<button
id="cancelSmsDelete"
className={`bs-Button btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={deleteRequest}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
<button
id="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
onClick={this.handleDelete}
disabled={deleteRequest}
autoFocus={true}
>
<ShouldRender if={!deleteRequest}>
<span>Delete Logs</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</ShouldRender>
<ShouldRender if={deleteRequest}>
<span>
<FormLoader />
<button
id="cancelSmsDelete"
className={`bs-Button btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={deleteRequest}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</ShouldRender>
</button>
</button>
<button
id="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${deleteRequest &&
'bs-is-disabled'}`}
onClick={this.handleDelete}
disabled={deleteRequest}
autoFocus={true}
>
<ShouldRender if={!deleteRequest}>
<span>Delete Logs</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</ShouldRender>
<ShouldRender if={deleteRequest}>
<span>
<FormLoader />
</span>
</ShouldRender>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
class SmsLogsContentViewModal extends Component {
componentDidMount() {
@@ -32,67 +33,80 @@ class SmsLogsContentViewModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--large">
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Content</span>
</span>
<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--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Content</span>
</span>
</div>
</div>
</div>
<div className="bs-Modal-content">
<div className="jsonViwer Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<div className="db-SmsLogsContentViewModal-ContentViewerWrapper">
<div className="db-SmsLogsContentViewModal-ContentViewerContainer">
<div
dangerouslySetInnerHTML={{
__html: content,
}}
></div>
<div className="bs-Modal-content">
<div className="jsonViwer Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<div className="db-SmsLogsContentViewModal-ContentViewerWrapper">
<div className="db-SmsLogsContentViewModal-ContentViewerContainer">
{content ? (
<div
dangerouslySetInnerHTML={{
__html: content,
}}
></div>
) : (
<span>
The SMS Body is Empty.
</span>
)}
</div>
</div>
</div>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</div>
</div>
</div>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -0,0 +1,125 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import ShouldRender from '../basic/ShouldRender';
class SmsLogsErrorViewModal extends Component {
componentDidMount() {
window.addEventListener('keydown', this.handleKeyboard);
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeyboard);
}
handleKeyboard = e => {
switch (e.key) {
case 'Escape':
return this.props.closeThisDialog();
default:
return false;
}
};
render() {
const { isRequesting, error, closeThisDialog, content } = this.props;
return (
<div className="db-SmsLogsContentViewModal 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="ds-Modal">
<div className="bs-Modal-header">
<div className="bs-Modal-header-copy">
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Error</span>
</span>
</div>
</div>
<div className="bs-Modal-content">
<div className="db-SmsLogsContentViewModal-ContentViewerWrapper">
<div className="db-SmsLogsContentViewModal-ContentViewerContainer">
<span>{content}</span>
</div>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
</div>
</div>
</div>
</ShouldRender>
<button
className={`bs-Button btn__modal ${isRequesting &&
'bs-is-disabled'}`}
type="button"
onClick={closeThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<span>Close</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
SmsLogsErrorViewModal.displayName = 'SmsLogsErrorViewModal';
const mapStateToProps = state => {
return {
isRequesting:
state.smsLogs &&
state.smsLogs.smsLogs &&
state.smsLogs.smsLogs.requesting,
error:
state.smsLogs &&
state.smsLogs.smsLogs &&
state.smsLogs.smsLogs.error,
};
};
SmsLogsErrorViewModal.propTypes = {
isRequesting: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf([null, undefined]),
]),
closeThisDialog: PropTypes.func,
content: PropTypes.string,
error: PropTypes.oneOfType([
PropTypes.string,
PropTypes.oneOf([null, undefined]),
]),
};
export default connect(mapStateToProps)(SmsLogsErrorViewModal);

View File

@@ -8,6 +8,9 @@ import { ListLoader } from '../basic/Loader';
import { openModal, closeModal } from '../../actions/modal';
import DeleteConfirmationModal from './DeleteConfirmationModal';
import SmsLogsContentViewModal from './SmsLogsContentViewModal';
import SmsLogsErrorViewModal from './SmsLogsErrorViewModal';
import { history } from '../../store';
export class SmsLogsList extends Component {
constructor(props) {
@@ -74,6 +77,16 @@ export class SmsLogsList extends Component {
<table className="Table">
<thead className="Table-body">
<tr className="Table-row db-ListViewItem db-ListViewItem-header">
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--dark Text-display--inline Text-fontSize--13 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--upper Text-wrap--wrap">
<span>Status</span>
</span>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--noWrap db-ListViewItem-cell"
style={{ height: '1px' }}
@@ -90,7 +103,7 @@ export class SmsLogsList extends Component {
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--dark Text-display--inline Text-fontSize--13 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--upper Text-wrap--wrap">
<span>User name</span>
<span>Users</span>
</span>
</div>
</td>
@@ -146,17 +159,67 @@ export class SmsLogsList extends Component {
key={smsLog._id}
className="Table-row db-ListViewItem bs-ActionsParent"
>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--wrap db-ListViewItem-cell db-ListViewItem-cell--breakWord"
style={{
height: '1px',
}}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--cyan Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root Margin-right--16">
<div
className={`Badge Badge--color--${
smsLog.status ===
'Success'
? 'green'
: 'red'
} Box-root Flex-inlineFlex Flex-alignItems--center Padding-horizontal--8 Padding-vertical--2`}
>
<span
className={`Badge-text Text-color--${
smsLog.status ===
'Success'
? 'green'
: 'red'
} Text-display--inline Text-fontSize--12 Text-fontWeight--bold Text-lineHeight--16 Text-typeface--upper Text-wrap--noWrap`}
>
<span>
{smsLog.status
? smsLog.status
: 'N/A'}
</span>
</span>
</div>
</div>
</span>
</div>
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--wrap db-ListViewItem-cell"
style={{ height: '1px' }}
style={{
height: '1px',
cursor: 'pointer',
textDecoration: smsLog.projectId
? 'underline'
: null,
}}
onClick={() => {
history.push(
'/admin/projects/' +
smsLog.projectId._id
);
}}
>
<div className="db-ListViewItem-link">
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<span className="db-ListViewItem-text Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root">
<span>
{smsLog.projectId
? smsLog.projectId
? smsLog
.projectId
.name
: 'N/A'}
</span>
</div>
@@ -166,14 +229,31 @@ export class SmsLogsList extends Component {
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--wrap db-ListViewItem-cell db-ListViewItem-cell--breakWord"
style={{ height: '1px' }}
style={{
height: '1px',
cursor: 'pointer',
textDecoration: smsLog.userId
? 'underline'
: null,
}}
onClick={() => {
if (smsLog.userId) {
history.push(
'/admin/users/' +
smsLog.userId
._id
);
}
}}
>
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
<span className="db-ListViewItem-text Text-color--cyan Text-display--inline Text-fontSize--14 Text-fontWeight--medium Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<div className="Box-root Margin-right--16">
<span>
{smsLog.userId
? smsLog.userId
? smsLog
.userId
.name
: 'N/A'}
</span>
</div>
@@ -182,7 +262,9 @@ export class SmsLogsList extends Component {
</td>
<td
className="Table-cell Table-cell--align--left Table-cell--verticalAlign--top Table-cell--width--minimized Table-cell--wrap--wrap db-ListViewItem-cell"
style={{ height: '1px' }}
style={{
height: '1px',
}}
>
<div className="db-ListViewItem-link">
<div className="db-ListViewItem-cellContent Box-root Padding-all--8">
@@ -234,6 +316,35 @@ export class SmsLogsList extends Component {
Content
</span>
</button>
{smsLog.error ? (
<button
onClick={() => {
this.props.openModal(
{
id: uuid.v4(),
onConfirm: () => {
return Promise.resolve();
},
content: props => (
<SmsLogsErrorViewModal
{...props}
content={
smsLog.error
}
/>
),
}
);
}}
id="view"
className="bs-Button"
>
<span>
View
Error
</span>
</button>
) : null}
</span>
</div>
</span>

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { PropTypes } from 'prop-types';
import ClickOutside from 'react-click-outside';
const MessageModal = props => {
const { closeThisDialog } = props;
@@ -16,29 +17,31 @@ const MessageModal = props => {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<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>Info</span>
<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>Info</span>
</span>
</div>
</div>
<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">
Only the user can turn on 2FA not the admin
</span>
</div>
</div>
<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">
Only the user can turn on 2FA not the admin
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<button
id="confirmDelete"
className="bs-Button"
onClick={closeThisDialog}
>
<span>Ok</span>
</button>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<button
id="confirmDelete"
className="bs-Button"
onClick={closeThisDialog}
>
<span>Ok</span>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Field, reduxForm } from 'redux-form';
import ClickOutside from 'react-click-outside';
import { FormLoader } from '../basic/Loader';
import ShouldRender from '../basic/ShouldRender';
import { RenderField } from '../basic/RenderField';
@@ -62,230 +63,256 @@ class UserAddModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<div className="bs-Modal-header">
<div
className="bs-Modal-header-copy"
style={{
marginBottom: '10px',
marginTop: '10px',
}}
>
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Add New User</span>
</span>
<ClickOutside onClickOutside={closeThisDialog}>
<div className="bs-Modal-header">
<div
className="bs-Modal-header-copy"
style={{
marginBottom: '10px',
marginTop: '10px',
}}
>
<span className="Text-color--inherit Text-display--inline Text-fontSize--20 Text-fontWeight--medium Text-lineHeight--24 Text-typeface--base Text-wrap--wrap">
<span>Add New User</span>
</span>
</div>
</div>
</div>
<form
id="frmUser"
onSubmit={handleSubmit(this.submitForm)}
>
<div className="bs-Modal-content bs-u-paddingless">
<div className="bs-Modal-block bs-u-paddingless">
<div className="bs-Modal-content">
<span className="bs-Fieldset">
<div className="bs-Fieldset-rows">
<div className="bs-Fieldset-row">
<label
htmlFor="email"
className="bs-Fieldset-label"
>
<span>Email</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="email"
name="email"
id="email"
placeholder="jeff@example.com"
required="required"
disabled={disabled}
autoFocus={true}
/>
<form
id="frmUser"
onSubmit={handleSubmit(this.submitForm)}
>
<div className="bs-Modal-content bs-u-paddingless">
<div className="bs-Modal-block bs-u-paddingless">
<div className="bs-Modal-content">
<span className="bs-Fieldset">
<div className="bs-Fieldset-rows">
<div className="bs-Fieldset-row">
<label
htmlFor="email"
className="bs-Fieldset-label"
>
<span>Email</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="email"
name="email"
id="email"
placeholder="jeff@example.com"
required="required"
disabled={
disabled
}
autoFocus={true}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="name"
className="bs-Fieldset-label"
>
<span>
Full Name
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="text"
name="name"
id="name"
placeholder="Jeff Smith"
required="required"
disabled={
disabled
}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="companyName"
className="bs-Fieldset-label"
>
<span>
Company Name
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="text"
name="companyName"
id="companyName"
placeholder="Company Name"
disabled={
disabled
}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="companyPhoneNumber"
className="bs-Fieldset-label"
>
<span>
Company Phone
Number
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="tel"
name="companyPhoneNumber"
id="companyPhoneNumber"
placeholder="+1-123-456-7890"
disabled={
disabled
}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="password"
className="bs-Fieldset-label"
>
<span>
Password
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="password"
name="password"
id="password"
placeholder="Password"
required="required"
disabled={
disabled
}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="confirmPassword"
className="bs-Fieldset-label"
>
<span>
Confirm Password
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="password"
name="confirmPassword"
id="confirmPassword"
placeholder="Confirm Password"
required="required"
disabled={
disabled
}
/>
</div>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="name"
className="bs-Fieldset-label"
>
<span>Full Name</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="text"
name="name"
id="name"
placeholder="Jeff Smith"
required="required"
disabled={disabled}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="companyName"
className="bs-Fieldset-label"
>
<span>
Company Name
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="text"
name="companyName"
id="companyName"
placeholder="Company Name"
disabled={disabled}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="companyPhoneNumber"
className="bs-Fieldset-label"
>
<span>
Company Phone Number
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="tel"
name="companyPhoneNumber"
id="companyPhoneNumber"
placeholder="+1-123-456-7890"
disabled={disabled}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="password"
className="bs-Fieldset-label"
>
<span>Password</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="password"
name="password"
id="password"
placeholder="Password"
required="required"
disabled={disabled}
/>
</div>
</div>
<div className="bs-Fieldset-row">
<label
htmlFor="confirmPassword"
className="bs-Fieldset-label"
>
<span>
Confirm Password
</span>
</label>
<div className="bs-Fieldset-fields">
<Field
className="db-BusinessSettings-input TextInput bs-TextInput"
component={
RenderField
}
type="password"
name="confirmPassword"
id="confirmPassword"
placeholder="Confirm Password"
required="required"
disabled={disabled}
/>
</div>
</div>
</div>
</span>
</span>
</div>
</div>
</div>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender
if={addUserState && addUserState.error}
>
<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"></div>
</div>
<div className="Box-root">
<span
style={{ color: 'red' }}
>
{addUserState.error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender
if={
addUserState &&
addUserState.error
}
>
<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"></div>
</div>
<div className="Box-root">
<span
style={{
color: 'red',
}}
>
{addUserState.error}
</span>
</div>
</div>
</div>
</div>
</ShouldRender>
<button
className="bs-Button bs-DeprecatedButton btn__modal"
type="button"
onClick={() => {
resetAddUser();
closeThisDialog();
}}
disabled={disabled}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
<button
id="add_user_btn"
className="bs-Button bs-DeprecatedButton bs-Button--blue btn__modal"
disabled={disabled}
type="submit"
>
{addUserState &&
!addUserState.requesting && (
<>
<span>Create</span>
<span className="create-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</>
)}
{addUserState &&
addUserState.requesting && (
<FormLoader />
)}
</button>
</ShouldRender>
<button
className="bs-Button bs-DeprecatedButton btn__modal"
type="button"
onClick={() => {
resetAddUser();
closeThisDialog();
}}
disabled={disabled}
>
<span>Cancel</span>
<span className="cancel-btn__keycode">
Esc
</span>
</button>
<button
id="add_user_btn"
className="bs-Button bs-DeprecatedButton bs-Button--blue btn__modal"
disabled={disabled}
type="submit"
>
{addUserState &&
!addUserState.requesting && (
<>
<span>Create</span>
<span className="create-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</>
)}
{addUserState &&
addUserState.requesting && (
<FormLoader />
)}
</button>
</div>
</div>
</div>
</form>
</form>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
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';
@@ -41,74 +42,82 @@ class UserBlockModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<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>Block User</span>
<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>Block User</span>
</span>
</div>
</div>
<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">
Are you sure you want to block this
user?
</span>
</div>
</div>
<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">
Are you sure you want to block this user?
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={confirmThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Block User</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
<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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={confirmThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Block User</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
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';
@@ -41,74 +42,82 @@ class UserDeleteModal extends Component {
>
<div className="bs-BIM">
<div className="bs-Modal bs-Modal--medium">
<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>Delete User</span>
<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>Delete User</span>
</span>
</div>
</div>
<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">
Are you sure you want to delete this
user?
</span>
</div>
</div>
<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">
Are you sure you want to delete this user?
</span>
</div>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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' }}
>
{error}
</span>
<div className="bs-Modal-footer">
<div className="bs-Modal-footer-actions">
<ShouldRender if={error}>
<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',
}}
>
{error}
</span>
</div>
</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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={confirmThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Delete User</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
<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="confirmDelete"
className={`bs-Button bs-Button--red Box-background--red btn__modal ${isRequesting &&
'bs-is-disabled'}`}
onClick={confirmThisDialog}
disabled={isRequesting}
autoFocus={true}
>
<ShouldRender if={isRequesting}>
<Spinner />
</ShouldRender>
<span>Delete User</span>
<span className="delete-btn__keycode">
<span className="keycode__icon keycode__icon--enter" />
</span>
</button>
</div>
</div>
</div>
</ClickOutside>
</div>
</div>
</div>

View File

@@ -0,0 +1,26 @@
// Fetch All Call Log List
export const FETCH_CALLLOGS_REQUEST = 'FETCH_CALLLOGS_REQUEST';
export const FETCH_CALLLOGS_SUCCESS = 'FETCH_CALLLOGS_SUCCESS';
export const FETCH_CALLLOGS_FAILURE = 'FETCH_CALLLOGS_FAILURE';
// Search Call Logs
export const SEARCH_CALLLOGS_REQUEST = 'SEARCH_CALLLOGS_REQUEST';
export const SEARCH_CALLLOGS_SUCCESS = 'SEARCH_CALLLOGS_SUCCESS';
export const SEARCH_CALLLOGS_FAILURE = 'SEARCH_CALLLOGS_FAILURE';
// Delete All Logs
export const DELETE_ALL_CALLLOGS_REQUEST = 'DELETE_ALL_CALLLOGS_REQUEST';
export const DELETE_ALL_CALLLOGS_SUCCESS = 'DELETE_ALL_CALLLOGS_SUCCESS';
export const DELETE_ALL_CALLLOGS_FAILURE = 'DELETE_ALL_CALLLOGS_FAILURE';
// Fetch call log status
export const FETCH_CALLLOG_STATUS_SUCCESS = 'FETCH_CALLLOG_STATUS_SUCCESS';
export const FETCH_CALLLOG_STATUS_FAILED = 'FETCH_CALLLOG_STATUS_FAILED';
export const FETCH_CALLLOG_STATUS_REQUEST = 'FETCH_CALLLOG_STATUS_REQUEST';
export const FETCH_CALLLOG_STATUS_RESET = 'FETCH_CALLLOG_STATUS_RESET';
// change call log status
export const CHANGE_CALLLOG_STATUS_SUCCESS = 'CHANGE_CALLLOG_STATUS_SUCCESS';
export const CHANGE_CALLLOG_STATUS_FAILED = 'CHANGE_CALLLOG_STATUS_FAILED';
export const CHANGE_CALLLOG_STATUS_REQUEST = 'CHANGE_CALLLOG_STATUS_REQUEST';
export const CHANGE_CALLLOG_STATUS_RESET = 'CHANGE_CALLLOG_STATUS_RESET';

View File

@@ -0,0 +1,226 @@
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import CallLogsList from '../components/callLogs/CallLogsList';
import {
fetchCallLogs,
searchCallLogs,
fetchCallLogStatus,
} from '../actions/callLogs';
import Dashboard from '../components/Dashboard';
import { Link } from 'react-router-dom';
import AlertPanel from '../components/basic/AlertPanel';
import ShouldRender from '../components/basic/ShouldRender';
class CallLogs extends React.Component {
constructor(props) {
super(props);
this.state = {
searchBox: null,
};
}
prevClicked = (skip, limit) => {
const { searchBox } = this.state;
const { fetchCallLogs, searchCallLogs } = this.props;
if (searchBox && searchBox !== '') {
searchCallLogs(
searchBox,
(skip || 0) > (limit || 10) ? skip - limit : 0,
10
);
} else {
fetchCallLogs((skip || 0) > (limit || 10) ? skip - limit : 0, 10);
}
};
nextClicked = (skip, limit) => {
const { searchBox } = this.state;
const { fetchCallLogs, searchCallLogs } = this.props;
if (searchBox && searchBox !== '') {
searchCallLogs(searchBox, skip + limit, 10);
} else {
fetchCallLogs(skip + limit, 10);
}
};
ready = () => {
this.props.fetchCallLogs();
this.props.fetchCallLogStatus();
};
onChange = e => {
const value = e.target.value;
const { searchCallLogs } = this.props;
this.setState({ searchBox: value });
searchCallLogs(value, 0, 10);
};
render() {
const { callLogStatus } = this.props;
return (
<Dashboard ready={this.ready}>
<div
id="fyipeCallLog"
onKeyDown={this.handleKeyBoard}
className="Box-root Margin-vertical--12"
>
<div>
<div>
<div className="db-BackboneViewContainer">
<div
className="customers-list-view react-view popover-container"
style={{
position: 'relative',
overflow: 'visible',
}}
></div>
<div className="bs-BIM">
<div className="Box-root Margin-bottom--12">
<div className="bs-ContentSection Card-root Card-shadow--medium">
<div className="Box-root">
<div className="ContentHeader Box-root Box-background--white Box-divider--surface-bottom-1 Flex-flex Flex-direction--column Padding-horizontal--20 Padding-vertical--16">
<div className="Box-root Flex-flex Flex-direction--row Flex-justifyContent--spaceBetween">
<div className="ContentHeader-center Box-root Flex-flex Flex-direction--column Flex-justifyContent--center">
<span className="ContentHeader-title Text-color--inherit Text-display--inline Text-fontSize--16 Text-fontWeight--medium Text-lineHeight--28 Text-typeface--base Text-wrap--wrap">
<span
style={{
textTransform:
'capitalize',
}}
>
Call Logs
</span>
</span>
<span className="ContentHeader-description Text-color--inherit Text-display--inline Text-fontSize--14 Text-fontWeight--regular Text-lineHeight--20 Text-typeface--base Text-wrap--wrap">
<span>
Here is a
complete
list of Call
logs.
</span>
</span>
</div>
{/* <div className="ContentHeader-end Box-root Flex-flex Flex-alignItems--center Margin-left--16">
<div className="Box-root">
<div className="ContentHeader-end Box-root Flex-flex Flex-alignItems--center Margin-left--16">
<div>
<input
id="searchCallLog"
className="db-BusinessSettings-input TextInput bs-TextInput"
placeholder="Search Logs"
onChange={
this
.onChange
}
/>
</div>
</div>
</div>
</div> */}
</div>
</div>
<div className="ContentHeader Box-root Box-background--white Box-divider--surface-bottom-1 Flex-flex Flex-direction--column">
<ShouldRender
if={
callLogStatus.data &&
!callLogStatus.data
.value
}
>
<AlertPanel
className=""
message={
<span id="callLogDisabled">
You are
currently
not storing
any call
logs at the
moment.
Click{' '}
<Link
className="Border-bottom--white Text-fontWeight--bold Text-color--white"
to="/admin/settings/call-logs"
id="callLogSetting"
>
here
</Link>{' '}
to turn it
on.
</span>
}
/>
</ShouldRender>
</div>
</div>
<CallLogsList
callLogs={
this.props.callLogs || {}
}
prevClicked={this.prevClicked}
nextClicked={this.nextClicked}
userId={this.props.userId}
requesting={
this.props.requesting
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</Dashboard>
);
}
}
CallLogs.displayName = 'CallLogs';
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
fetchCallLogs,
searchCallLogs,
fetchCallLogStatus,
},
dispatch
);
};
const mapStateToProps = state => {
const callLogs = state.callLogs.callLogs;
const searchCallLogs = state.callLogs.searchCallLogs;
const requesting =
callLogs && searchCallLogs
? callLogs.requesting || searchCallLogs.requesting
? true
: false
: false;
const callLogStatus = state.callLogs.callLogStatus;
const changeCallLogStatus = state.callLogs.changeCallLogStatus;
return {
callLogs,
requesting,
callLogStatus,
changeCallLogStatus,
};
};
CallLogs.propTypes = {
fetchCallLogs: PropTypes.func.isRequired,
searchCallLogs: PropTypes.func.isRequired,
requesting: PropTypes.bool,
callLogs: PropTypes.object,
userId: PropTypes.string,
fetchCallLogStatus: PropTypes.func.isRequired,
callLogStatus: PropTypes.object,
};
export default connect(mapStateToProps, mapDispatchToProps)(CallLogs);

View File

@@ -8,6 +8,7 @@ import Sso from '../components/settings/sso';
import SsoDefaultRoles from '../components/settings/ssoDefaultRoles';
import AuditLog from '../components/settings/auditLog';
import EmailLog from '../components/settings/emailLog';
import CallLog from '../components/settings/callLog';
import SmsLog from '../components/settings/smsLog';
// eslint-disable-next-line react/display-name
@@ -26,6 +27,8 @@ const getChild = key => {
);
case '/admin/settings/audit-logs':
return <AuditLog />;
case '/admin/settings/call-logs':
return <CallLog />;
case '/admin/settings/email-logs':
return <EmailLog />;
case '/admin/settings/sms-logs':

View File

@@ -5,6 +5,7 @@ import Project from './Project';
import Probes from './Probes';
import AuditLogs from './AuditLogs';
import EmailLogs from './EmailLogs';
import CallLogs from './CallLogs';
import SmsLogs from './SmsLogs';
import Settings from './Settings';
import License from './License';
@@ -18,6 +19,7 @@ export default {
AuditLogs,
EmailLogs,
SmsLogs,
CallLogs,
Settings,
License,
};

View File

@@ -0,0 +1,253 @@
import {
FETCH_CALLLOGS_REQUEST,
FETCH_CALLLOGS_SUCCESS,
FETCH_CALLLOGS_FAILURE,
SEARCH_CALLLOGS_REQUEST,
SEARCH_CALLLOGS_SUCCESS,
SEARCH_CALLLOGS_FAILURE,
DELETE_ALL_CALLLOGS_REQUEST,
DELETE_ALL_CALLLOGS_SUCCESS,
DELETE_ALL_CALLLOGS_FAILURE,
FETCH_CALLLOG_STATUS_FAILED,
FETCH_CALLLOG_STATUS_REQUEST,
FETCH_CALLLOG_STATUS_SUCCESS,
FETCH_CALLLOG_STATUS_RESET,
CHANGE_CALLLOG_STATUS_FAILED,
CHANGE_CALLLOG_STATUS_REQUEST,
CHANGE_CALLLOG_STATUS_RESET,
CHANGE_CALLLOG_STATUS_SUCCESS,
} from '../constants/callLogs';
const INITIAL_STATE = {
callLogs: {
error: null,
requesting: false,
success: false,
callLogs: [],
count: null,
limit: null,
skip: null,
deleteRequest: false,
deleted: false,
},
searchCallLogs: {
requesting: false,
error: null,
success: false,
},
callLogStatus: {
error: null,
requesting: false,
success: false,
data: null,
},
changeCallLogStatus: {
error: null,
requesting: false,
success: false,
},
};
export default function project(state = INITIAL_STATE, action) {
switch (action.type) {
// Fetch callLogs list
case FETCH_CALLLOGS_REQUEST:
return Object.assign({}, state, {
callLogs: {
requesting: true,
error: null,
success: false,
},
});
case FETCH_CALLLOGS_SUCCESS:
return Object.assign({}, state, {
callLogs: {
requesting: false,
error: null,
success: true,
callLogs: action.payload.data,
count: action.payload.count,
limit: action.payload.limit,
skip: action.payload.skip,
deleteRequest: false,
deleted: false,
},
});
case FETCH_CALLLOGS_FAILURE:
return Object.assign({}, state, {
callLogs: {
requesting: false,
error: action.payload,
success: false,
},
});
// Search CallLog list.
case SEARCH_CALLLOGS_REQUEST:
return Object.assign({}, state, {
searchCallLogs: {
requesting: true,
error: null,
success: false,
},
});
case SEARCH_CALLLOGS_SUCCESS:
return Object.assign({}, state, {
callLogs: {
requesting: false,
error: null,
success: true,
callLogs: action.payload.data,
count: action.payload.count,
limit: action.payload.limit,
skip: action.payload.skip,
deleteRequest: false,
deleted: true,
},
searchCallLogs: {
requesting: false,
error: null,
success: true,
},
});
case SEARCH_CALLLOGS_FAILURE:
return Object.assign({}, state, {
searchCallLogs: {
requesting: false,
error: action.payload,
success: false,
},
});
//Delete all Call logs
case DELETE_ALL_CALLLOGS_REQUEST:
return {
...state,
callLogs: {
...state.callLogs,
error: null,
success: false,
deleteRequest: true,
},
};
case DELETE_ALL_CALLLOGS_SUCCESS:
return Object.assign({}, state, {
callLogs: {
requesting: false,
error: null,
success: true,
deleteRequest: false,
deleted: true,
callLogs: [],
count: null,
limit: null,
skip: null,
},
});
case DELETE_ALL_CALLLOGS_FAILURE:
return {
...state,
callLogs: {
...state.callLogs,
error: action.payload,
success: false,
deleteRequest: false,
},
};
case FETCH_CALLLOG_STATUS_REQUEST:
return Object.assign({}, state, {
callLogStatus: {
error: null,
requesting: true,
success: false,
data: null,
},
});
case FETCH_CALLLOG_STATUS_SUCCESS: {
return Object.assign({}, state, {
callLogStatus: {
error: null,
requesting: false,
success: true,
data: {
...action.payload,
},
},
});
}
case FETCH_CALLLOG_STATUS_FAILED:
return Object.assign({}, state, {
callLogStatus: {
...state.callLogStatus,
error: action.payload,
requesting: false,
success: false,
},
});
case FETCH_CALLLOG_STATUS_RESET:
return Object.assign({}, state, {
callLogStatus: {
error: null,
requesting: false,
success: false,
data: null,
},
});
case CHANGE_CALLLOG_STATUS_REQUEST:
return Object.assign({}, state, {
changeCallLogStatus: {
error: null,
requesting: true,
success: false,
},
});
case CHANGE_CALLLOG_STATUS_SUCCESS: {
return Object.assign({}, state, {
callLogStatus: {
error: null,
requesting: false,
success: true,
data: {
...action.payload,
},
},
changeCallLogStatus: {
error: null,
requesting: false,
success: true,
},
});
}
case CHANGE_CALLLOG_STATUS_FAILED:
return Object.assign({}, state, {
changeCallLogStatus: {
error: action.payload,
requesting: false,
success: false,
},
});
case CHANGE_CALLLOG_STATUS_RESET:
return Object.assign({}, state, {
changeEmailLogStatus: {
error: null,
requesting: false,
success: false,
},
});
default:
return state;
}
}

View File

@@ -9,6 +9,7 @@ import project from './project';
import probe from './probe';
import auditLogs from './auditLogs';
import emailLogs from './emailLogs';
import callLogs from './callLogs';
import smsLogs from './smsLogs';
import settings from './settings';
import license from './license';
@@ -27,6 +28,7 @@ const appReducer = combineReducers({
project,
probe,
auditLogs,
callLogs,
emailLogs,
smsLogs,
settings,

View File

@@ -16,6 +16,7 @@ const {
License,
EmailLogs,
SmsLogs,
CallLogs,
} = pages;
export const groups = [
@@ -73,35 +74,63 @@ export const groups = [
index: 3,
shortcut: 'f+b',
},
],
},
{
group: 'Logs',
visible: true,
routes: [
{
title: 'Audit Logs',
path: '/admin/audit-logs',
icon: 'appLog',
component: AuditLogs,
title: 'Logs',
exact: true,
visible: true,
subRoutes: [],
index: 4,
shortcut: 'f+a',
},
{
title: 'Email Logs',
path: '/admin/email-logs',
icon: 'emailIcon',
component: EmailLogs,
visible: true,
subRoutes: [],
index: 5,
shortcut: 'f+e',
},
{
title: 'SMS Logs',
path: '/admin/sms-logs',
icon: 'smsIcon',
component: SmsLogs,
visible: true,
subRoutes: [],
index: 6,
shortcut: 'f+k',
path: '/admin/audit-logs',
icon: 'LogsIcon',
component: AuditLogs,
index: 1,
subRoutes: [
{
title: 'Audit Logs',
path: '/admin/audit-logs',
icon: 'appLog',
component: AuditLogs,
visible: true,
subRoutes: [],
index: 4,
shortcut: 'f+a',
},
{
title: 'Call Logs',
path: '/admin/call-logs',
icon: 'emailIcon',
component: CallLogs,
visible: true,
subRoutes: [],
index: 5,
shortcut: 'f+w',
},
{
title: 'Email Logs',
path: '/admin/email-logs',
icon: 'emailIcon',
component: EmailLogs,
visible: true,
subRoutes: [],
index: 6,
shortcut: 'f+e',
},
{
title: 'SMS Logs',
path: '/admin/sms-logs',
icon: 'smsIcon',
component: SmsLogs,
visible: true,
subRoutes: [],
index: 7,
shortcut: 'f+k',
},
],
},
],
},
@@ -174,6 +203,16 @@ export const groups = [
index: 5,
shortcut: 'f+g',
},
{
title: 'Call Log',
path: '/admin/settings/call-logs',
icon: 'emailIcon',
component: Settings,
visible: true,
subRoutes: [],
index: 6,
shortcut: 'f+h',
},
{
title: 'Email Log',
path: '/admin/settings/email-logs',
@@ -181,7 +220,7 @@ export const groups = [
component: Settings,
visible: true,
subRoutes: [],
index: 6,
index: 7,
shortcut: 'f+i',
},
{
@@ -191,7 +230,7 @@ export const groups = [
component: Settings,
visible: true,
subRoutes: [],
index: 7,
index: 8,
shortcut: 'f+c',
},
],

View File

@@ -107,11 +107,18 @@ describe('About Modal (IS_SAAS_SERVICE=false)', () => {
'#admin-dashboard-version',
elem => elem.textContent
);
const probeVersion = await page.$eval(
'#probe-version',
elem => elem.textContent
);
expect(serverVersion).toBeDefined();
expect(docsVersion).toBeDefined();
expect(helmVersion).toBeDefined();
expect(dashboardVersion).toBeDefined();
expect(adminDashboardVersion).toBeDefined();
expect(probeVersion).toBeDefined();
});
},
operationTimeOut

View File

@@ -32,6 +32,7 @@ router.post('/:projectId', getUser, isAuthorized, async function(req, res) {
alertVia: data.alertVia,
userId: userId,
incidentId: data.incidentId,
eventType: data.eventType,
});
return sendItemResponse(req, res, alert);
} catch (error) {

View File

@@ -0,0 +1,110 @@
/**
*
* Copyright HackerBay, Inc.
*
*/
const express = require('express');
const router = express.Router();
const CallLogsService = require('../services/callLogsService');
const getUser = require('../middlewares/user').getUser;
const isUserMasterAdmin = require('../middlewares/user').isUserMasterAdmin;
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const sendListResponse = require('../middlewares/response').sendListResponse;
const { sendItemResponse } = require('../middlewares/response');
router.get('/', getUser, isUserMasterAdmin, async function(req, res) {
try {
const query = {};
const skip = req.query.skip;
const limit = req.query.limit;
const callLogs = await CallLogsService.findBy(query, limit, skip);
const count = await CallLogsService.countBy(query);
return sendListResponse(req, res, callLogs, count);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.post('/', getUser, isUserMasterAdmin, async (req, res) => {
try {
const data = req.body;
if (!data) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Values should not be null',
});
}
if (!data.status || !data.status.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Call Log Status is required',
});
}
if (!data.from || !data.from.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Call Log Sender name is required',
});
}
if (!data.to || !data.to.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Call Log Recipient name is required',
});
}
if (!data.projectId || !data.projectId.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Call Log ProjectId is required',
});
}
if (!data.content || !data.content.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Call Log Content is required',
});
}
const callLog = await CallLogsService.create(data);
return sendItemResponse(req, res, callLog);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.post('/search', getUser, isUserMasterAdmin, async function(req, res) {
try {
const filter = req.body.filter;
const skip = req.query.skip;
const limit = req.query.limit;
const {
searchedCallLogs,
totalSearchCount,
} = await CallLogsService.search({ filter, skip, limit });
return sendListResponse(req, res, searchedCallLogs, totalSearchCount);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.delete('/', getUser, isUserMasterAdmin, async function(req, res) {
try {
const query = {};
const msg = await CallLogsService.hardDeleteBy({ query });
return sendItemResponse(req, res, msg);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
module.exports = router;

View File

@@ -17,6 +17,7 @@ const LogService = require('../services/logService');
const ApplicationSecurityLogService = require('../services/applicationSecurityLogService');
const ContainerSecurityLogService = require('../services/containerSecurityLogService');
const ErrorTrackerService = require('../services/errorTrackerService');
const IssueService = require('../services/issueService');
const router = express.Router();
const isUserAdmin = require('../middlewares/project').isUserAdmin;
@@ -329,6 +330,36 @@ router.get(
return newElement;
})
);
// fetch error trackers
const errorTrackers = await ErrorTrackerService.getErrorTrackersByComponentId(
componentId,
limit,
skip
);
await Promise.all(
errorTrackers.map(async errorTracker => {
let errorStatus = 'No Errors yet';
const issues = await IssueService.findBy(
{ errorTrackerId: errorTracker._id },
1,
0
);
if (issues.length > 0) errorStatus = 'Listening for Errors';
const newElement = {
_id: errorTracker._id,
name: errorTracker.name,
type: 'error tracker',
createdAt: errorTracker.createdAt,
icon: 'errorTracking',
status: errorStatus,
};
// add it to the total resources
totalResources.push(newElement);
return newElement;
})
);
// return response
return sendItemResponse(req, res, {
totalResources,

View File

@@ -285,13 +285,38 @@ router.post('/:errorTrackerId/track', isErrorTrackerValid, async function(
// if it doesnt exist, create the issue and use its details
if (!issue) {
issue = await IssueService.create(data);
} else {
// issue exist but checked if it is resolved so to uresolve it
if (issue.resolved) {
const updateData = {
resolved: false,
resolvedAt: '',
resolvedById: null,
};
const query = {
_id: issue._id,
errorTrackerId,
};
await IssueService.updateOneBy(query, updateData);
}
}
// if it exist, use the issue details
// since it now exist, use the issue details
data.issueId = issue._id;
data.fingerprintHash = issue.fingerprintHash;
// create the error event
const errorEvent = await ErrorEventService.create(data);
await RealTimeService.sendErrorEventCreated(errorEvent);
// get the issue in the format that the fronnted will want for the real time update
const errorTrackerIssue = await ErrorEventService.findDistinct(
{ _id: data.issueId, errorTrackerId: data.errorTrackerId },
1,
0
);
issue = errorTrackerIssue.totalErrorEvents[0];
await RealTimeService.sendErrorEventCreated({ errorEvent, issue });
return sendItemResponse(req, res, errorEvent);
} catch (error) {
return sendErrorResponse(req, res, error);

View File

@@ -53,9 +53,10 @@ router.post('/', getUser, isUserMasterAdmin, async function(req, res) {
!value &&
name !== 'auditLogMonitoringStatus' &&
name !== 'emailLogMonitoringStatus' &&
name !== 'smsLogMonitoringStatus'
name !== 'smsLogMonitoringStatus' &&
name !== 'callLogMonitoringStatus'
) {
// Audit or Email or SMS Log Status can be 'false'
// Audit or Email or SMS or Call Log Status can be 'false'
return sendErrorResponse(req, res, {
code: 400,
message: 'Value must be present.',
@@ -157,6 +158,15 @@ router.get('/:name', getUser, isUserMasterAdmin, async function(req, res) {
};
await GlobalConfigService.create(smsLogConfig);
}
// If Call logs status was fetched and it doesnt exist, we need to create it
if (!globalConfig && req.params.name === 'callLogMonitoringStatus') {
const callLogConfig = {
name: 'callLogMonitoringStatus',
value: true,
};
await GlobalConfigService.create(callLogConfig);
}
globalConfig = await GlobalConfigService.findOneBy({
name: req.params.name,
});

View File

@@ -25,6 +25,7 @@ const getSubProjects = require('../middlewares/subProject').getSubProjects;
const sendErrorResponse = require('../middlewares/response').sendErrorResponse;
const sendListResponse = require('../middlewares/response').sendListResponse;
const sendItemResponse = require('../middlewares/response').sendItemResponse;
const subscriberAlertService = require('../services/subscriberAlertService');
// Route
// Description: Creating incident.
@@ -284,13 +285,49 @@ router.post(
async function(req, res) {
try {
const userId = req.user ? req.user.id : null;
const projectId = req.params.projectId;
// Call the IncidentService
const incident = await IncidentService.acknowledge(
req.params.incidentId,
userId,
req.user.name
);
return sendItemResponse(req, res, incident);
let incidentMessages = await IncidentMessageService.findBy({
incidentId: incident._id,
type: 'internal',
});
const timeline = await IncidentTimelineService.findBy({
incidentId: incident._id,
});
const alerts = await AlertService.findBy({
query: { incidentId: incident._id },
});
const subscriberAlerts = await subscriberAlertService.findBy({
incidentId: incident._id,
projectId,
});
const subAlerts = uniqByKeepFirst(
subscriberAlerts,
it => it.identification
);
incidentMessages = [
...incidentMessages,
...timeline,
...alerts,
...subAlerts,
];
incidentMessages.sort((a, b) => b.createdAt - a.createdAt);
const filteredMsg = incidentMessages.filter(
a =>
a.status !== 'internal notes added' &&
a.status !== 'internal notes updated'
);
const result = {
data: filteredMsg,
incident,
type: 'internal',
};
return sendItemResponse(req, res, result);
} catch (error) {
return sendErrorResponse(req, res, error);
}
@@ -309,13 +346,49 @@ router.post(
async function(req, res) {
try {
const userId = req.user ? req.user.id : null;
const projectId = req.params.projectId;
// Call the IncidentService
const incident = await IncidentService.resolve(
req.params.incidentId,
userId
);
let incidentMessages = await IncidentMessageService.findBy({
incidentId: incident._id,
type: 'internal',
});
const timeline = await IncidentTimelineService.findBy({
incidentId: incident._id,
});
const alerts = await AlertService.findBy({
query: { incidentId: incident._id },
});
const subscriberAlerts = await subscriberAlertService.findBy({
incidentId: incident._id,
projectId,
});
const subAlerts = uniqByKeepFirst(
subscriberAlerts,
it => it.identification
);
incidentMessages = [
...incidentMessages,
...timeline,
...alerts,
...subAlerts,
];
incidentMessages.sort((a, b) => b.createdAt - a.createdAt);
const filteredMsg = incidentMessages.filter(
a =>
a.status !== 'internal notes added' &&
a.status !== 'internal notes updated'
);
const result = {
data: filteredMsg,
incident,
type: 'internal',
};
return sendItemResponse(req, res, incident);
return sendItemResponse(req, res, result);
} catch (error) {
return sendErrorResponse(req, res, error);
}
@@ -530,10 +603,52 @@ router.post(
status,
});
incidentMessage = await IncidentMessageService.findOneBy({
_id: incidentMessage._id,
incidentId: incidentMessage.incidentId,
const alerts = await AlertService.findBy({
query: { incidentId: incident._id },
});
const subscriberAlerts = await subscriberAlertService.findBy({
incidentId: incident._id,
projectId: req.params.projectId,
});
if (
data.type === 'internal' ||
(data.type === 'internal' &&
data.incident_state === 'update')
) {
let incidentMessages = await IncidentMessageService.findBy({
incidentId: incident._id,
type: data.type,
});
const timeline = await IncidentTimelineService.findBy({
incidentId: incident._id,
});
const subAlerts = uniqByKeepFirst(
subscriberAlerts,
it => it.identification
);
incidentMessages = [
...incidentMessages,
...timeline,
...alerts,
...subAlerts,
];
incidentMessages.sort((a, b) => b.createdAt - a.createdAt);
const filteredMsg = incidentMessages.filter(
a =>
a.status !== 'internal notes added' &&
a.status !== 'internal notes updated'
);
incidentMessage = {
type: data.type,
data: filteredMsg,
};
} else {
incidentMessage = await IncidentMessageService.findOneBy({
_id: incidentMessage._id,
incidentId: incidentMessage.incidentId,
});
}
}
return sendItemResponse(req, res, incidentMessage);
} catch (error) {
@@ -571,7 +686,11 @@ router.delete(
isAuthorized,
async function(req, res) {
try {
const { incidentId, incidentMessageId } = req.params;
const { incidentId, incidentMessageId, projectId } = req.params;
const checkMsg = await IncidentMessageService.findOneBy({
_id: incidentMessageId,
});
let result;
const incidentMessage = await IncidentMessageService.deleteBy(
{
_id: incidentMessageId,
@@ -587,9 +706,47 @@ router.delete(
createdById: req.user.id,
status,
});
const alerts = await AlertService.findBy({
query: { incidentId: incidentId },
});
const subscriberAlerts = await subscriberAlertService.findBy({
incidentId: incidentId,
projectId,
});
await RealTimeService.deleteIncidentNote(incidentMessage);
return sendItemResponse(req, res, incidentMessage);
if (checkMsg.type === 'investigation') {
result = incidentMessage;
} else {
let incidentMessages = await IncidentMessageService.findBy({
incidentId,
type: checkMsg.type,
});
const timeline = await IncidentTimelineService.findBy({
incidentId,
});
const subAlerts = uniqByKeepFirst(
subscriberAlerts,
it => it.identification
);
incidentMessages = [
...incidentMessages,
...timeline,
...alerts,
...subAlerts,
];
incidentMessages.sort((a, b) => b.createdAt - a.createdAt);
const filteredMsg = incidentMessages.filter(
a =>
a.status !== 'internal notes added' &&
a.status !== 'internal notes updated'
);
result = {
type: checkMsg.type,
data: filteredMsg,
};
}
return sendItemResponse(req, res, result);
} else {
return sendErrorResponse(req, res, {
code: 404,
@@ -611,17 +768,57 @@ router.get(
type = 'internal';
}
try {
let incidentMessages, result;
const incidentId = req.params.incidentId;
const incidentMessages = await IncidentMessageService.findBy(
{ incidentId, type },
req.query.skip || 0,
req.query.limit || 10
);
const projectId = req.params.projectId;
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,
});
const count = await IncidentMessageService.countBy({
incidentId,
type,
});
return sendListResponse(req, res, incidentMessages, count);
if (type === 'investigation') {
result = incidentMessages;
} else {
const subAlerts = uniqByKeepFirst(
subscriberAlerts,
it => it.identification
);
incidentMessages = [
...incidentMessages,
...timeline,
...alerts,
...subAlerts,
];
incidentMessages.sort((a, b) => b.createdAt - a.createdAt);
const filteredMsg = incidentMessages.filter(
a =>
a.status !== 'internal notes added' &&
a.status !== 'internal notes updated'
);
result = filteredMsg;
}
return sendListResponse(req, res, result, count);
} catch (error) {
return sendErrorResponse(req, res, error);
}
@@ -709,4 +906,12 @@ router.get(
}
);
function uniqByKeepFirst(a, key) {
const seen = new Set();
return a.filter(item => {
const k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
module.exports = router;

View File

@@ -143,11 +143,6 @@ router.delete(
// process incoming http request from post request
router.post('/:projectId/request/:requestId', async function(req, res) {
try {
// target value key on request body, request query or request headers
// more may be added in the future
const externalFilter =
req.body.value || req.query.value || req.headers.value;
// request object for use in variables
const request = {
body: { ...req.body },
@@ -156,7 +151,7 @@ router.post('/:projectId/request/:requestId', async function(req, res) {
};
const { projectId, requestId } = req.params;
const data = { projectId, requestId, filter: externalFilter, request };
const data = { projectId, requestId, request };
const response = await IncomingRequestService.handleIncomingRequestAction(
data
@@ -170,20 +165,15 @@ router.post('/:projectId/request/:requestId', async function(req, res) {
// process incoming http request from get request
router.get('/:projectId/request/:requestId', async function(req, res) {
try {
// target value key on request body, request query or request headers
// more may be added in the future
// request body won't be available on get request
const externalFilter = req.query.value || req.headers.value;
// request object for use in variables
// request body won't be available for a get request
const request = {
body: { ...req.body },
query: { ...req.query },
headers: { ...req.headers },
};
const { projectId, requestId } = req.params;
const data = { projectId, requestId, filter: externalFilter, request };
const data = { projectId, requestId, request };
const response = await IncomingRequestService.handleIncomingRequestAction(
data

View File

@@ -542,53 +542,68 @@ router.post(
const {
stat: validUp,
reasons: upFailedReasons,
successReasons: upSuccessReasons,
failedReasons: upFailedReasons,
} = await (monitor && monitor.criteria && monitor.criteria.up
? ProbeService.conditions(
monitor.type,
monitor.criteria.up,
data
)
: { stat: false, reasons: [] });
: { stat: false, failedReasons: [], successReasons: [] });
const {
stat: validDegraded,
reasons: degradedFailedReasons,
successReasons: degradedSuccessReasons,
failedReasons: degradedFailedReasons,
} = await (monitor && monitor.criteria && monitor.criteria.degraded
? ProbeService.conditions(
monitor.type,
monitor.criteria.degraded,
data
)
: { stat: false, reasons: [] });
: { stat: false, failedReasons: [], successReasons: [] });
const {
stat: validDown,
reasons: downFailedReasons,
successReasons: downSuccessReasons,
failedReasons: downFailedReasons,
} = await (monitor && monitor.criteria && monitor.criteria.down
? ProbeService.conditions(
monitor.type,
monitor.criteria.down,
data
)
: { stat: false, reasons: [] });
: { stat: false, failedReasons: [], successReasons: [] });
if (validDown) {
data.status = 'offline';
data.reason = downFailedReasons;
if (validUp) {
data.status = 'online';
data.reason = upSuccessReasons;
} else if (validDegraded) {
data.status = 'degraded';
data.reason = degradedFailedReasons;
} else if (validUp) {
data.status = 'online';
data.reason = upFailedReasons;
data.reason = [...degradedSuccessReasons, ...upFailedReasons];
} else if (validDown) {
data.status = 'offline';
data.reason = [
...downSuccessReasons,
...degradedFailedReasons,
...upFailedReasons,
];
} else {
data.status = 'offline';
data.reason = [
...degradedFailedReasons,
...downFailedReasons,
...degradedFailedReasons,
...upFailedReasons,
];
}
const index = data.reason.indexOf('Request Timed out');
if (index > -1) {
data.reason = data.reason.filter(
item => !item.includes('Response Time is')
);
}
data.reason = data.reason.filter(
(item, pos, self) => self.indexOf(item) === pos
);
const log = await ProbeService.saveMonitorLog(data);
return sendItemResponse(req, res, log);

View File

@@ -8,6 +8,7 @@ const express = require('express');
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();
@@ -116,7 +117,6 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
log,
reason,
data = {};
if (type === 'incomingHttpRequest') {
const newMonitor = await MonitorService.findOneBy({
_id: monitor._id,
@@ -127,7 +127,8 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
if (type === 'api' || type === 'url') {
const {
stat: validUp,
reasons: upFailedReasons,
successReasons: upSuccessReasons,
failedReasons: upFailedReasons,
} = await (monitor && monitor.criteria && monitor.criteria.up
? ProbeService.conditions(
monitor.type,
@@ -136,10 +137,11 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
resp,
rawResp
)
: { stat: false, reasons: [] });
: { stat: false, successReasons: [], failedReasons: [] });
const {
stat: validDegraded,
reasons: degradedFailedReasons,
successReasons: degradedSuccessReasons,
failedReasons: degradedFailedReasons,
} = await (monitor &&
monitor.criteria &&
monitor.criteria.degraded
@@ -150,10 +152,11 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
resp,
rawResp
)
: { stat: false, reasons: [] });
: { stat: false, successReasons: [], failedReasons: [] });
const {
stat: validDown,
reasons: downFailedReasons,
successReasons: downSuccessReasons,
failedReasons: downFailedReasons,
} = await (monitor && monitor.criteria && monitor.criteria.down
? ProbeService.conditions(
monitor.type,
@@ -162,33 +165,36 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
resp,
rawResp
)
: { stat: false, reasons: [] });
: { stat: false, successReasons: [], failedReasons: [] });
if (validDown) {
status = 'offline';
reason = downFailedReasons;
if (validUp) {
status = 'online';
reason = upSuccessReasons;
} else if (validDegraded) {
status = 'degraded';
reason = degradedFailedReasons;
} else if (validUp) {
status = 'online';
reason = upFailedReasons;
reason = [...degradedSuccessReasons, ...upFailedReasons];
} else if (validDown) {
status = 'offline';
reason = [
...downSuccessReasons,
...degradedFailedReasons,
...upFailedReasons,
];
} else {
status = 'offline';
reason = [
...degradedFailedReasons,
...downFailedReasons,
...degradedFailedReasons,
...upFailedReasons,
];
}
data.status = status;
data.reason = reason;
}
if (type === 'script') {
const {
stat: validUp,
reasons: upFailedReasons,
reasons: upSuccessReasons,
} = await (monitor && monitor.criteria && monitor.criteria.up
? ProbeService.scriptConditions(
res,
@@ -198,7 +204,7 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
: { stat: false, reasons: [] });
const {
stat: validDegraded,
reasons: degradedFailedReasons,
reasons: degradedSuccessReasons,
} = await (monitor &&
monitor.criteria &&
monitor.criteria.degraded
@@ -210,7 +216,7 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
: { stat: false, reasons: [] });
const {
stat: validDown,
reasons: downFailedReasons,
reasons: downSuccessReasons,
} = await (monitor && monitor.criteria && monitor.criteria.down
? ProbeService.scriptConditions(
res,
@@ -221,19 +227,18 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
if (validDown) {
status = 'failed';
reason = upFailedReasons;
reason = upSuccessReasons;
} else if (validDegraded) {
status = 'degraded';
reason = upFailedReasons;
reason = upSuccessReasons;
} else if (validUp) {
status = 'success';
reason = [...degradedFailedReasons, ...downFailedReasons];
reason = [...degradedSuccessReasons, ...downSuccessReasons];
} else {
status = 'failed';
reason = upFailedReasons;
reason = upSuccessReasons;
}
resp.status = null;
data.status = status;
data.reason = reason;
}
@@ -246,20 +251,21 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
}
if (type === 'server-monitor') {
data = serverData;
const {
stat: validUp,
reasons: upFailedReasons,
successReasons: upSuccessReasons,
failedReasons: upFailedReasons,
} = await (monitor && monitor.criteria && monitor.criteria.up
? ProbeService.conditions(
monitor.type,
monitor.criteria.up,
data
)
: { stat: false, reasons: [] });
: { stat: false, successReasons: [], failedReasons: [] });
const {
stat: validDegraded,
reasons: degradedFailedReasons,
successReasons: degradedSuccessReasons,
failedReasons: degradedFailedReasons,
} = await (monitor &&
monitor.criteria &&
monitor.criteria.degraded
@@ -268,32 +274,40 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
monitor.criteria.degraded,
data
)
: { stat: false, reasons: [] });
: { stat: false, successReasons: [], failedReasons: [] });
const {
stat: validDown,
reasons: downFailedReasons,
successReasons: downSuccessReasons,
failedReasons: downFailedReasons,
} = await (monitor && monitor.criteria && monitor.criteria.down
? ProbeService.conditions(
monitor.type,
monitor.criteria.down,
data
)
: { stat: false, reasons: [] });
: { stat: false, successReasons: [], failedReasons: [] });
if (validDown) {
data.status = 'offline';
data.reason = downFailedReasons;
if (validUp) {
data.status = 'online';
data.reason = upSuccessReasons;
} else if (validDegraded) {
data.status = 'degraded';
data.reason = degradedFailedReasons;
} else if (validUp) {
data.status = 'online';
data.reason = upFailedReasons;
data.reason = [
...degradedSuccessReasons,
...upFailedReasons,
];
} else if (validDown) {
data.status = 'offline';
data.reason = [
...downSuccessReasons,
...degradedFailedReasons,
...upFailedReasons,
];
} else {
data.status = 'offline';
data.reason = [
...degradedFailedReasons,
...downFailedReasons,
...degradedFailedReasons,
...upFailedReasons,
];
}
@@ -325,14 +339,35 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
data.monitorId = req.params.monitorId || monitor._id;
data.probeId = req.probe && req.probe.id ? req.probe.id : null;
data.reason =
data && data.reason && data.reason.length
? data.reason.filter(
(item, pos, self) => self.indexOf(item) === pos
)
: data.reason;
const index =
data.reason && data.reason.indexOf('Request Timed out');
if (index > -1) {
data.reason =
data && data.reason && data.reason.length
? data.reason.filter(
item => !item.includes('Response Time is')
)
: data.reason;
}
if (data.lighthouseScanStatus) {
if (data.lighthouseScanStatus === 'scanning') {
await MonitorService.updateOneBy(
{ _id: data.monitorId },
{
lighthouseScanStatus: data.lighthouseScanStatus,
}
},
{ fetchLightHouse: true }
);
await LighthouseLogService.updateAllLighthouseLogs(
data.monitor.projectId,
data.monitorId,
{ scanning: true }
);
} else {
await MonitorService.updateOneBy(
@@ -346,6 +381,7 @@ router.post('/ping/:monitorId', isAuthorizedProbe, async function(
}
} else {
if (data.lighthouseData) {
data.scanning = false;
log = await ProbeService.saveLighthouseLog(data);
} else {
log = await ProbeService.saveMonitorLog(data);

View File

@@ -20,8 +20,7 @@ router.get('/', getUser, isUserMasterAdmin, async function(req, res) {
const query = {};
const skip = req.query.skip;
const limit = req.query.limit;
const smsLogs = await SmsLogsService.findBy(query, skip, limit);
const smsLogs = await SmsLogsService.findBy(query, limit, skip);
const count = await SmsLogsService.countBy(query);
return sendListResponse(req, res, smsLogs, count);
} catch (error) {
@@ -29,6 +28,56 @@ router.get('/', getUser, isUserMasterAdmin, async function(req, res) {
}
});
router.post('/', getUser, isUserMasterAdmin, async (req, res) => {
try {
const data = req.body;
if (!data) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Values should not be null',
});
}
if (!data.status || !data.status.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'SMS Log Status is required',
});
}
if (!data.userId || !data.userId.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'SMS Log UserId is required',
});
}
if (!data.sentTo || !data.sendTo.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'Email Log Recipient name is required',
});
}
if (!data.projectId || !data.projectId.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'SMS Log ProjectId is required',
});
}
if (!data.content || !data.content.trim()) {
return sendErrorResponse(req, res, {
code: 400,
message: 'SMS Log Content is required',
});
}
const smsLog = await SmsLogsService.create(data);
return sendItemResponse(req, res, smsLog);
} catch (error) {
return sendErrorResponse(req, res, error);
}
});
router.post('/search', getUser, isUserMasterAdmin, async function(req, res) {
try {
const filter = req.body.filter;

View File

@@ -527,6 +527,7 @@ router.post('/totp/verifyToken', async function(req, res) {
},
tempEmail: user.tempEmail || null,
tempAlertPhoneNumber: user.tempAlertPhoneNumber || null,
role: user.role || null,
};
return sendItemResponse(req, res, userObj);
} catch (error) {
@@ -607,6 +608,7 @@ router.post('/verify/backupCode', async function(req, res) {
},
tempEmail: user.tempEmail || null,
tempAlertPhoneNumber: user.tempAlertPhoneNumber || null,
role: user.role || null,
};
return sendItemResponse(req, res, userObj);
} catch (error) {
@@ -1066,6 +1068,7 @@ router.get('/profile', getUser, async function(req, res) {
},
tempEmail: user.tempEmail || null,
tempAlertPhoneNumber: user.tempAlertPhoneNumber || null,
role: user.role || null,
};
return sendItemResponse(req, res, userObj);
} catch (error) {

View File

@@ -11,6 +11,8 @@ module.exports = [
'{{projectId}} : Unique identifier for the current project.',
"{{trackEmailAsViewedUrl}} : Include this in your email to track emails and know when they're opened.",
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
emailType: 'Subscriber Incident Created',
subject:
@@ -553,6 +555,8 @@ span.st-Delink.st-Delink--footer a {
'{{projectId}} : Unique identifier for the current project.',
"{{trackEmailAsViewedUrl}} : Include this in your email to track emails and know when they're opened.",
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
emailType: 'Subscriber Incident Acknowldeged',
subject:
@@ -1095,6 +1099,8 @@ body[override] table.st-Button td.st-Button-area span.st-Button-internal{
"{{trackEmailAsViewedUrl}} : Include this in your email to track emails and know when they're opened.",
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incidentNoteStatus}} : Status of the incident note',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
emailType: 'Investigation note is created',
subject: 'An update on an active incident for {{monitorName}}',
@@ -1653,6 +1659,8 @@ body[override] table.st-Button td.st-Button-area span.st-Button-internal{
'{{projectId}} : Unique identifier for the current project.',
"{{trackEmailAsViewedUrl}} : Include this in your email to track emails and know when they're opened.",
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
emailType: 'Subscriber Incident Resolved',
subject:

View File

@@ -9,6 +9,8 @@ module.exports = {
'{{projectId}} : Unique identifier for the current project.',
"{{trackEmailAsViewedUrl}} : Include this in your email to track emails and know when they're opened.",
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
'Subscriber Incident Acknowldeged': [
'{{userName}} : User display name.',
@@ -20,6 +22,8 @@ module.exports = {
'{{projectId}} : Unique identifier for the current project.',
"{{trackEmailAsViewedUrl}} : Include this in your email to track emails and know when they're opened.",
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
'Subscriber Incident Resolved': [
'{{userName}} : User display name.',
@@ -31,6 +35,8 @@ module.exports = {
'{{projectId}} : Unique identifier for the current project.',
"{{trackEmailAsViewedUrl}} : Include this in your email to track emails and know when they're opened.",
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
'Team Member Incident': [
'{{ incidentTime }} : Time at which this incident occured.',

View File

@@ -7,6 +7,8 @@ module.exports = [
'{{incidentType}} : Type of incident. Online, offline or degraded.',
'{{componentName}} : Name of the component the monitor belongs to',
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
smsType: 'Subscriber Incident Created',
body:
@@ -20,6 +22,8 @@ module.exports = [
'{{incidentType}} : Type of incident. Online, offline or degraded.',
'{{componentName}} : Name of the component the monitor belongs to',
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
smsType: 'Subscriber Incident Acknowldeged',
body:
@@ -33,6 +37,8 @@ module.exports = [
'{{incidentType}} : Type of incident. Online, offline or degraded.',
'{{componentName}} : Name of the component the monitor belongs to',
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
smsType: 'Subscriber Incident Resolved',
body:
@@ -46,6 +52,8 @@ module.exports = [
'{{incidentType}} : Type of incident. Online, offline or degraded.',
'{{componentName}} : Name of the component the monitor belongs to',
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
smsType: 'Investigation note is created',
body:

View File

@@ -6,6 +6,8 @@ module.exports = {
'{{incidentType}} : Type of incident.',
'{{componentName}} : Name of the component the monitor belongs to',
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
'Subscriber Incident Acknowldeged': [
'{{incidentTime}} : Time at which this incident occured.',
@@ -14,6 +16,8 @@ module.exports = {
'{{incidentType}} : Type of incident.',
'{{componentName}} : Name of the component the monitor belongs to',
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
'Subscriber Incident Resolved': [
'{{incidentTime}} : Time at which this incident occured.',
@@ -22,5 +26,7 @@ module.exports = {
'{{incidentType}} : Type of incident.',
'{{componentName}} : Name of the component the monitor belongs to',
'{{statusPageUrl}} : URL of the Status Page your subscriber can go to. ',
'{{incident.customFields.*}} : The value of any incident custom field',
'{{monitor.customFields.*}} : The value of any monitor custom field',
],
};

View File

@@ -131,6 +131,16 @@ module.exports = {
return statusPageId;
},
getStatusPageUrl: function(req) {
const statusPageUrl =
req.params.url ||
req.query.url ||
req.headers['url'] ||
req.body.url;
return statusPageUrl;
},
isValidMonitor: async function(req, res, next) {
const id = req.params.id;
let monitor = await MonitorService.findBy({

View File

@@ -6,13 +6,22 @@ const ipaddr = require('ipaddr.js');
const _this = {
ipWhitelist: async function(req, res, next) {
const statusPageId = apiMiddleware.getStatusPageId(req);
const statusPage = await StatusPageService.findOneBy({
_id: statusPageId,
});
const statusPageUrl = apiMiddleware.getStatusPageUrl(req);
let statusPage;
if (statusPageId && statusPageId.length && statusPageId !== 'null') {
statusPage = await StatusPageService.findOneBy({
_id: statusPageId,
});
} else {
statusPage = await StatusPageService.findOneBy({
domains: { $elemMatch: { domain: statusPageUrl } },
});
}
if (!statusPage.enableIpWhitelist) {
return next();
}
const ipWhitelist = statusPage.ipWhitelist
? [...statusPage.ipWhitelist]
: [];

View File

@@ -10,7 +10,7 @@ const CLUSTER_KEY = process.env.CLUSTER_KEY;
module.exports = {
isAuthorizedProbe: async function(req, res, next) {
try {
let probeKey, probeName, clusterKey;
let probeKey, probeName, clusterKey, probeVersion;
if (req.params.probeKey) {
probeKey = req.params.probeKey;
@@ -58,6 +58,18 @@ module.exports = {
clusterKey = req.body.clusterKey;
}
if (req.params.probeVersion) {
probeVersion = req.params.probeVersion;
} else if (req.query.probeVersion) {
probeVersion = req.query.probeVersion;
} else if (req.headers['probeversion']) {
probeVersion = req.headers['probeversion'];
} else if (req.headers['probeversion']) {
probeVersion = req.headers['probeversion'];
} else if (req.body.probeVersion) {
probeVersion = req.body.probeVersion;
}
let probe = null;
if (clusterKey && clusterKey === CLUSTER_KEY) {
@@ -81,6 +93,7 @@ module.exports = {
probe = await ProbeService.create({
probeKey,
probeName,
probeVersion,
});
}
@@ -93,10 +106,25 @@ module.exports = {
{ probeKey }
);
}
req.probe = {};
req.probe.id = probe._id;
await ProbeService.updateProbeStatus(probe._id);
//Update probe version
const probeValue = await ProbeService.findOneBy({
probeKey,
probeName,
});
if (!probeValue.version || probeValue.version !== probeVersion) {
await ProbeService.updateOneBy(
{
probeName,
},
{ version: probeVersion }
);
}
next();
} catch (error) {
ErrorService.log('probeAuthorization.isAuthorizedProbe', error);

View File

@@ -0,0 +1,19 @@
const mongoose = require('../config/db');
const Schema = mongoose.Schema;
const callLogsSchema = new Schema({
from: String,
to: String,
projectId: { type: String, ref: 'Project' },
createdAt: { type: Date, default: Date.now },
deleted: { type: Boolean, default: false },
deletedAt: {
type: Date,
},
deletedById: { type: String, ref: 'User' },
content: String,
status: String,
error: String,
});
module.exports = mongoose.model('callLogs', callLogsSchema);

View File

@@ -16,8 +16,8 @@ const schema = new Schema({
template: String,
status: String,
content: String,
error: String,
deleted: { type: Boolean, default: false },
deletedAt: {
type: Date,
},

View File

@@ -22,19 +22,6 @@ const incomingRequestSchema = new Schema(
url: String,
deleted: { type: Boolean, default: false },
deletedAt: Date,
filterCriteria: String,
filterCondition: {
type: String,
enum: [
'equalTo',
'notEqualTo',
'lessThan',
'greaterThan',
'greaterThanOrEqualTo',
'lessThanOrEqualTo',
],
},
filterText: Schema.Types.Mixed, // expected to store both string and number
incidentTitle: String,
incidentType: { type: String },
incidentPriority: {
@@ -43,6 +30,24 @@ const incomingRequestSchema = new Schema(
},
incidentDescription: String,
customFields: [{ fieldName: String, fieldValue: Schema.Types.Mixed }],
filterMatch: String,
filters: [
{
filterCriteria: String,
filterCondition: {
type: String,
enum: [
'equalTo',
'notEqualTo',
'lessThan',
'greaterThan',
'greaterThanOrEqualTo',
'lessThanOrEqualTo',
],
},
filterText: Schema.Types.Mixed,
},
],
},
{ timestamps: true }
);

View File

@@ -15,5 +15,6 @@ const lighthouseLogSchema = new Schema({
type: Date,
default: Date.now,
},
scanning: Boolean,
});
module.exports = mongoose.model('LighthouseLog', lighthouseLogSchema);

View File

@@ -7,6 +7,8 @@ const monitorLogSchema = new Schema({
status: String, // status based on criteria.
responseTime: Number, // time taken for ping.
responseStatus: Number, // status code of ping.
responseBody: String, //response body of ping
responseHeader: Object, //response header(s) of ping
cpuLoad: Number, // cpu load.
avgCpuLoad: Number, // average cpu load from server.
cpuCores: Number, // number of cpu cores.

View File

@@ -11,6 +11,7 @@ const probeSchema = new Schema({
createdAt: { type: Date, default: Date.now },
probeKey: { type: String },
probeName: { type: String },
version: { type: String },
lastAlive: { type: Date, default: Date.now },
deleted: { type: Boolean, default: false },
deletedAt: { type: Date },

View File

@@ -6,11 +6,15 @@ const smsCountSchema = new Schema({
sentTo: String,
createdAt: { type: Date, default: Date.now },
projectId: { type: String, ref: 'Project' },
parentProjectId: { type: String, ref: 'Project' },
deleted: { type: Boolean, default: false },
deletedAt: {
type: Date,
},
deletedById: { type: String, ref: 'User' },
content: String,
status: String,
error: String,
});
module.exports = mongoose.model('SmsCount', smsCountSchema);

View File

@@ -32,5 +32,7 @@ const subscriberAlertSchema = new Schema({
},
deletedById: { type: String, ref: 'User' },
totalSubscribers: { type: Number },
identification: { type: Number },
});
module.exports = mongoose.model('SubscriberAlert', subscriberAlertSchema);

View File

@@ -596,6 +596,16 @@ module.exports = {
!areEmailAlertsEnabledInGlobalSettings &&
!hasCustomSmtpSettings
) {
let errorMessageText;
if (!hasGlobalSmtpSettings && !hasCustomSmtpSettings) {
errorMessageText =
'SMTP Settings not found on Admin Dashboard';
} else if (
hasGlobalSmtpSettings &&
!areEmailAlertsEnabledInGlobalSettings
) {
errorMessageText = 'Alert Disabled on Admin Dashboard';
}
return await _this.create({
projectId: incident.projectId,
monitorId,
@@ -608,13 +618,7 @@ module.exports = {
alertStatus: null,
error: true,
eventType,
errorMessage:
!hasGlobalSmtpSettings && !hasCustomSmtpSettings
? 'SMTP Settings not found on Admin Dashboard'
: hasGlobalSmtpSettings &&
!areEmailAlertsEnabledInGlobalSettings
? 'Alert Disabled on Admin Dashboard'
: 'Error.',
errorMessage: errorMessageText,
});
}
const incidentcreatedBy =
@@ -674,6 +678,8 @@ module.exports = {
incidentId: incident._id,
eventType,
alertStatus: 'Cannot Send',
error: true,
errorMessage: e.message,
});
}
},
@@ -818,6 +824,15 @@ module.exports = {
(!project.alertEnable || !areAlertsEnabledGlobally)) ||
(!IS_SAAS_SERVICE && !areAlertsEnabledGlobally))
) {
let errorMessageText;
if (!hasGlobalTwilioSettings) {
errorMessageText =
'Twilio Settings not found on Admin Dashboard';
} else if (!areAlertsEnabledGlobally) {
errorMessageText = 'Alert Disabled on Admin Dashboard';
} else if (IS_SAAS_SERVICE && !project.alertEnable) {
errorMessageText = 'Alert Disabled for this project';
}
return await _this.create({
projectId: incident.projectId,
schedule: schedule._id,
@@ -830,13 +845,7 @@ module.exports = {
alertStatus: null,
error: true,
eventType,
errorMessage: !hasGlobalTwilioSettings
? 'Twilio Settings not found on Admin Dashboard'
: !areAlertsEnabledGlobally
? 'Alert Disabled on Admin Dashboard'
: IS_SAAS_SERVICE && !project.alertEnable
? 'Alert Disabled for this project'
: 'Error',
errorMessage: errorMessageText,
});
}
@@ -847,6 +856,17 @@ module.exports = {
);
if (!doesPhoneNumberComplyWithHighRiskConfig) {
const countryType = getCountryType(user.alertPhoneNumber);
let errorMessageText;
if (countryType === 'us') {
errorMessageText =
'Calls for numbers inside US not enabled for this project';
} else if (countryType === 'non-us') {
errorMessageText =
'Calls for numbers outside US not enabled for this project';
} else {
errorMessageText =
'Calls to High Risk country not enabled for this project';
}
return await _this.create({
projectId: incident.projectId,
monitorId,
@@ -859,12 +879,7 @@ module.exports = {
alertStatus: null,
error: true,
eventType,
errorMessage:
countryType === 'us'
? 'Calls for numbers inside US not enabled for this project'
: countryType === 'non-us'
? 'Calls for numbers outside US not enabled for this project'
: 'Calls to High Risk country not enabled for this project',
errorMessage: errorMessageText,
});
}
@@ -914,7 +929,7 @@ module.exports = {
alertStatus: null,
error: true,
eventType,
errorMessage: 'Error',
errorMessage: alertStatus.message,
});
} else if (alertStatus) {
alert = await _this.create({
@@ -1003,6 +1018,17 @@ module.exports = {
(!project.alertEnable || !areAlertsEnabledGlobally)) ||
(!IS_SAAS_SERVICE && !areAlertsEnabledGlobally))
) {
let errorMessageText;
if (!hasGlobalTwilioSettings) {
errorMessageText =
'Twilio Settings not found on Admin Dashboard';
} else if (!areAlertsEnabledGlobally) {
errorMessageText = 'Alert Disabled on Admin Dashboard';
} else if (IS_SAAS_SERVICE && !project.alertEnable) {
errorMessageText = 'Alert Disabled for this project';
} else {
errorMessageText = 'Error';
}
return await _this.create({
projectId: incident.projectId,
schedule: schedule._id,
@@ -1015,13 +1041,7 @@ module.exports = {
alertStatus: null,
error: true,
eventType,
errorMessage: !hasGlobalTwilioSettings
? 'Twilio Settings not found on Admin Dashboard'
: !areAlertsEnabledGlobally
? 'Alert Disabled on Admin Dashboard'
: IS_SAAS_SERVICE && !project.alertEnable
? 'Alert Disabled for this project'
: 'Error',
errorMessage: errorMessageText,
});
}
@@ -1032,6 +1052,17 @@ module.exports = {
);
if (!doesPhoneNumberComplyWithHighRiskConfig) {
const countryType = getCountryType(user.alertPhoneNumber);
let errorMessageText;
if (countryType === 'us') {
errorMessageText =
'SMS for numbers inside US not enabled for this project';
} else if (countryType === 'non-us') {
errorMessageText =
'SMS for numbers outside US not enabled for this project';
} else {
errorMessageText =
'SMS to High Risk country not enabled for this project';
}
return await _this.create({
projectId: incident.projectId,
monitorId,
@@ -1044,12 +1075,7 @@ module.exports = {
alertStatus: null,
error: true,
eventType,
errorMessage:
countryType === 'us'
? 'SMS for numbers inside US not enabled for this project'
: countryType === 'non-us'
? 'SMS for numbers outside US not enabled for this project'
: 'SMS to High Risk country not enabled for this project',
errorMessage: errorMessageText,
});
}
@@ -1120,9 +1146,7 @@ module.exports = {
});
if (IS_SAAS_SERVICE && !hasCustomTwilioSettings) {
// calculate charge per 160 chars
// numSegments is the number of segments the sms will be divided into
// numSegments is provided by twilio
const segments = Number(sendResult.numSegments);
const segments = calcSmsSegments(sendResult.body);
const balanceStatus = await PaymentService.chargeAlertAndGetProjectBalance(
user._id,
project,
@@ -1205,6 +1229,7 @@ module.exports = {
) {
try {
const _this = this;
const uuid = new Date().getTime();
const monitor = await MonitorService.findOneBy({
_id: incident.monitorId._id,
});
@@ -1234,7 +1259,9 @@ module.exports = {
note: data.content,
incidentState: data.incident_state,
statusNoteStatus,
}
},
subscribers.length,
uuid
);
}
}
@@ -1247,6 +1274,7 @@ module.exports = {
sendCreatedIncidentToSubscribers: async function(incident, component) {
try {
const _this = this;
const uuid = new Date().getTime();
if (incident) {
const monitorId = incident.monitorId._id
? incident.monitorId._id
@@ -1270,7 +1298,10 @@ module.exports = {
subscriber,
incident,
'Subscriber Incident Created',
enabledStatusPage
enabledStatusPage,
null,
subscribers.length,
uuid
);
}
} else {
@@ -1279,7 +1310,9 @@ module.exports = {
incident,
'Subscriber Incident Created',
null,
component
component,
subscribers.length,
uuid
);
}
}
@@ -1422,7 +1455,7 @@ module.exports = {
}
} else {
if (escalation.email) {
_this.sendAcknowledgeEmailAlert({
await _this.sendAcknowledgeEmailAlert({
incident,
user,
project,
@@ -1493,6 +1526,16 @@ module.exports = {
!areEmailAlertsEnabledInGlobalSettings &&
!hasCustomSmtpSettings
) {
let errorMessageText;
if (!hasGlobalSmtpSettings && !hasCustomSmtpSettings) {
errorMessageText =
'SMTP Settings not found on Admin Dashboard';
} else if (
hasGlobalSmtpSettings &&
!areEmailAlertsEnabledInGlobalSettings
) {
errorMessageText = 'Alert Disabled on Admin Dashboard';
}
return await _this.create({
projectId: incident.projectId,
monitorId: monitor._id,
@@ -1505,13 +1548,7 @@ module.exports = {
alertStatus: null,
error: true,
eventType,
errorMessage:
!hasGlobalSmtpSettings && !hasCustomSmtpSettings
? 'SMTP Settings not found on Admin Dashboard'
: hasGlobalSmtpSettings &&
!areEmailAlertsEnabledInGlobalSettings
? 'Alert Disabled on Admin Dashboard'
: 'Error.',
errorMessage: errorMessageText,
});
}
const incidentcreatedBy =
@@ -1590,6 +1627,8 @@ module.exports = {
incidentId: incident._id,
eventType,
alertStatus: 'Cannot Send',
error: true,
errorMessage: e.message,
});
}
},
@@ -1723,7 +1762,7 @@ module.exports = {
}
} else {
if (escalation.email) {
_this.sendResolveEmailAlert({
await _this.sendResolveEmailAlert({
incident,
user,
project,
@@ -1790,6 +1829,16 @@ module.exports = {
!areEmailAlertsEnabledInGlobalSettings &&
!hasCustomSmtpSettings
) {
let errorMessageText;
if (!hasGlobalSmtpSettings && !hasCustomSmtpSettings) {
errorMessageText =
'SMTP Settings not found on Admin Dashboard';
} else if (
hasGlobalSmtpSettings &&
!areEmailAlertsEnabledInGlobalSettings
) {
errorMessageText = 'Alert Disabled on Admin Dashboard';
}
return await _this.create({
projectId: incident.projectId,
monitorId: monitor._id,
@@ -1802,13 +1851,7 @@ module.exports = {
alertStatus: null,
error: true,
eventType,
errorMessage:
!hasGlobalSmtpSettings && !hasCustomSmtpSettings
? 'SMTP Settings not found on Admin Dashboard'
: hasGlobalSmtpSettings &&
!areEmailAlertsEnabledInGlobalSettings
? 'Alert Disabled on Admin Dashboard'
: 'Error.',
errorMessage: errorMessageText,
});
}
const incidentcreatedBy =
@@ -1886,6 +1929,8 @@ module.exports = {
incidentId: incident._id,
eventType,
alertStatus: 'Cannot Send',
error: true,
errorMessage: e.message,
});
}
},
@@ -1893,6 +1938,7 @@ module.exports = {
sendAcknowledgedIncidentToSubscribers: async function(incident) {
try {
const _this = this;
const uuid = new Date().getTime();
if (incident) {
const monitorId = incident.monitorId._id
? incident.monitorId._id
@@ -1915,14 +1961,21 @@ module.exports = {
subscriber,
incident,
'Subscriber Incident Acknowldeged',
enabledStatusPage
enabledStatusPage,
{},
subscribers.length,
uuid
);
}
} else {
await _this.sendSubscriberAlert(
subscriber,
incident,
'Subscriber Incident Acknowldeged'
'Subscriber Incident Acknowldeged',
null,
{},
subscribers.length,
uuid
);
}
}
@@ -1939,6 +1992,7 @@ module.exports = {
sendResolvedIncidentToSubscribers: async function(incident) {
try {
const _this = this;
const uuid = new Date().getTime();
if (incident) {
const monitorId = incident.monitorId._id
? incident.monitorId._id
@@ -1961,14 +2015,21 @@ module.exports = {
subscriber,
incident,
'Subscriber Incident Resolved',
enabledStatusPage
enabledStatusPage,
{},
subscribers.length,
uuid
);
}
} else {
await _this.sendSubscriberAlert(
subscriber,
incident,
'Subscriber Incident Resolved'
'Subscriber Incident Resolved',
null,
{},
subscribers.length,
uuid
);
}
}
@@ -1987,7 +2048,9 @@ module.exports = {
incident,
templateType = 'Subscriber Incident Created',
statusPage,
{ note, incidentState, statusNoteStatus } = {}
{ note, incidentState, statusNoteStatus } = {},
totalSubscribers,
id
) {
try {
const _this = this;
@@ -2031,6 +2094,21 @@ module.exports = {
}
}
const monitorCustomFields = {},
incidentCustomFields = {};
monitor.customFields.forEach(
field =>
(monitorCustomFields[field.fieldName] = field.fieldValue)
);
incident.customFields.forEach(
field =>
(incidentCustomFields[field.fieldName] = field.fieldValue)
);
const customFields = {
monitor: { customFields: monitorCustomFields },
incident: { customFields: incidentCustomFields },
};
let webhookNotificationSent = true;
if (subscriber.alertVia === AlertType.Webhook) {
@@ -2038,24 +2116,33 @@ module.exports = {
isStatusPageNoteAlert &&
!project.enableInvestigationNoteNotificationWebhook;
let eventType;
if (investigationNoteNotificationWebhookDisabled) {
if (isStatusPageNoteAlert) {
eventType = statusPageNoteAlertEventType;
} else if (
templateType === 'Subscriber Incident Acknowldeged'
) {
eventType = 'acknowledged';
} else if (
templateType === 'Subscriber Incident Resolved'
) {
eventType = 'resolved';
} else {
eventType = 'identified';
}
return await SubscriberAlertService.create({
projectId: incident.projectId,
incidentId: incident._id,
subscriberId: subscriber._id,
alertVia: AlertType.Webhook,
eventType: isStatusPageNoteAlert
? statusPageNoteAlertEventType
: templateType ===
'Subscriber Incident Acknowldeged'
? 'acknowledged'
: templateType === 'Subscriber Incident Resolved'
? 'resolved'
: 'identified',
eventType: eventType,
alertStatus: null,
error: true,
errorMessage:
'Investigation Note Webhook Notification Disabled',
totalSubscribers,
id,
});
}
const downTimeString = IncidentUtility.calculateHumanReadableDownTime(
@@ -2079,20 +2166,28 @@ module.exports = {
alertStatus = null;
throw error;
} finally {
if (isStatusPageNoteAlert) {
eventType = statusPageNoteAlertEventType;
} else if (
templateType === 'Subscriber Incident Acknowldeged'
) {
eventType = 'acknowledged';
} else if (
templateType === 'Subscriber Incident Resolved'
) {
eventType = 'resolved';
} else {
eventType = 'identified';
}
SubscriberAlertService.create({
projectId: incident.projectId,
incidentId: incident._id,
subscriberId: subscriber._id,
alertVia: AlertType.Webhook,
alertStatus: alertStatus,
eventType: isStatusPageNoteAlert
? statusPageNoteAlertEventType
: templateType ===
'Subscriber Incident Acknowldeged'
? 'acknowledged'
: templateType === 'Subscriber Incident Resolved'
? 'resolved'
: 'identified',
eventType: eventType,
totalSubscribers,
id,
}).catch(error => {
ErrorService.log(
'AlertService.sendSubscriberAlert',
@@ -2124,54 +2219,74 @@ module.exports = {
isStatusPageNoteAlert &&
!project.enableInvestigationNoteNotificationEmail;
let errorMessageText, eventType;
if (
(!areEmailAlertsEnabledInGlobalSettings &&
!hasCustomSmtpSettings) ||
investigationNoteNotificationEmailDisabled
) {
if (!hasGlobalSmtpSettings && !hasCustomSmtpSettings) {
errorMessageText =
'SMTP Settings not found on Admin Dashboard';
} else if (
hasGlobalSmtpSettings &&
!areEmailAlertsEnabledInGlobalSettings
) {
errorMessageText = 'Alert Disabled on Admin Dashboard';
} else if (investigationNoteNotificationEmailDisabled) {
errorMessageText =
'Investigation Note Email Notification Disabled';
}
if (isStatusPageNoteAlert) {
eventType = statusPageNoteAlertEventType;
} else if (
templateType === 'Subscriber Incident Acknowldeged'
) {
eventType = 'acknowledged';
} else if (
templateType === 'Subscriber Incident Resolved'
) {
eventType = 'resolved';
} else {
eventType = 'identified';
}
return await SubscriberAlertService.create({
projectId: incident.projectId,
incidentId: incident._id,
subscriberId: subscriber._id,
alertVia: AlertType.Email,
eventType: isStatusPageNoteAlert
? statusPageNoteAlertEventType
: templateType ===
'Subscriber Incident Acknowldeged'
? 'acknowledged'
: templateType === 'Subscriber Incident Resolved'
? 'resolved'
: 'identified',
eventType: eventType,
alertStatus: null,
error: true,
errorMessage:
!hasGlobalSmtpSettings && !hasCustomSmtpSettings
? 'SMTP Settings not found on Admin Dashboard'
: hasGlobalSmtpSettings &&
!areEmailAlertsEnabledInGlobalSettings
? 'Alert Disabled on Admin Dashboard'
: investigationNoteNotificationEmailDisabled
? 'Investigation Note Email Notification Disabled'
: 'Error',
errorMessage: errorMessageText,
totalSubscribers,
id,
});
}
const emailTemplate = await EmailTemplateService.findOneBy({
projectId: incident.projectId,
emailType: templateType,
});
if (isStatusPageNoteAlert) {
eventType = statusPageNoteAlertEventType;
} else if (
templateType === 'Subscriber Incident Acknowldeged'
) {
eventType = 'acknowledged';
} else if (templateType === 'Subscriber Incident Resolved') {
eventType = 'resolved';
} else {
eventType = 'identified';
}
const subscriberAlert = await SubscriberAlertService.create({
projectId: incident.projectId,
incidentId: incident._id,
subscriberId: subscriber._id,
alertVia: AlertType.Email,
alertStatus: 'Pending',
eventType: isStatusPageNoteAlert
? statusPageNoteAlertEventType
: templateType === 'Subscriber Incident Acknowldeged'
? 'acknowledged'
: templateType === 'Subscriber Incident Resolved'
? 'resolved'
: 'identified',
eventType: eventType,
totalSubscribers,
id,
});
const alertId = subscriberAlert._id;
const trackEmailAsViewedUrl = `${global.apiHost}/subscriberAlert/${incident.projectId}/${alertId}/viewed`;
@@ -2193,7 +2308,8 @@ module.exports = {
trackEmailAsViewedUrl,
component.name,
statusPageUrl,
project.replyAddress
project.replyAddress,
customFields
);
alertStatus = 'Sent';
@@ -2210,7 +2326,8 @@ module.exports = {
trackEmailAsViewedUrl,
component.name,
statusPageUrl,
project.replyAddress
project.replyAddress,
customFields
);
alertStatus = 'Sent';
@@ -2235,7 +2352,8 @@ module.exports = {
trackEmailAsViewedUrl,
component.name,
statusPageUrl,
project.replyAddress
project.replyAddress,
customFields
);
alertStatus = 'Sent';
} else {
@@ -2251,7 +2369,8 @@ module.exports = {
trackEmailAsViewedUrl,
component.name,
statusPageUrl,
project.replyAddress
project.replyAddress,
customFields
);
alertStatus = 'Sent';
}
@@ -2273,7 +2392,8 @@ module.exports = {
component.name,
note,
statusUrl,
statusNoteStatus
statusNoteStatus,
customFields
);
alertStatus = 'Sent';
} else {
@@ -2291,7 +2411,8 @@ module.exports = {
trackEmailAsViewedUrl,
component.name,
statusPageUrl,
project.replyAddress
project.replyAddress,
customFields
);
alertStatus = 'Sent';
} else {
@@ -2307,7 +2428,8 @@ module.exports = {
trackEmailAsViewedUrl,
component.name,
statusPageUrl,
project.replyAddress
project.replyAddress,
customFields
);
alertStatus = 'Sent';
}
@@ -2355,6 +2477,33 @@ module.exports = {
(!IS_SAAS_SERVICE && !areAlertsEnabledGlobally))) ||
investigationNoteNotificationSMSDisabled
) {
let errorMessageText, eventType;
if (!hasGlobalTwilioSettings) {
errorMessageText =
'Twilio Settings not found on Admin Dashboard';
} else if (!areAlertsEnabledGlobally) {
errorMessageText = 'Alert Disabled on Admin Dashboard';
} else if (IS_SAAS_SERVICE && !project.alertEnable) {
errorMessageText = 'Alert Disabled for this project';
} else if (investigationNoteNotificationSMSDisabled) {
errorMessageText =
'Investigation Note SMS Notification Disabled';
} else {
errorMessageText = 'Error';
}
if (isStatusPageNoteAlert) {
eventType = statusPageNoteAlertEventType;
} else if (
templateType === 'Subscriber Incident Acknowldeged'
) {
eventType = 'acknowledged';
} else if (
templateType === 'Subscriber Incident Resolved'
) {
eventType = 'resolved';
} else {
eventType = 'identified';
}
return await SubscriberAlertService.create({
projectId: incident.projectId,
incidentId: incident._id,
@@ -2362,23 +2511,10 @@ module.exports = {
alertVia: AlertType.SMS,
alertStatus: null,
error: true,
errorMessage: !hasGlobalTwilioSettings
? 'Twilio Settings not found on Admin Dashboard'
: !areAlertsEnabledGlobally
? 'Alert Disabled on Admin Dashboard'
: IS_SAAS_SERVICE && !project.alertEnable
? 'Alert Disabled for this project'
: investigationNoteNotificationSMSDisabled
? 'Investigation Note SMS Notification Disabled'
: 'Error',
eventType: isStatusPageNoteAlert
? statusPageNoteAlertEventType
: templateType ===
'Subscriber Incident Acknowldeged'
? 'acknowledged'
: templateType === 'Subscriber Incident Resolved'
? 'resolved'
: 'identified',
errorMessage: errorMessageText,
eventType: eventType,
totalSubscribers,
id,
});
}
const countryCode = await _this.mapCountryShortNameToCountryCode(
@@ -2399,6 +2535,30 @@ module.exports = {
);
if (!doesPhoneNumberComplyWithHighRiskConfig) {
const countryType = getCountryType(contactPhone);
let errorMessageText, eventType;
if (countryType === 'us') {
errorMessageText =
'SMS for numbers inside US not enabled for this project';
} else if (countryType === 'non-us') {
errorMessageText =
'SMS for numbers outside US not enabled for this project';
} else {
errorMessageText =
'SMS to High Risk country not enabled for this project';
}
if (isStatusPageNoteAlert) {
eventType = statusPageNoteAlertEventType;
} else if (
templateType === 'Subscriber Incident Acknowldeged'
) {
eventType = 'acknowledged';
} else if (
templateType === 'Subscriber Incident Resolved'
) {
eventType = 'resolved';
} else {
eventType = 'identified';
}
return await SubscriberAlertService.create({
projectId: incident.projectId,
incidentId: incident._id,
@@ -2406,21 +2566,10 @@ module.exports = {
alertVia: AlertType.SMS,
alertStatus: null,
error: true,
errorMessage:
countryType === 'us'
? 'SMS for numbers inside US not enabled for this project'
: countryType === 'non-us'
? 'SMS for numbers outside US not enabled for this project'
: 'SMS to High Risk country not enabled for this project',
eventType: isStatusPageNoteAlert
? statusPageNoteAlertEventType
: templateType ===
'Subscriber Incident Acknowldeged'
? 'acknowledged'
: templateType ===
'Subscriber Incident Resolved'
? 'resolved'
: 'identified',
errorMessage: errorMessageText,
eventType: eventType,
totalSubscribers,
id,
});
}
@@ -2430,6 +2579,20 @@ module.exports = {
contactPhone,
AlertType.SMS
);
let eventType;
if (isStatusPageNoteAlert) {
eventType = statusPageNoteAlertEventType;
} else if (
templateType === 'Subscriber Incident Acknowldeged'
) {
eventType = 'acknowledged';
} else if (
templateType === 'Subscriber Incident Resolved'
) {
eventType = 'resolved';
} else {
eventType = 'identified';
}
if (!status.success) {
return await SubscriberAlertService.create({
@@ -2440,15 +2603,9 @@ module.exports = {
alertStatus: null,
error: true,
errorMessage: status.message,
eventType: isStatusPageNoteAlert
? statusPageNoteAlertEventType
: templateType ===
'Subscriber Incident Acknowldeged'
? 'acknowledged'
: templateType ===
'Subscriber Incident Resolved'
? 'resolved'
: 'identified',
eventType: eventType,
totalSubscribers,
id,
});
}
}
@@ -2458,19 +2615,27 @@ module.exports = {
projectId: incident.projectId,
smsType: templateType,
});
let eventType;
if (isStatusPageNoteAlert) {
eventType = statusPageNoteAlertEventType;
} else if (
templateType === 'Subscriber Incident Acknowldeged'
) {
eventType = 'acknowledged';
} else if (templateType === 'Subscriber Incident Resolved') {
eventType = 'resolved';
} else {
eventType = 'identified';
}
const subscriberAlert = await SubscriberAlertService.create({
projectId: incident.projectId,
incidentId: incident._id,
subscriberId: subscriber._id,
alertVia: AlertType.SMS,
alertStatus: 'Pending',
eventType: isStatusPageNoteAlert
? statusPageNoteAlertEventType
: templateType === 'Subscriber Incident Acknowldeged'
? 'acknowledged'
: templateType === 'Subscriber Incident Resolved'
? 'resolved'
: 'identified',
eventType: eventType,
totalSubscribers,
id,
});
const alertId = subscriberAlert._id;
@@ -2488,7 +2653,8 @@ module.exports = {
project.name,
incident.projectId,
component.name,
statusPageUrl
statusPageUrl,
customFields
);
alertStatus = 'Success';
} else {
@@ -2501,7 +2667,8 @@ module.exports = {
project.name,
incident.projectId,
component.name,
statusPageUrl
statusPageUrl,
customFields
);
alertStatus = 'Success';
}
@@ -2522,7 +2689,8 @@ module.exports = {
project.name,
incident.projectId,
component.name,
statusPageUrl
statusPageUrl,
customFields
);
alertStatus = 'Success';
} else {
@@ -2535,7 +2703,8 @@ module.exports = {
project.name,
incident.projectId,
component.name,
statusPageUrl
statusPageUrl,
customFields
);
alertStatus = 'Success';
}
@@ -2554,7 +2723,8 @@ module.exports = {
project.name,
incident.projectId,
component.name,
statusUrl
statusUrl,
customFields
);
alertStatus = 'Success';
} else {
@@ -2569,7 +2739,8 @@ module.exports = {
project.name,
incident.projectId,
component.name,
statusPageUrl
statusPageUrl,
customFields
);
alertStatus = 'Success';
} else {
@@ -2582,7 +2753,8 @@ module.exports = {
project.name,
incident.projectId,
component.name,
statusPageUrl
statusPageUrl,
customFields
);
alertStatus = 'Success';
}
@@ -2617,9 +2789,7 @@ module.exports = {
!hasCustomTwilioSettings
) {
// charge sms per 160 chars
// numSegments is the number of segments an sms can be divided into
// numSegments is provided by twilio
const segments = Number(sendResult.numSegments);
const segments = calcSmsSegments(sendResult.body);
const balanceStatus = await PaymentService.chargeAlertAndGetProjectBalance(
owner.userId,
project,
@@ -2650,7 +2820,7 @@ module.exports = {
{
alertStatus: null,
error: true,
errorMessage: 'Error',
errorMessage: error.message,
}
);
throw error;
@@ -2846,6 +3016,17 @@ module.exports = {
},
};
/**
* @description calculates the number of segments an sms is divided into
* @param {string} sms the body of the sms sent
* @returns an interger
*/
function calcSmsSegments(sms) {
let smsLength = sms.length;
smsLength = Number(smsLength);
return Math.ceil(smsLength / 160);
}
const AlertModel = require('../models/alert');
const ProjectService = require('./projectService');
const PaymentService = require('./paymentService');

View File

@@ -0,0 +1,111 @@
module.exports = {
findBy: async function(query, limit, skip, sort) {
try {
if (!skip) skip = 0;
if (!limit) limit = 0;
if (typeof skip === 'string') skip = parseInt(skip);
if (typeof limit === 'string') limit = parseInt(limit);
if (!query) {
query = {};
}
if (!sort) {
sort = { createdAt: 'desc' };
}
if (!query.deleted) query.deleted = false;
const items = await CallLogsModel.find(query)
.limit(limit)
.skip(skip)
.sort(sort)
.populate('projectId', 'name');
return items;
} catch (error) {
ErrorService.log('callLogsService.findBy', error);
throw error;
}
},
create: async function(from, to, projectId, content, status, error) {
try {
let item = new CallLogsModel();
item.from = from;
item.to = to;
item.projectId = projectId;
item.content = content;
item.status = status;
item.error = error;
item = await item.save();
return item;
} catch (error) {
ErrorService.log('callLogsService.create', error);
throw error;
}
},
countBy: async function(query) {
try {
if (!query) {
query = {};
}
if (!query.deleted) query.deleted = false;
const count = await CallLogsModel.countDocuments(query);
return count;
} catch (error) {
ErrorService.log('callLogsService.countBy', error);
throw error;
}
},
deleteBy: async function(query, userId) {
try {
if (!query) {
query = {};
}
query.deleted = false;
const items = await CallLogsModel.findOneAndUpdate(query, {
$set: {
deleted: true,
deletedAt: Date.now(),
deletedById: userId,
},
});
return items;
} catch (error) {
ErrorService.log('callLogsService.findOneAndUpdate', error);
throw error;
}
},
hardDeleteBy: async function({ query }) {
try {
await CallLogsModel.deleteMany(query);
} catch (error) {
ErrorService.log('callLogs.hardDeleteBy', error);
throw error;
}
},
search: async function({ filter, skip, limit }) {
const _this = this;
const query = {
to: { $regex: new RegExp(filter), $options: 'i' },
};
const searchedCallLogs = await _this.findBy({ query, skip, limit });
const totalSearchCount = await _this.countBy({ query });
return { searchedCallLogs, totalSearchCount };
},
};
const CallLogsModel = require('../models/callLogs');
const ErrorService = require('./errorService');

View File

@@ -37,6 +37,7 @@ module.exports = {
body,
template,
content,
error,
}) {
try {
let item = new EmailStatusModel();
@@ -48,6 +49,7 @@ module.exports = {
item.body = body;
item.template = template;
item.content = content;
item.error = error;
item = await item.save();
return item;

View File

@@ -109,7 +109,7 @@ module.exports = {
// get all unique hashes by error tracker Id
const errorTrackerIssues = await IssueService.findBy(
query,
limit,
0,
skip
);
@@ -117,7 +117,10 @@ module.exports = {
let index = 0;
// if the next index is available in the issue tracker, proceed
while (errorTrackerIssues[index]) {
while (
errorTrackerIssues[index] &&
totalErrorEvents.length < limit
) {
const issue = errorTrackerIssues[index];
if (issue) {
@@ -168,6 +171,10 @@ module.exports = {
// increment index
index = index + 1;
}
// sort total error events by latest occurence date
totalErrorEvents.sort((eventA, eventB) =>
moment(eventB.latestOccurennce).isAfter(eventA.latestOccurennce)
);
let dateRange = { startDate: '', endDate: '' };
// set the date time range
if (query.createdAt) {
@@ -184,6 +191,17 @@ module.exports = {
endDate: totalErrorEvents[0].latestOccurennce,
})
: null;
errorTrackerIssues.length > 0
? (dateRange = {
startDate:
errorTrackerIssues[errorTrackerIssues.length - 1]
.createdAt,
endDate:
totalErrorEvents.length > 0
? totalErrorEvents[0].latestOccurennce
: errorTrackerIssues[0].createdAt,
})
: null;
}
return {
@@ -327,3 +345,4 @@ const ErrorEventModel = require('../models/errorEvent');
const ErrorService = require('./errorService');
const IssueService = require('./issueService');
const IssueMemberService = require('./issueMemberService');
const moment = require('moment');

View File

@@ -509,8 +509,10 @@ module.exports = {
status: 'acknowledged',
});
AlertService.sendAcknowledgedIncidentToSubscribers(incident);
AlertService.sendAcknowledgedIncidentMail(incident);
await AlertService.sendAcknowledgedIncidentToSubscribers(
incident
);
await AlertService.sendAcknowledgedIncidentMail(incident);
WebHookService.sendIntegrationNotification(
incident.projectId,
@@ -638,7 +640,7 @@ module.exports = {
status: 'resolved',
});
_this.sendIncidentResolvedNotification(incident, name);
await _this.sendIncidentResolvedNotification(incident, name);
RealTimeService.incidentResolved(incident);
ZapierService.pushToZapier('incident_resolve', incident);
@@ -753,8 +755,8 @@ module.exports = {
);
// send notificaton to subscribers
AlertService.sendResolvedIncidentToSubscribers(incident);
AlertService.sendResolveIncidentMail(incident);
await AlertService.sendResolvedIncidentToSubscribers(incident);
await AlertService.sendResolveIncidentMail(incident);
const msg = `${
resolvedincident.monitorId.name

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ module.exports = {
Log.bestPractices = data.bestPractices;
Log.seo = data.seo;
Log.pwa = data.pwa;
Log.scanning = data.scanning;
const savedLog = await Log.save();
@@ -47,6 +48,26 @@ module.exports = {
throw error;
}
},
updateManyBy: async function(query, data) {
try {
if (!query) {
query = {};
}
const lighthouseLog = await LighthouseLogModel.updateMany(
query,
{ $set: data },
{
new: true,
}
);
return lighthouseLog;
} catch (error) {
ErrorService.log('lighthouseLogService.updateManyBy', error);
throw error;
}
},
async findBy(query, limit, skip) {
try {
@@ -183,6 +204,27 @@ module.exports = {
throw error;
}
},
async updateAllLighthouseLogs(projectId, monitorId, query) {
try {
await this.updateManyBy({ monitorId: monitorId }, query);
const logs = await this.findLastestScan({
monitorId,
url: null,
limit: 5,
skip: 0,
});
await RealTimeService.updateAllLighthouseLog(projectId, {
monitorId,
logs,
});
} catch (error) {
ErrorService.log(
'lighthouseLogService.updateAllLighthouseLog',
error
);
throw error;
}
},
};
const LighthouseLogModel = require('../models/lighthouseLog');

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,26 @@ module.exports = {
create: async function(data) {
try {
const Log = new MonitorLogModel();
let responseBody = '';
if (data.resp && data.resp.body) {
if (typeof data.resp.body === 'object') {
responseBody = JSON.stringify(data.resp.body);
} else {
responseBody = data.resp.body;
}
} else {
responseBody = '';
}
Log.monitorId = data.monitorId;
Log.probeId = data.probeId;
Log.status = data.status;
Log.responseTime = data.responseTime;
Log.responseStatus = data.responseStatus;
Log.responseBody = responseBody;
Log.responseHeader =
data.rawResp && data.rawResp.headers
? data.rawResp.headers
: {};
Log.cpuLoad = data.cpuLoad;
Log.avgCpuLoad = data.avgCpuLoad;
Log.cpuCores = data.cpuCores;

View File

@@ -87,6 +87,7 @@ module.exports = {
monitor.monitorSla = data.monitorSla;
monitor.incidentCommunicationSla =
data.incidentCommunicationSla;
monitor.customFields = data.customFields;
monitor.createdById = data.createdById;
if (data.type === 'url' || data.type === 'api') {
monitor.data = {};
@@ -195,7 +196,6 @@ module.exports = {
);
}
const monitor = await this.findOneBy(query);
await RealTimeService.monitorEdit(monitor);
return monitor;

File diff suppressed because it is too large Load Diff

View File

@@ -105,19 +105,7 @@ module.exports = {
query = {};
}
query.deleted = false;
const project = await ProjectModel.findOneAndUpdate(
query,
{
$set: {
deleted: true,
deletedById: userId,
deletedAt: Date.now(),
},
},
{
new: true,
}
);
let project = await ProjectModel.findOne(query);
if (project) {
if (project.stripeSubscriptionId) {
await PaymentService.removeSubscription(
@@ -161,6 +149,32 @@ module.exports = {
{ projectId: project._id },
userId
);
const components = await componentService.findBy({
projectId: project._id,
});
await Promise.all(
components.map(async component => {
await componentService.deleteBy(
{ _id: component._id },
userId
);
})
);
project = await ProjectModel.findOneAndUpdate(
query,
{
$set: {
deleted: true,
deletedById: userId,
deletedAt: Date.now(),
},
},
{
new: true,
}
);
}
return project;
} catch (error) {
@@ -743,3 +757,4 @@ const StatusPageService = require('./statusPageService');
const slugify = require('slugify');
const generate = require('nanoid/generate');
const { IS_SAAS_SERVICE } = require('../config/server');
const componentService = require('./componentService');

View File

@@ -691,6 +691,31 @@ module.exports = {
}
},
updateAllLighthouseLog: async (projectId, data) => {
try {
if (!global || !global.io) {
return;
}
const project = await ProjectService.findOneBy({ _id: projectId });
const parentProjectId = project
? project.parentProjectId
? project.parentProjectId._id
: project._id
: projectId;
console.log(projectId, data, 'project id');
global.io.emit(`updateAllLighthouseLog-${parentProjectId}`, {
projectId,
monitorId: data.monitorId,
data,
});
} catch (error) {
ErrorService.log('realTimeService.updateAllLighthouseLog', error);
throw error;
}
},
updateMonitorStatus: async (data, projectId) => {
try {
if (!global || !global.io) {
@@ -970,14 +995,14 @@ module.exports = {
throw error;
}
},
sendErrorEventCreated: async errorEvent => {
sendErrorEventCreated: async data => {
try {
if (!global || !global.io) {
return;
}
const errorTrackerId = errorEvent.errorTrackerId._id;
const errorTrackerId = data.errorEvent.errorTrackerId._id;
global.io.emit(`createErrorEvent-${errorTrackerId}`, errorEvent);
global.io.emit(`createErrorEvent-${errorTrackerId}`, data);
} catch (error) {
ErrorService.log('realTimeService.sendErrorEventCreated', error);
throw error;

View File

@@ -17,7 +17,8 @@ module.exports = {
.sort([['createdAt', -1]])
.limit(limit)
.skip(skip)
.populate('userIds', 'name');
.populate('userId', 'name')
.populate('projectId', 'name');
return SmsCount;
} catch (error) {
ErrorService.log('smsCountService.findBy', error);
@@ -34,7 +35,8 @@ module.exports = {
if (!query.deleted) query.deleted = false;
const SmsCount = await SmsCountModel.findOne(query)
.sort([['createdAt', -1]])
.populate('userIds', 'name');
.populate('userId', 'name')
.populate('projectId', 'name');
return SmsCount;
} catch (error) {
ErrorService.log('smsCountService.findOneBy', error);
@@ -42,12 +44,15 @@ module.exports = {
}
},
create: async function(userId, sentTo, projectId) {
create: async function(userId, sentTo, projectId, content, status, error) {
try {
const smsCountModel = new SmsCountModel();
smsCountModel.userId = userId || null;
smsCountModel.sentTo = sentTo || null;
smsCountModel.projectId = projectId || null;
smsCountModel.content = content || null;
smsCountModel.status = status || null;
smsCountModel.error = error || null;
const smsCount = await smsCountModel.save();
return smsCount;
} catch (error) {

View File

@@ -8,6 +8,8 @@ module.exports = {
subscriberAlertModel.alertVia = data.alertVia || null;
subscriberAlertModel.alertStatus = data.alertStatus || null;
subscriberAlertModel.eventType = data.eventType || null;
subscriberAlertModel.totalSubscribers = data.totalSubscribers || 0;
subscriberAlertModel.identification = data.id || 0;
if (data.error) {
subscriberAlertModel.error = data.error;
subscriberAlertModel.errorMessage = data.errorMessage;

View File

@@ -12,6 +12,7 @@ const defaultSmsTemplates = require('../config/smsTemplate');
const GlobalConfigService = require('./globalConfigService');
const UserService = require('./userService');
const SmsCountService = require('./smsCountService');
const CallLogsService = require('./callLogsService');
const AlertService = require('./alertService');
const { IS_TESTING } = require('../config/server');
@@ -62,17 +63,17 @@ const _this = {
incidentType,
projectId
) {
let smsBody;
try {
const options = {
body: `Fyipe Alert: Monitor ${monitorName} is ${incidentType}. Please acknowledge or resolve this incident on Fyipe Dashboard.`,
to: number,
};
smsBody = options.body;
const customTwilioSettings = await _this.findByOne({
projectId,
enabled: true,
});
if (customTwilioSettings) {
options.from = customTwilioSettings.phoneNumber;
const incidentSMSAction = new incidentSMSActionModel();
@@ -87,6 +88,13 @@ const _this = {
customTwilioSettings.authToken
);
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
userId,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const creds = await _this.getSettings();
@@ -98,6 +106,14 @@ const _this = {
if (!creds['sms-enabled']) {
const error = new Error('SMS Not Enabled');
error.code = 400;
await SmsCountService.create(
userId,
number,
projectId,
smsBody,
'Error',
error.message
);
return error;
}
const alertLimit = await AlertService.checkPhoneAlertsLimit(
@@ -113,12 +129,27 @@ const _this = {
incidentSMSAction.name = name;
await incidentSMSAction.save();
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
userId,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const error = new Error(
'Alerts limit reached for the day.'
);
error.code = 400;
await SmsCountService.create(
userId,
number,
projectId,
smsBody,
'Error',
error.message
);
return error;
}
}
@@ -127,6 +158,14 @@ const _this = {
'twillioService.sendIncidentCreatedMessage',
error
);
await SmsCountService.create(
userId,
number,
projectId,
smsBody,
'Error',
error.message
);
throw error;
}
},
@@ -140,8 +179,10 @@ const _this = {
projectName,
projectId,
componentName,
statusPageUrl
statusPageUrl,
customFields
) {
let smsBody;
try {
let { template } = await _this.getTemplate(
smsTemplate,
@@ -154,8 +195,10 @@ const _this = {
incidentType: incident.incidentType,
componentName,
statusPageUrl,
...customFields,
};
template = template(data);
smsBody = template;
const customTwilioSettings = await _this.findByOne({
projectId,
enabled: true,
@@ -174,13 +217,27 @@ const _this = {
);
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const creds = await _this.getSettings();
if (!creds['sms-enabled']) {
const error = new Error('SMS Not Enabled');
error.code = 400;
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
return error;
}
const options = {
@@ -200,11 +257,26 @@ const _this = {
if (alertLimit) {
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const error = new Error(
'Alerts limit reached for the day.'
);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
error.code = 400;
return error;
}
@@ -214,6 +286,14 @@ const _this = {
'twillioService.sendIncidentCreatedMessageToSubscriber',
error
);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
throw error;
}
},
@@ -227,8 +307,10 @@ const _this = {
projectName,
projectId,
componentName,
statusUrl
statusUrl,
customFields
) {
let smsBody;
try {
let { template } = await _this.getTemplate(
smsTemplate,
@@ -241,8 +323,10 @@ const _this = {
incidentType: incident.incidentType,
componentName,
statusPageUrl: statusUrl,
...customFields,
};
template = template(data);
smsBody = template;
const customTwilioSettings = await _this.findByOne({
projectId,
enabled: true,
@@ -261,13 +345,27 @@ const _this = {
);
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const creds = await _this.getSettings();
if (!creds['sms-enabled']) {
const error = new Error('SMS Not Enabled');
error.code = 400;
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
return error;
}
const options = {
@@ -287,11 +385,26 @@ const _this = {
if (alertLimit) {
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const error = new Error(
'Alerts limit reached for the day.'
);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
error.code = 400;
return error;
}
@@ -301,6 +414,14 @@ const _this = {
'twillioService.sendInvestigationNoteToSubscribers',
error
);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
throw error;
}
},
@@ -314,8 +435,10 @@ const _this = {
projectName,
projectId,
componentName,
statusPageUrl
statusPageUrl,
customFields
) {
let smsBody;
try {
const _this = this;
let { template } = await _this.getTemplate(
@@ -329,8 +452,10 @@ const _this = {
incidentType: incident.incidentType,
componentName,
statusPageUrl,
...customFields,
};
template = template(data);
smsBody = template;
const customTwilioSettings = await _this.findByOne({
projectId,
enabled: true,
@@ -347,12 +472,27 @@ const _this = {
customTwilioSettings.authToken
);
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const creds = await _this.getSettings();
if (!creds['sms-enabled']) {
const error = new Error('SMS Not Enabled');
error.code = 400;
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
return error;
}
const options = {
@@ -372,11 +512,26 @@ const _this = {
if (alertLimit) {
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const error = new Error(
'Alerts limit reached for the day.'
);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
error.code = 400;
return error;
}
@@ -386,6 +541,14 @@ const _this = {
'twillioService.sendIncidentAcknowldegedMessageToSubscriber',
error
);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
throw error;
}
},
@@ -399,8 +562,10 @@ const _this = {
projectName,
projectId,
componentName,
statusPageUrl
statusPageUrl,
customFields
) {
let smsBody;
try {
const _this = this;
let { template } = await _this.getTemplate(
@@ -414,8 +579,10 @@ const _this = {
incidentType: incident.incidentType,
componentName,
statusPageUrl,
...customFields,
};
template = template(data);
smsBody = template;
const customTwilioSettings = await _this.findByOne({
projectId,
enabled: true,
@@ -432,12 +599,27 @@ const _this = {
customTwilioSettings.authToken
);
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const creds = await _this.getSettings();
if (!creds['sms-enabled']) {
const error = new Error('SMS Not Enabled');
error.code = 400;
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
return error;
}
const options = {
@@ -457,11 +639,26 @@ const _this = {
if (alertLimit) {
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Success'
);
return message;
} else {
const error = new Error(
'Alerts limit reached for the day.'
);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
error.code = 400;
return error;
}
@@ -471,6 +668,14 @@ const _this = {
'twillioService.sendIncidentResolvedMessageToSubscriber',
error
);
await SmsCountService.create(
null,
number,
projectId,
smsBody,
'Error',
error.message
);
throw error;
}
},
@@ -489,6 +694,13 @@ const _this = {
);
const message = await twilioClient.messages.create(options);
await SmsCountService.create(
null,
options.to,
null,
options.body,
'Success'
);
return message;
} catch (error) {
@@ -512,9 +724,9 @@ const _this = {
accessToken,
incidentId,
projectId,
redialCount,
incidentType
) {
let callBody;
try {
const message =
'<Say voice="alice">This is an alert from Fyipe. Your monitor ' +
@@ -524,6 +736,7 @@ const _this = {
'. Please go to Fyipe Dashboard or Mobile app to acknowledge or resolve this incident.</Say>';
const hangUp = '<Hangup />';
const twiml = '<Response> ' + message + hangUp + '</Response>';
callBody = twiml;
const options = {
twiml: twiml,
to: number,
@@ -540,6 +753,13 @@ const _this = {
customTwilioSettings.authToken
);
const call = await twilioClient.calls.create(options);
await CallLogsService.create(
'+15005550006',
number,
projectId,
callBody,
'Success'
);
return call;
} else {
const creds = await _this.getSettings();
@@ -550,6 +770,14 @@ const _this = {
if (!creds['call-enabled']) {
const error = new Error('Call Not Enabled');
error.code = 400;
await CallLogsService.create(
'+15005550006',
number,
projectId,
callBody,
'Error',
error.message
);
return error;
}
@@ -560,6 +788,13 @@ const _this = {
options.from = creds.phone;
if (twilioClient) {
const call = await twilioClient.calls.create(options);
await CallLogsService.create(
'+15005550006',
number,
projectId,
callBody,
'Success'
);
return call;
}
} else {
@@ -567,11 +802,27 @@ const _this = {
'Alerts limit reached for the day.'
);
error.code = 400;
await CallLogsService.create(
'+15005550006',
number,
projectId,
callBody,
'Error',
error.message
);
return error;
}
}
} catch (error) {
ErrorService.log('twillioService.sendIncidentCreatedCall', error);
await CallLogsService.create(
'+15005550006',
number,
projectId,
callBody,
'Error',
error.message
);
throw error;
}
},
@@ -597,6 +848,7 @@ const _this = {
projectId,
validationResult
) {
let smsBody;
try {
const customTwilioSettings = await _this.findByOne({
projectId,
@@ -612,6 +864,7 @@ const _this = {
.substr(2, 6);
if (customTwilioSettings) {
const template = `Your verification code: ${alertPhoneVerificationCode}`;
smsBody = template;
const options = {
body: template,
from: customTwilioSettings.phoneNumber,
@@ -652,13 +905,20 @@ const _this = {
throw error;
}
const template = `Your verification code: ${alertPhoneVerificationCode}`;
smsBody = template;
const options = {
body: template,
from: creds.phone,
to,
};
await twilioClient.messages.create(options);
await SmsCountService.create(userId, to, projectId);
await SmsCountService.create(
userId,
to,
projectId,
options.body,
'Success'
);
await UserService.updateOneBy(
{ _id: userId },
{
@@ -672,12 +932,28 @@ const _this = {
const error = new Error(
'Alerts limit reached for the day.'
);
await SmsCountService.create(
userId,
to,
projectId,
smsBody,
'Error',
error.message
);
error.code = 400;
throw error;
}
}
} catch (error) {
ErrorService.log('twillioService.sendVerificationSMS', error);
await SmsCountService.create(
userId,
to,
projectId,
smsBody,
'Error',
error.message
);
throw error;
}
},

View File

@@ -43,11 +43,11 @@ module.exports = {
const job = async monitor => {
try {
const { stat: validUp, reasons } = await (monitor &&
const { stat: validUp, successReasons } = await (monitor &&
monitor.criteria &&
monitor.criteria.up
? ProbeService.conditions(monitor.type, monitor.criteria.up)
: { stat: false, reasons: [] });
: { stat: false, successReasons: [] });
const { stat: validDown } = await (monitor &&
monitor.criteria &&
monitor.criteria.down
@@ -57,7 +57,9 @@ const job = async monitor => {
await ProbeService.saveMonitorLog({
monitorId: monitor._id,
status: 'offline',
reason: reasons,
reason: await successReasons.filter(function(item, pos, self) {
return self.indexOf(item) == pos;
}),
});
}
} catch (error) {

7636
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -204,6 +204,7 @@ app.use(['/version', '/api/version'], require('./backend/api/version'));
app.use(['/tutorial', '/api/tutorial'], require('./backend/api/tutorial'));
app.use(['/audit-logs', '/api/audit-logs'], require('./backend/api/auditLogs'));
app.use(['/email-logs', '/api/email-logs'], require('./backend/api/emailLogs'));
app.use(['/call-logs', '/api/call-logs'], require('./backend/api/callLogs'));
app.use(['/sms-logs', '/api/sms-logs'], require('./backend/api/smsLogs'));
app.use(['/component', '/api/component'], require('./backend/api/component'));
app.use(

View File

@@ -0,0 +1,176 @@
/* eslint-disable no-undef */
process.env.PORT = 3020;
process.env.IS_SAAS_SERVICE = true;
const chai = require('chai');
const expect = require('chai').expect;
const userData = require('./data/user');
const app = require('../server');
chai.use(require('chai-http'));
const request = chai.request.agent(app);
const GlobalConfig = require('./utils/globalConfig');
const { createUser } = require('./utils/userSignUp');
const VerificationTokenModel = require('../backend/models/verificationToken');
const AirtableService = require('../backend/services/airtableService');
const UserService = require('../backend/services/userService');
const ProjectService = require('../backend/services/projectService');
const ComponentService = require('../backend/services/componentService');
const IncidentCustomFieldService = require('../backend/services/customFieldService');
describe('Incident Custom Field API', function() {
const timeout = 30000;
let projectId, userId, token, authorization, customFieldId;
const incidentFieldText = {
fieldName: 'inTextField',
fieldType: 'text',
},
incidentFieldNumber = {
fieldName: 'inNumField',
fieldType: 'number',
};
this.timeout(timeout);
before(function(done) {
GlobalConfig.initTestConfig().then(function() {
createUser(request, userData.user, function(err, res) {
const project = res.body.project;
projectId = project._id;
userId = res.body.id;
VerificationTokenModel.findOne({ userId }, function(
err,
verificationToken
) {
request
.get(`/user/confirmation/${verificationToken.token}`)
.redirects(0)
.end(function() {
request
.post('/user/login')
.send({
email: userData.user.email,
password: userData.user.password,
})
.end(function(err, res) {
token = res.body.tokens.jwtAccessToken;
authorization = `Basic ${token}`;
done();
});
});
});
});
});
});
after(async function() {
await GlobalConfig.removeTestConfig();
await ProjectService.hardDeleteBy({ _id: projectId });
await UserService.hardDeleteBy({
email: userData.user.email,
});
await ComponentService.hardDeleteBy({ projectId });
await IncidentCustomFieldService.hardDeleteBy({ projectId });
await AirtableService.deleteAll({ tableName: 'User' });
});
it('should not create an incident custom field when field name is missing or not specified', function(done) {
request
.post(`/customField/${projectId}`)
.send({ fieldType: 'text' })
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(400);
expect(res.body.message).to.be.equal('Field name is required');
done();
});
});
it('should not create an incident custom field when field type is missing or not specified', function(done) {
request
.post(`/customField/${projectId}`)
.send({ fieldName: 'missingType' })
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(400);
expect(res.body.message).to.be.equal('Field type is required');
done();
});
});
it('should setup custom fields for all incidents in a project (text)', function(done) {
request
.post(`/customField/${projectId}`)
.send(incidentFieldText)
.set('Authorization', authorization)
.end(function(err, res) {
customFieldId = res.body._id;
expect(res).to.have.status(200);
expect(res.body.fieldName).to.be.equal(
incidentFieldText.fieldName
);
done();
});
});
it('should not create incident custom field with an existing name in a project', function(done) {
request
.post(`/customField/${projectId}`)
.send(incidentFieldText)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(400);
expect(res.body.message).to.be.equal(
'Custom field with this name already exist'
);
done();
});
});
it('should update a particular incident custom field in a project', function(done) {
incidentFieldText.fieldName = 'newName';
request
.put(`/customField/${projectId}/${customFieldId}`)
.send(incidentFieldText)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body.fieldName).to.be.equal(
incidentFieldText.fieldName
);
expect(String(res.body._id)).to.be.equal(String(customFieldId));
done();
});
});
it('should list all the incident custom fields in a project', function(done) {
// add one more monitor custom field
request
.post(`/customField/${projectId}`)
.send(incidentFieldNumber)
.set('Authorization', authorization)
.end(function() {
request
.get(`/customField/${projectId}?skip=0&limit=10`)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body.count).to.be.equal(2);
expect(res.body.data).to.be.an('array');
done();
});
});
});
it('should delete a particular monitor custom field in a project', function(done) {
request
.delete(`/customField/${projectId}/${customFieldId}`)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(200);
expect(String(res.body._id)).to.be.equal(String(customFieldId));
done();
});
});
});

View File

@@ -0,0 +1,72 @@
module.exports = {
incidentRequest: {
name: 'pyInt',
isDefault: true,
createIncident: true,
incidentTitle: 'Test Incident',
incidentType: 'offline',
incidentDescription:
'This is a sample incident to test incoming http request',
filterMatch: 'any',
filters: [
{
filterCondition: 'equalTo',
filterCriteria: 'monitorField',
filterText: 'testing',
},
],
monitors: [],
},
acknowledgeRequest: {
name: 'ack',
acknowledgeIncident: true,
filterMatch: 'any',
filters: [
{
filterCondition: 'greaterThanOrEqualTo',
filterCriteria: 'incidentId',
filterText: 1,
},
],
},
resolveRequest: {
name: 'resolve',
resolveIncident: true,
filterMatch: 'any',
filters: [
{
filterCondition: 'greaterThanOrEqualTo',
filterCriteria: 'incidentId',
filterText: 1,
},
],
},
incidentNoteRequest: {
name: 'incidentNote',
updateIncidentNote: true,
filterMatch: 'all',
filters: [
{
filterCondition: 'greaterThanOrEqualTo',
filterCriteria: 'incidentId',
filterText: 1,
},
],
incidentState: 'update',
noteContent: 'This is a sample incident note',
},
internalNoteRequest: {
name: 'internalNote',
updateInternalNote: true,
incidentState: 'investigating',
noteContent: 'This is a sample internal note',
filterMatch: 'all',
filters: [
{
filterCondition: 'equalTo',
filterCriteria: 'incidentId',
filterText: 1,
},
],
},
};

View File

@@ -86,6 +86,7 @@ describe('Enterprise Alert API', function() {
monitorId,
alertVia: 'email',
incidentId,
eventType: 'identified',
})
.end(function(err, res) {
alertId = res.body._id;

View File

@@ -0,0 +1,336 @@
/* eslint-disable no-undef */
process.env.PORT = 3020;
process.env.IS_SAAS_SERVICE = true;
const chai = require('chai');
const expect = require('chai').expect;
const userData = require('./data/user');
const app = require('../server');
chai.use(require('chai-http'));
const request = chai.request.agent(app);
const GlobalConfig = require('./utils/globalConfig');
const { createUser } = require('./utils/userSignUp');
const VerificationTokenModel = require('../backend/models/verificationToken');
const AirtableService = require('../backend/services/airtableService');
const UserService = require('../backend/services/userService');
const ProjectService = require('../backend/services/projectService');
const ComponentService = require('../backend/services/componentService');
const IncidentPrioritiesService = require('../backend/services/incidentPrioritiesService');
const MonitorService = require('../backend/services/monitorService');
const IncomingHttpRequestService = require('../backend/services/incomingRequestService');
const MonitorCustomFieldService = require('../backend/services/monitorCustomField');
const IncidentCustomFieldService = require('../backend/services/customFieldService');
const IncidentService = require('../backend/services/incidentService');
const axios = require('axios');
const {
resolveRequest,
internalNoteRequest,
incidentRequest,
incidentNoteRequest,
acknowledgeRequest,
} = require('./data/incomingHttpRequest');
describe('Incoming HTTP Request API', function() {
const timeout = 30000;
let projectId,
componentId,
userId,
token,
incidentPriorityId,
monitorId,
requestId,
authorization,
createIncidentUrl,
acknowledgeIncidentUrl,
resolveIncidentUrl,
incidentNoteUrl,
internalNoteUrl;
this.timeout(timeout);
before(function(done) {
GlobalConfig.initTestConfig().then(function() {
createUser(request, userData.user, function(err, res) {
const project = res.body.project;
projectId = project._id;
userId = res.body.id;
VerificationTokenModel.findOne({ userId }, function(
err,
verificationToken
) {
request
.get(`/user/confirmation/${verificationToken.token}`)
.redirects(0)
.end(function() {
request
.post('/user/login')
.send({
email: userData.user.email,
password: userData.user.password,
})
.end(function(err, res) {
token = res.body.tokens.jwtAccessToken;
authorization = `Basic ${token}`;
request
.post(`/component/${projectId}`)
.set('Authorization', authorization)
.send({ name: 'Test Component' })
.end(function(err, res) {
componentId = res.body._id;
request
.post(`/monitor/${projectId}`)
.set(
'Authorization',
authorization
)
.send({
name: 'testMonitor',
criteria: {},
componentId,
projectId,
type: 'manual',
data: { description: null },
customFields: [
{
fieldName:
'monitorField',
fieldValue:
'testing',
},
],
})
.end(function(err, res) {
monitorId = res.body._id;
MonitorCustomFieldService.create(
{
projectId,
fieldName:
'monitorField',
fieldType: 'text',
}
).then(function() {
done();
});
});
});
});
});
});
});
});
});
after(async function() {
await GlobalConfig.removeTestConfig();
await ProjectService.hardDeleteBy({ _id: projectId });
await UserService.hardDeleteBy({
email: userData.user.email,
});
await ComponentService.hardDeleteBy({ projectId });
await MonitorService.hardDeleteBy({ projectId });
await IncomingHttpRequestService.hardDeleteBy({ projectId });
await MonitorCustomFieldService.hardDeleteBy({ projectId });
await IncidentCustomFieldService.hardDeleteBy({ projectId });
await IncidentService.hardDeleteBy({ projectId });
await AirtableService.deleteAll({ tableName: 'User' });
});
it('should create an incoming http request (Create Incident)', function(done) {
IncidentPrioritiesService.findOne({ projectId }).then(function(
priority
) {
// fetch one of the priorities
incidentPriorityId = priority._id;
incidentRequest.incidentPriority = incidentPriorityId;
request
.post(`/incoming-request/${projectId}/create-request-url`)
.set('Authorization', authorization)
.send(incidentRequest)
.end(function(err, res) {
requestId = res.body._id;
createIncidentUrl = res.body.url;
expect(res).to.have.status(200);
expect(res.body.name).to.be.equal(incidentRequest.name);
expect(res.body.filters).to.be.an('array');
done();
});
});
});
it('should create an incoming http request (Acknowledge Incident)', function(done) {
request
.post(`/incoming-request/${projectId}/create-request-url`)
.set('Authorization', authorization)
.send(acknowledgeRequest)
.end(function(err, res) {
acknowledgeIncidentUrl = res.body.url;
expect(res).to.have.status(200);
expect(res.body.name).to.be.equal(acknowledgeRequest.name);
expect(res.body.filters).to.be.an('array');
done();
});
});
it('should create an incoming http request (Resolve Incident)', function(done) {
request
.post(`/incoming-request/${projectId}/create-request-url`)
.set('Authorization', authorization)
.send(resolveRequest)
.end(function(err, res) {
resolveIncidentUrl = res.body.url;
expect(res).to.have.status(200);
expect(res.body.name).to.be.equal(resolveRequest.name);
expect(res.body.filters).to.be.an('array');
done();
});
});
it('should create an incoming http request (Update incident note)', function(done) {
request
.post(`/incoming-request/${projectId}/create-request-url`)
.send(incidentNoteRequest)
.set('Authorization', authorization)
.end(function(err, res) {
incidentNoteUrl = res.body.url;
expect(res).to.have.status(200);
expect(res.body.name).to.be.equal(incidentNoteRequest.name);
expect(res.body.filterMatch).to.be.equal(
incidentNoteRequest.filterMatch
);
expect(res.body.filters).to.be.an('array');
done();
});
});
it('should create an incoming http request (Update internal note)', function(done) {
request
.post(`/incoming-request/${projectId}/create-request-url`)
.send(internalNoteRequest)
.set('Authorization', authorization)
.end(function(err, res) {
internalNoteUrl = res.body.url;
expect(res).to.have.status(200);
expect(res.body.name).to.be.equal(internalNoteRequest.name);
expect(res.body.filters).to.be.an('array');
expect(res.body.filterMatch).to.be.equal(
internalNoteRequest.filterMatch
);
done();
});
});
it('should update an incoming http request', function(done) {
const update = {
name: 'updateName',
};
request
.put(`/incoming-request/${projectId}/update/${requestId}`)
.send(update)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body.name).to.be.equal(update.name);
done();
});
});
it('should list all the created incoming http request in a project', function(done) {
incidentRequest.name = 'anotherOne';
incidentRequest.isDefault = false;
incidentRequest.monitors = [monitorId];
// add one more incoming http request
request
.post(`/incoming-request/${projectId}/create-request-url`)
.set('Authorization', authorization)
.send(incidentRequest)
.end(function(err, res) {
requestId = res.body._id;
request
.get(`/incoming-request/${projectId}/all-incoming-request`)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body.data).to.be.an('array');
done();
});
});
});
it('should create an incident with incoming http request url', function(done) {
axios({
method: 'post',
url: createIncidentUrl,
}).then(function(res) {
expect(res).to.have.status(200);
expect(res.data.status).to.be.equal('success');
expect(res.data.created_incidents).to.be.an('array');
done();
});
});
it('should acknowledge an incident with an incoming http request url', function(done) {
axios({
method: 'post',
url: acknowledgeIncidentUrl,
}).then(function(res) {
expect(res).to.have.status(200);
expect(res.data.status).to.be.equal('success');
expect(res.data.acknowledged_incidents).to.be.an('array');
done();
});
});
it('should resolve an incident with an incoming http request url', function(done) {
axios({
method: 'post',
url: resolveIncidentUrl,
}).then(function(res) {
expect(res).to.have.status(200);
expect(res.data.status).to.be.equal('success');
expect(res.data.resolved_incidents).to.be.an('array');
done();
});
});
it('should add incident note with an incoming http request url', function(done) {
// it should also work for a get request
axios({
method: 'get',
url: incidentNoteUrl,
}).then(function(res) {
expect(res).to.have.status(200);
expect(res.data.status).to.be.equal('success');
expect(res.data.notes_addedTo).to.be.an('array');
done();
});
});
it('should add internal note with an incoming http request url', function(done) {
axios({
method: 'get',
url: internalNoteUrl,
}).then(function(res) {
expect(res).to.have.status(200);
expect(res.data.status).to.be.equal('success');
expect(res.data.notes_addedTo).to.be.an('array');
done();
});
});
it('should delete an incoming http request in project', function(done) {
request
.delete(`/incoming-request/${projectId}/remove/${requestId}`)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(200);
expect(String(res.body._id)).to.be.equal(String(requestId));
done();
});
});
});

View File

@@ -1,5 +1,8 @@
try {
require('./monitorSla.test');
require('./customField.test');
require('./incomingHttpRequest.test');
require('./monitorCustomField.test');
require('./adminCredentials.test');
require('./incidentCommunicationSla.test');
require('./adminCredentials.test');

View File

@@ -0,0 +1,180 @@
/* eslint-disable no-undef */
process.env.PORT = 3020;
process.env.IS_SAAS_SERVICE = true;
const chai = require('chai');
const expect = require('chai').expect;
const userData = require('./data/user');
const app = require('../server');
chai.use(require('chai-http'));
const request = chai.request.agent(app);
const GlobalConfig = require('./utils/globalConfig');
const { createUser } = require('./utils/userSignUp');
const VerificationTokenModel = require('../backend/models/verificationToken');
const AirtableService = require('../backend/services/airtableService');
const UserService = require('../backend/services/userService');
const ProjectService = require('../backend/services/projectService');
const ComponentService = require('../backend/services/componentService');
const MonitorCustomFieldService = require('../backend/services/monitorCustomField');
describe('Monitor Custom Field API', function() {
const timeout = 30000;
let projectId, userId, token, authorization, monitorCustomFieldId;
const monitorFieldText = {
fieldName: 'textField',
fieldType: 'text',
},
monitorFieldNumber = {
fieldName: 'numField',
fieldType: 'number',
};
this.timeout(timeout);
before(function(done) {
GlobalConfig.initTestConfig().then(function() {
createUser(request, userData.user, function(err, res) {
const project = res.body.project;
projectId = project._id;
userId = res.body.id;
VerificationTokenModel.findOne({ userId }, function(
err,
verificationToken
) {
request
.get(`/user/confirmation/${verificationToken.token}`)
.redirects(0)
.end(function() {
request
.post('/user/login')
.send({
email: userData.user.email,
password: userData.user.password,
})
.end(function(err, res) {
token = res.body.tokens.jwtAccessToken;
authorization = `Basic ${token}`;
done();
});
});
});
});
});
});
after(async function() {
await GlobalConfig.removeTestConfig();
await ProjectService.hardDeleteBy({ _id: projectId });
await UserService.hardDeleteBy({
email: userData.user.email,
});
await ComponentService.hardDeleteBy({ projectId });
await MonitorCustomFieldService.hardDeleteBy({ projectId });
await AirtableService.deleteAll({ tableName: 'User' });
});
it('should not create a monitor custom field when field name is missing or not specified', function(done) {
request
.post(`/monitorCustomField/${projectId}`)
.send({ fieldType: 'text' })
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(400);
expect(res.body.message).to.be.equal('Field name is required');
done();
});
});
it('should not create a monitor custom field when field type is missing or not specified', function(done) {
request
.post(`/monitorCustomField/${projectId}`)
.send({ fieldName: 'missingType' })
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(400);
expect(res.body.message).to.be.equal('Field type is required');
done();
});
});
it('should setup custom fields for all monitors in a project (text)', function(done) {
request
.post(`/monitorCustomField/${projectId}`)
.send(monitorFieldText)
.set('Authorization', authorization)
.end(function(err, res) {
monitorCustomFieldId = res.body._id;
expect(res).to.have.status(200);
expect(res.body.fieldName).to.be.equal(
monitorFieldText.fieldName
);
done();
});
});
it('should not create monitor custom field with an existing name in a project', function(done) {
request
.post(`/monitorCustomField/${projectId}`)
.send(monitorFieldText)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(400);
expect(res.body.message).to.be.equal(
'Custom field with this name already exist'
);
done();
});
});
it('should update a particular monitor custom field in a project', function(done) {
monitorFieldText.fieldName = 'newName';
request
.put(`/monitorCustomField/${projectId}/${monitorCustomFieldId}`)
.send(monitorFieldText)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body.fieldName).to.be.equal(
monitorFieldText.fieldName
);
expect(String(res.body._id)).to.be.equal(
String(monitorCustomFieldId)
);
done();
});
});
it('should list all the monitor custom fields in a project', function(done) {
// add one more monitor custom field
request
.post(`/monitorCustomField/${projectId}`)
.send(monitorFieldNumber)
.set('Authorization', authorization)
.end(function() {
request
.get(`/monitorCustomField/${projectId}?skip=0&limit=10`)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body.count).to.be.equal(2);
expect(res.body.data).to.be.an('array');
done();
});
});
});
it('should delete a particular monitor custom field in a project', function(done) {
request
.delete(`/monitorCustomField/${projectId}/${monitorCustomFieldId}`)
.set('Authorization', authorization)
.end(function(err, res) {
expect(res).to.have.status(200);
expect(String(res.body._id)).to.be.equal(
String(monitorCustomFieldId)
);
done();
});
});
});

View File

@@ -6,7 +6,7 @@ build_n_test_accounts:
- if [[ $next_stage == *"skip"* ]]; then exit ${CI_JOB_SKIP_EXIT_CODE:-0}; fi
- curl -sSL https://get.docker.com/ | sh #Install docker.
- sudo apt-get update
- sudo apt install -y gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
- sudo apt install -y gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget libgbm-dev
- echo "Setup machine for running puppeteer tests"
- sudo docker stop $(sudo docker ps -aq) || echo 'No docker containers'
- sudo docker rm $(sudo docker ps -aq) || echo 'No docker containers'
@@ -19,6 +19,7 @@ build_n_test_accounts:
- sudo docker build -t fyipeproject/dashboard:3.0.$CI_PIPELINE_IID ./dashboard
- sudo docker run --env-file ./dashboard/.env -e IS_SAAS_SERVICE=true -p 3000:3000 -d fyipeproject/dashboard:3.0.$CI_PIPELINE_IID
- sudo docker ps
- npm ci
- cd accounts
- npm ci
# try building, if there are warnings then this will fail
@@ -42,7 +43,7 @@ build_n_test_enterprise_accounts:
- if [[ $next_stage == *"skip"* ]]; then exit ${CI_JOB_SKIP_EXIT_CODE:-0}; fi
- curl -sSL https://get.docker.com/ | sh #Install docker.
- sudo apt-get update
- sudo apt install -y gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
- sudo apt install -y gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget libgbm-dev
- echo "Setup machine for running puppeteer tests"
- sudo docker stop $(sudo docker ps -aq) || echo 'No docker containers'
- sudo docker rm $(sudo docker ps -aq) || echo 'No docker containers'
@@ -58,6 +59,7 @@ build_n_test_enterprise_accounts:
- sudo docker build -t fyipeproject/dashboard:3.0.$CI_PIPELINE_IID ./dashboard
- sudo docker run --env-file ./dashboard/.env -p 3000:3000 -d fyipeproject/dashboard:3.0.$CI_PIPELINE_IID
- sudo docker ps
- npm ci
- cd accounts
- npm ci
# try building, if there are warnings then this will fail

View File

@@ -20,6 +20,7 @@ mobile_lighthouse_accounts:
- sudo docker build -t fyipeproject/dashboard:3.0.$CI_PIPELINE_IID ./dashboard
- sudo docker run --env-file ./dashboard/.env -e IS_SAAS_SERVICE=true -p 3000:3000 -d fyipeproject/dashboard:3.0.$CI_PIPELINE_IID
- sudo docker ps
- npm ci
- cd accounts
- npm ci
- export CHROME_PATH="$(pwd)/node_modules/puppeteer/.local-chromium/linux-722234/chrome-linux/chrome"
@@ -57,6 +58,7 @@ desktop_lighthouse_accounts:
- sudo docker build -t fyipeproject/dashboard:3.0.$CI_PIPELINE_IID ./dashboard
- sudo docker run --env-file ./dashboard/.env -e IS_SAAS_SERVICE=true -p 3000:3000 -d fyipeproject/dashboard:3.0.$CI_PIPELINE_IID
- sudo docker ps
- npm ci
- cd accounts
- npm ci
- export CHROME_PATH="$(pwd)/node_modules/puppeteer/.local-chromium/linux-722234/chrome-linux/chrome"

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