Merge branch 'master' into ft-modify-script-monitor

This commit is contained in:
Nawaz Dhandala
2021-06-01 21:02:54 +01:00
101 changed files with 33210 additions and 2537 deletions

View File

@@ -241,7 +241,7 @@ export const PricingPlan = {
planId: 'plan_GoWIqpBpStiqQp',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
},
{
category: 'Growth',
@@ -255,21 +255,21 @@ export const PricingPlan = {
planId: 'plan_GoWKiTdQ6NiQFw',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
},
{
category: 'Scale',
planId: 'plan_H9Iox3l2YqLTDR',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IlBKhsFz4hV2',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
},
];
} else {
@@ -286,7 +286,7 @@ export const PricingPlan = {
planId: 'plan_GoVgJu5PKMLRJU',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
},
{
category: 'Growth',
@@ -300,21 +300,21 @@ export const PricingPlan = {
planId: 'plan_GoViZshjqzZ0vv',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
},
{
category: 'Scale',
planId: 'plan_H9Ii6Qj3HLdtty',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IjvX2Flsvlcg',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
},
];
}

View File

@@ -311,7 +311,7 @@ export const PricingPlan = {
planId: 'plan_GoWIqpBpStiqQp',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
},
{
category: 'Growth',
@@ -325,21 +325,21 @@ export const PricingPlan = {
planId: 'plan_GoWKiTdQ6NiQFw',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
},
{
category: 'Scale',
planId: 'plan_H9Iox3l2YqLTDR',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IlBKhsFz4hV2',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
},
];
} else {
@@ -356,7 +356,7 @@ export const PricingPlan = {
planId: 'plan_GoVgJu5PKMLRJU',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
},
{
category: 'Growth',
@@ -370,21 +370,21 @@ export const PricingPlan = {
planId: 'plan_GoViZshjqzZ0vv',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
},
{
category: 'Scale',
planId: 'plan_H9Ii6Qj3HLdtty',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IjvX2Flsvlcg',
type: 'annual',
amount: 1188,
details: '$1188/ Year / User',
details: '$99/mo per user paid annually. ',
},
];
}

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ const leadSchema = new Schema({
deletedAt: {
type: Date,
},
source: Object,
deletedById: { type: String, ref: 'User', index: true },
});
module.exports = mongoose.model('Lead', leadSchema);

View File

@@ -67,7 +67,7 @@ const statusSchema = new Schema({
// show or hide the probe bar
hideProbeBar: {
type: Boolean,
default: false,
default: true,
},
// show or hide uptime (%) on the status page
hideUptime: {

View File

@@ -1,98 +1,97 @@
const mongoose = require('../config/db');
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: { type: String, index: true },
email: String,
tempEmail: String,
password: String,
isVerified: {
type: Boolean,
default: false,
},
sso: { type: String, ref: 'Sso', index: true },
companyName: String,
companyRole: String,
companySize: String,
referral: String,
companyPhoneNumber: String,
airtableId: String,
onCallAlert: Array,
profilePic: String,
twoFactorAuthEnabled: { type: Boolean, default: false },
twoFactorSecretCode: String,
otpauth_url: String,
backupCodes: Array,
jwtRefreshToken: String,
stripeCustomerId: String,
resetPasswordToken: String,
resetPasswordExpires: String,
createdAt: {
type: Date,
default: Date.now,
},
timezone: String,
lastActive: {
type: Date,
default: Date.now,
},
coupon: String,
disabled: {
type: Boolean,
default: false,
},
paymentFailedDate: {
type: Date,
default: null,
},
role: {
type: String,
enum: ['master-admin', 'user'],
},
isBlocked: {
type: Boolean,
default: false,
},
adminNotes: [
{
note: { type: String },
createdAt: { type: Date },
const userSchema = new Schema(
{
name: { type: String, index: true },
email: String,
tempEmail: String,
password: String,
isVerified: {
type: Boolean,
default: false,
},
],
sso: { type: String, ref: 'Sso', index: true },
companyName: String,
companyRole: String,
companySize: String,
referral: String,
companyPhoneNumber: String,
deleted: { type: Boolean, default: false },
airtableId: String,
deletedAt: {
type: Date,
},
onCallAlert: Array,
profilePic: String,
deletedById: { type: String, ref: 'User', index: true },
alertPhoneNumber: {
type: String,
default: '',
},
alertPhoneVerificationCode: {
type: String,
default: '',
},
alertPhoneVerificationCodeRequestTime: {
type: Date,
},
tempAlertPhoneNumber: String,
tutorial: Object,
createdBy: { type: String, ref: 'User' },
identification: [
{
subscription: Object,
userAgent: String,
twoFactorAuthEnabled: { type: Boolean, default: false },
twoFactorSecretCode: String,
otpauth_url: String,
backupCodes: Array,
jwtRefreshToken: String,
stripeCustomerId: String,
resetPasswordToken: String,
resetPasswordExpires: String,
timezone: String,
lastActive: {
type: Date,
default: Date.now,
},
],
source: Object,
});
coupon: String,
disabled: {
type: Boolean,
default: false,
},
paymentFailedDate: {
type: Date,
default: null,
},
role: {
type: String,
enum: ['master-admin', 'user'],
},
isBlocked: {
type: Boolean,
default: false,
},
adminNotes: [
{
note: { type: String },
createdAt: { type: Date },
},
],
deleted: { type: Boolean, default: false },
deletedAt: {
type: Date,
},
deletedById: { type: String, ref: 'User', index: true },
alertPhoneNumber: {
type: String,
default: '',
},
alertPhoneVerificationCode: {
type: String,
default: '',
},
alertPhoneVerificationCodeRequestTime: {
type: Date,
},
tempAlertPhoneNumber: String,
tutorial: Object,
createdBy: { type: String, ref: 'User' },
identification: [
{
subscription: Object,
userAgent: String,
},
],
source: Object,
},
{ timestamps: true }
);
module.exports = mongoose.model('User', userSchema);

View File

@@ -50,6 +50,34 @@ module.exports = {
});
},
logLeads: function({
name,
country,
email,
fullname,
message,
phone,
source,
type,
volume,
website,
}) {
if (!base) return;
return base('Leads').create({
Name: name,
Email: email,
Phone: phone,
Country: country,
'Full Name': fullname,
Message: message,
Type: type,
Volume: volume,
Website: website,
Source: JSON.stringify(source),
});
},
deleteUser: function(airtableId) {
if (!base) return;

View File

@@ -306,14 +306,9 @@ module.exports = {
componentId: component._id,
});
await Promise.all(
monitors.map(async monitor => {
await MonitorService.deleteBy(
{ _id: monitor._id },
userId
);
})
);
for (const monitor of monitors) {
await MonitorService.deleteBy({ _id: monitor._id }, userId);
}
await NotificationService.create(
component.projectId,
`A Component ${component.name} was deleted from the project by ${component.deletedById.name}`,

View File

@@ -156,9 +156,10 @@ module.exports = {
// records is an array of arrays
// flatten the array to a single array
const txtRecords = flatten(records);
return txtRecords.some(
const result = txtRecords.some(
txtRecord => verificationToken === txtRecord
);
return { result, txtRecords };
} catch (error) {
if (error.code === 'ENODATA') {
throw {

View File

@@ -23,6 +23,7 @@ module.exports = {
lead.country = data.country;
lead.message = data.message;
lead.whitepaperName = data.whitepaperName;
lead.source = data.source;
lead.templateName = 'Request Demo';
if (data.whitepaperName) {
@@ -43,6 +44,17 @@ module.exports = {
); //whitepaper name should be stored in moreInfo.
}
}
AirtableService.logLeads({
name: data.name,
email: data.email,
phone: data.phone,
country: data.country,
message: data.message,
website: data.website,
source: data.source,
volume: data.companySize,
type: data.type,
});
return lead;
} catch (error) {
ErrorService.log('leadService.create', error);
@@ -64,3 +76,4 @@ module.exports = {
const LeadsModel = require('../models/lead');
const MailService = require('./mailService');
const ErrorService = require('./errorService');
const AirtableService = require('./airtableService');

View File

@@ -451,24 +451,21 @@ module.exports = {
const statusPages = await this.findBy({
'monitors.monitor': monitorId,
});
for (const statusPage of statusPages) {
const monitors = statusPage.monitors.filter(
monitorData =>
String(
monitorData.monitor._id || monitorData.monitor
) !== String(monitorId)
);
await Promise.all(
statusPages.map(async statusPage => {
const monitors = statusPage.monitors.filter(
monitorData =>
String(monitorData.monitor) !== String(monitorId)
if (monitors.length !== statusPage.monitors.length) {
await this.updateOneBy(
{ _id: statusPage._id },
{ monitors }
);
if (monitors.length !== statusPage.monitors.length) {
statusPage = await this.updateOneBy(
{ _id: statusPage._id },
{ monitors }
);
}
return statusPage;
})
);
}
}
} catch (error) {
ErrorService.log('statusPageService.removeMonitor', error);
throw error;

View File

@@ -297,7 +297,6 @@ module.exports = {
} else {
const newUser = await UserService.create({
email,
createdAt: Date.now(),
});
invitedTeamMembers.push(newUser);

View File

@@ -5,7 +5,7 @@ module.exports = function getSlug(name) {
name = String(name);
if (!name || !name.trim()) return;
let slug = slugify(name, { remove: /[*+~.()'"!:@]+/g });
let slug = slugify(name, { remove: /[&*+~.,\\/()|'"!:@]+/g });
slug = `${slug}-${generate('1234567890', 8)}`;
slug = slug.toLowerCase();

View File

@@ -6,7 +6,7 @@ This script rollbacks every project if any of the deployment fails
chmod +x ./ci/scripts/job-status.sh
function rollback {
export status=`./ci/scripts/job-status.sh deploy_production_$1`
export status=`./ci/scripts/job-status.sh production_$1`
if [[ $status == \"success\" ]]
then
echo "Rolling back $1"
@@ -17,7 +17,7 @@ function rollback {
}
function check {
export status=`./ci/scripts/job-status.sh deploy_production_$1`
export status=`./ci/scripts/job-status.sh production_$1`
if [[ $status == \"failed\" ]]
then
echo "Deployment unsuccessful for $1, rolling back all new deployments"

View File

@@ -6,7 +6,7 @@ This script rollbacks every project if any of the deployment fails
chmod +x ./ci/scripts/job-status.sh
function rollback {
export status=`./ci/scripts/job-status.sh deploy_staging_$1`
export status=`./ci/scripts/job-status.sh staging_$1`
if [[ $status == \"success\" ]]
then
echo "Rolling back $1"
@@ -23,7 +23,7 @@ function rollback {
}
function check {
export status=`./ci/scripts/job-status.sh deploy_staging_$1`
export status=`./ci/scripts/job-status.sh staging_$1`
if [[ $status == \"failed\" ]]
then
echo "Deployment unsuccessful for $1, rolling back all new deployments"

View File

@@ -1,4 +1,4 @@
deploy_production_accounts:
production_accounts:
stage: Deploy
retry: 2
allow_failure: true
@@ -28,7 +28,7 @@ deploy_production_accounts:
name: production
# DEPLOYMENT STAGE - Accounts
deploy_staging_accounts:
staging_accounts:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,4 +1,4 @@
deploy_production_admin-dashboard:
production_admin-dashboard:
stage: Deploy
allow_failure: true
script:
@@ -27,7 +27,7 @@ deploy_production_admin-dashboard:
name: production
# DEPLOYMENT STAGE - Admin Dashboard
deploy_staging_admin-dashboard:
staging_admin-dashboard:
stage: Deploy
allow_failure: true
script:

View File

@@ -1,4 +1,4 @@
deploy_production_api-docs:
production_api-docs:
stage: Deploy
retry: 2
allow_failure: true
@@ -28,7 +28,7 @@ deploy_production_api-docs:
name: production
# DEPLOYMENT STAGE - Api docs
deploy_staging_api-docs:
staging_api-docs:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
# DEPLOYMENT STAGE - BACKEND
deploy_staging_backend:
staging_backend:
stage: Deploy
allow_failure: true
retry: 2
@@ -29,7 +29,7 @@ deploy_staging_backend:
environment:
name: staging
deploy_production_backend:
production_backend:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
# DEPLOYMENT STAGE - Dashboard
deploy_staging_dashboard:
staging_dashboard:
stage: Deploy
allow_failure: true
retry: 2
@@ -29,7 +29,7 @@ deploy_staging_dashboard:
environment:
name: staging
deploy_production_dashboard:
production_dashboard:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
## DEPLOYMENT STAGE FOR STAGING - fyipe-acme-http-01-staging
deploy_staging_fyipe-acme-http-01:
staging_fyipe-acme-http-01:
stage: Deploy
allow_failure: true
retry: 2
@@ -27,7 +27,7 @@ deploy_staging_fyipe-acme-http-01:
name: staging
## DEPLOYMENT STAGE FOR PRODUCTION - fyipe-acme-http-01
deploy_production_fyipe-acme-http-01:
production_fyipe-acme-http-01:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
## DEPLOYMENT STAGE FOR STAGING - fyipe-gl-manager-staging
deploy_staging_fyipe-gl-manager:
staging_fyipe-gl-manager:
stage: Deploy
allow_failure: true
retry: 2
@@ -27,7 +27,7 @@ deploy_staging_fyipe-gl-manager:
name: staging
## DEPLOYMENT STAGE FOR PRODUCTION - fyipe-gl-manager
deploy_production_fyipe-gl-manager:
production_fyipe-gl-manager:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
## DEPLOYMENT STAGE FOR STAGING - fyipe-le-store-staging
deploy_staging_fyipe-le-store:
staging_fyipe-le-store:
stage: Deploy
allow_failure: true
retry: 2
@@ -27,7 +27,7 @@ deploy_staging_fyipe-le-store:
name: staging
## DEPLOYMENT STAGE FOR PRODUCTION - fyipe-le-store
deploy_production_fyipe-le-store:
production_fyipe-le-store:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
# DEPLOYMENT STAGE - Haraka
deploy_staging_haraka:
staging_haraka:
stage: Deploy
allow_failure: true
retry: 2
@@ -29,7 +29,7 @@ deploy_staging_haraka:
environment:
name: staging
deploy_production_haraka:
production_haraka:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
# DEPLOYMENT STAGE - HELM CHART
deploy_staging_helm-chart:
staging_helm-chart:
stage: Deploy
allow_failure: true
script:
@@ -36,7 +36,7 @@ deploy_staging_helm-chart:
environment:
name: staging
deploy_production_helm-chart:
production_helm-chart:
stage: Deploy
allow_failure: true
script:

View File

@@ -1,4 +1,4 @@
deploy_production_home:
production_home:
stage: Deploy
allow_failure: true
retry: 2
@@ -28,7 +28,7 @@ deploy_production_home:
name: production
# DEPLOYMENT STAGE - Home
deploy_staging_home:
staging_home:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
# DEPLOYMENT STAGE - HTTP-TEST-SERVER
deploy_staging_http_test_server:
staging_http_test_server:
stage: Deploy
allow_failure: true
script:
@@ -28,7 +28,7 @@ deploy_staging_http_test_server:
name: staging
# DEPLOYMENT STAGE - HTTP-TEST-SERVER
deploy_production_http_test_server:
production_http_test_server:
stage: Deploy
allow_failure: true
script:

View File

@@ -1,5 +1,5 @@
# DEPLOYMENT STAGE - Init Script
deploy_staging_init-script:
staging_init-script:
stage: Deploy
retry: 2
allow_failure: true
@@ -30,7 +30,7 @@ deploy_staging_init-script:
environment:
name: staging
deploy_production_init-script:
production_init-script:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
## DEPLOYMENT STAGE - JAVA SDK
deploy_staging_java-sdk:
staging_java-sdk:
stage: Deploy
allow_failure: true
retry: 2
@@ -41,7 +41,7 @@ deploy_staging_java-sdk:
environment:
name: staging
deploy_production_java-sdk:
production_java-sdk:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
## DEPLOYMENT STAGE - JS SDK
deploy_staging_js-sdk:
staging_js-sdk:
stage: Deploy
allow_failure: true
retry: 2
@@ -36,7 +36,7 @@ deploy_staging_js-sdk:
environment:
name: staging
deploy_production_js-sdk:
production_js-sdk:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
# DEPLOYMENT STAGE - LICENSING
deploy_staging_licensing:
staging_licensing:
stage: Deploy
allow_failure: true
retry: 2
@@ -29,7 +29,7 @@ deploy_staging_licensing:
environment:
name: staging
deploy_production_licensing:
production_licensing:
stage: Deploy
retry: 2
allow_failure: true

View File

@@ -1,5 +1,5 @@
## DEPLOYMENT STAGE - PHP SDK
deploy_staging_php-sdk:
staging_php-sdk:
stage: Deploy
allow_failure: true
retry: 2
@@ -36,7 +36,7 @@ deploy_staging_php-sdk:
environment:
name: staging
deploy_production_php-sdk:
production_php-sdk:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,4 +1,4 @@
deploy_production_probe:
production_probe:
stage: Deploy
retry: 2
allow_failure: true
@@ -29,7 +29,7 @@ deploy_production_probe:
name: production
# DEPLOYMENT STAGE - Probe
deploy_staging_probe:
staging_probe:
stage: Deploy
retry: 2
allow_failure: true

View File

@@ -1,5 +1,5 @@
## DEPLOYMENT STAGE - PYTHON SDK
deploy_staging_python-sdk:
staging_python-sdk:
stage: Deploy
allow_failure: true
retry: 2
@@ -34,7 +34,7 @@ deploy_staging_python-sdk:
name: staging
## DEPLOYMENT STAGE - PYTHON SDK
deploy_production_python-sdk:
production_python-sdk:
stage: Deploy
allow_failure: true
retry: 2

View File

@@ -1,5 +1,5 @@
# DEPLOYMENT STAGE - status-page
deploy_staging_status-page:
staging_status-page:
stage: Deploy
allow_failure: true
script:
@@ -28,7 +28,7 @@ deploy_staging_status-page:
environment:
name: staging
deploy_production_status-page:
production_status-page:
stage: Deploy
allow_failure: true
script:

View File

@@ -22,6 +22,9 @@ RUN npm ci --only=production
# Copy app source
COPY . /usr/src/app
# Install purgecss to mame CSS files smaller.
RUN npm install purgecss -g
# Bundle app source
RUN npm run build

29477
dashboard/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -89,6 +89,7 @@
"lint": "eslint .",
"dev": "PORT=3000 react-scripts start",
"build": "react-scripts build && npm run build-sw",
"postbuild": "purgecss --css build/assets/css/*.css --content build/index.html build/static/js/*.js --output build/assets/css",
"build-sw": "node ./src/sw-build.js",
"test": "jest --forceExit --testSequencer ./src/test/puppeteer/CustomSequencer.js --runInBand ./src/test/puppeteer/*.test.js",
"enterprise-test": "jest --forceExit --runInBand ./src/test/puppeteer/*.test.enterprise.js",
@@ -105,6 +106,7 @@
"fyipe": "^3.0.10051",
"jest-localstorage-mock": "^2.2.0",
"npm-force-resolutions": "0.0.3",
"purgecss": "^4.0.3",
"redux-mock-store": "^1.5.3",
"should": "^13.2.3"
},

View File

@@ -121,7 +121,8 @@ div.block-chart div.bar:last-child {
border-right: 0;
}
******* Tipsy Tooltips ******/ .tipsy {
/******* Tipsy Tooltips ******/
.tipsy {
font-size: 12px;
position: absolute;
padding: 5px;

View File

@@ -1,6 +1,12 @@
import { putApi, deleteApi } from '../api';
import * as types from '../constants/domain';
export function resetDomain() {
return {
type: types.RESET_VERIFY_DOMAIN,
};
}
export function verifyDomainRequest() {
return {
type: types.VERIFY_DOMAIN_REQUEST,

View File

@@ -670,7 +670,7 @@ export function closeIncidentSuccess(incident) {
}
export function closeIncident(projectId, incidentId) {
//This fucntion will switch to incidentId of the params beig passed.
//This function will switch to incidentId of the params beig passed.
return function(dispatch) {
const promise = postApi(
`incident/${projectId}/close/${incidentId}`,

View File

@@ -1,12 +1,19 @@
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { CrumbItem, Breadcrumbs } from 'react-breadcrumbs-dynamic';
import { PropTypes } from 'prop-types';
import { Spinner } from '../basic/Loader';
import ShouldRender from '../basic/ShouldRender';
function BreadCrumbs({ styles, showDeleteBtn, close, name }) {
const [loading, setLoading] = useState(false);
function BreadCrumbs({
styles,
showDeleteBtn,
close,
name,
closeIncidentRequest,
}) {
const [loading, setLoading] = useState(true);
const closeAllIncidents = async () => {
setLoading(true);
await close();
setLoading(false);
};
@@ -50,7 +57,8 @@ function BreadCrumbs({ styles, showDeleteBtn, close, name }) {
}}
/>
</div>
{showDeleteBtn && name === 'Home' && (
{loading && showDeleteBtn && name === 'Home' && (
<div
id="incidents-close-all-btn"
style={{ height: 'fit-content' }}
@@ -63,29 +71,34 @@ function BreadCrumbs({ styles, showDeleteBtn, close, name }) {
style={{ marginTop: '0' }}
></div>
Close all Resolved Incidents
<span style={{ marginLeft: '5px' }}>
{loading && (
<Spinner
style={{
stroke: '#000000',
}}
/>
)}
</span>
<span style={{ marginLeft: '5px' }}></span>
</span>
</div>
)}
<ShouldRender if={closeIncidentRequest.requesting !== false}>
<Spinner
style={{
stroke: '#000000',
}}
/>
</ShouldRender>
</div>
);
}
BreadCrumbs.displayName = 'BreadCrumbs';
const mapStateToProps = state => {
return {
closeIncidentRequest: state.incident.closeincident,
};
};
BreadCrumbs.propTypes = {
styles: PropTypes.string.isRequired,
showDeleteBtn: PropTypes.bool,
close: PropTypes.func,
name: PropTypes.string,
closeIncidentRequest: PropTypes.object,
};
export default BreadCrumbs;
export default connect(mapStateToProps, null)(BreadCrumbs);

View File

@@ -873,7 +873,7 @@ export class IncidentStatus extends Component {
<div className="bs-margin-right">
<span className="bs-content-create bs-text-bold">
Created
by
by{' '}
</span>
<span className=" bs-text-bold">
{this

View File

@@ -1,8 +1,9 @@
import { v4 as uuidv4 } from 'uuid';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { bindActionCreators, compose } from 'redux';
import { FormLoader } from '../basic/Loader';
import ShouldRender from '../basic/ShouldRender';
import { openModal, closeModal } from '../../actions/modal';
@@ -141,7 +142,7 @@ MonitorViewChangeComponentBox.propTypes = {
component: PropTypes.object,
};
export default connect(
mapStateToProps,
mapDispatchToProps
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(MonitorViewChangeComponentBox);

View File

@@ -392,6 +392,38 @@ class Search extends Component {
return 'db-SideNav-icon--appLog';
case 'Performance Tracker':
return 'db-SideNav-icon--performanceTracker';
case 'Home':
return 'db-SideNav-icon--home';
case 'Report':
return 'db-SideNav-icon--report';
case 'Back':
return 'db-SideNav-icon--back';
case 'Security':
return 'db-SideNav-icon--security';
case 'businessSettings':
return 'db-SideNav-icon--businessSettings';
case 'consulting':
return 'db-SideNav-icon--consulting';
case 'email':
return 'db-SideNav-icon--email';
case 'sms':
return 'db-SideNav-icon--sms';
case 'callrouting':
return 'db-SideNav-icon--callrouting';
case 'integration':
return 'db-SideNav-icon--integration';
case 'probes':
return 'db-SideNav-icon--probes';
case 'git':
return 'db-SideNav-icon--git';
case 'docker':
return 'db-SideNav-icon--docker';
case 'apis':
return 'db-SideNav-icon--apis';
case 'user':
return 'db-SideNav-icon--user';
case 'password':
return 'db-SideNav-icon--password';
default:
return '';
}

View File

@@ -93,11 +93,10 @@ export class CustomerBalance extends Component {
.then(response => {
const { status, amount_received } = response.data;
const { paymentIntent } = this.props;
if (status === 'succeeded') {
const creditedBalance = amount_received / 100;
getProjects();
getProjects().then( () =>
openModal({
id: MessageBoxId,
content: MessageBox,
@@ -105,7 +104,7 @@ export class CustomerBalance extends Component {
message: `Transaction successful, your balance is now ${(
balance + creditedBalance
).toFixed(2)}$`,
});
}));
} else {
this.handlePaymentIntent(paymentIntent.client_secret);
}

View File

@@ -116,12 +116,14 @@ let RenderMonitor = ({
id="monitor-name"
component={RenderSelect}
options={[
...allMonitors.map(m => ({
value: m._id,
label: `${
getParentComponent(m).name
} / ${m.name}`,
})),
...allMonitors
.filter(m => getParentComponent(m))
.map(m => ({
value: m._id,
label: `${
getParentComponent(m).name
} / ${m.name}`,
})),
]}
onChange={() => resetSelectedCharts()}
/>

View File

@@ -4,9 +4,12 @@ import PropTypes from 'prop-types';
import ClickOutside from 'react-click-outside';
import ShouldRender from '../basic/ShouldRender';
import { ListLoader } from '../basic/Loader';
import { resetDomain } from '../../actions/domain';
import { bindActionCreators } from 'redux';
class VerifyDomainModal extends Component {
componentDidMount() {
this.props.resetDomain();
window.addEventListener('keydown', this.handleKeyboard);
}
@@ -298,6 +301,10 @@ VerifyDomainModal.propTypes = {
domainField: PropTypes.object,
requesting: PropTypes.bool,
propArr: PropTypes.array,
resetDomain: PropTypes.func,
};
export default connect(mapStateToProps)(VerifyDomainModal);
const mapDispatchToProps = dispatch =>
bindActionCreators({ resetDomain }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(VerifyDomainModal);

View File

@@ -321,7 +321,7 @@ export const PricingPlan = {
planId: 'plan_GoWIqpBpStiqQp',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
title: 'Startup Plan',
},
{
@@ -336,7 +336,7 @@ export const PricingPlan = {
planId: 'plan_GoWKiTdQ6NiQFw',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
title: 'Growth Plan',
},
{
@@ -344,14 +344,14 @@ export const PricingPlan = {
planId: 'plan_H9Iox3l2YqLTDR',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IlBKhsFz4hV2',
type: 'annual',
amount: 1188,
details: '$1188 / Year / User',
details: '$99/mo per user paid annually. ',
title: 'Scale Plan',
},
];
@@ -369,7 +369,7 @@ export const PricingPlan = {
planId: 'plan_GoVgJu5PKMLRJU',
type: 'annual',
amount: 264,
details: '$264 / Year / User',
details: '$22/mo per user paid annually. ',
title: 'Startup Plan',
},
{
@@ -384,7 +384,7 @@ export const PricingPlan = {
planId: 'plan_GoViZshjqzZ0vv',
type: 'annual',
amount: 588,
details: '$588 / Year / User',
details: '$49/mo per user paid annually. ',
title: 'Growth Plan',
},
{
@@ -392,14 +392,14 @@ export const PricingPlan = {
planId: 'plan_H9Ii6Qj3HLdtty',
type: 'month',
amount: 99,
details: '$99 / Month / User',
details: '$120 / Month / User',
},
{
category: 'Scale',
planId: 'plan_H9IjvX2Flsvlcg',
type: 'annual',
amount: 1188,
details: '$1188 / Year / User',
details: '$99/mo per user paid annually. ',
title: 'Scale Plan',
},
];

View File

@@ -5,6 +5,7 @@ export const CREATE_DOMAIN_FAILURE = 'CREATE_DOMAIN_FAILURE';
export const VERIFY_DOMAIN_REQUEST = 'VERIFY_DOMAIN_REQUEST';
export const VERIFY_DOMAIN_SUCCESS = 'VERIFY_DOMAIN_SUCCESS';
export const VERIFY_DOMAIN_FAILURE = 'VERIFY_DOMAIN_FAILURE';
export const RESET_VERIFY_DOMAIN = 'RESET_VERIFY_DOMAIN';
export const DELETE_DOMAIN_REQUEST = 'DELETE_DOMAIN_REQUEST';
export const DELETE_DOMAIN_SUCCESS = 'DELETE_DOMAIN_SUCCESS';

View File

@@ -82,7 +82,7 @@ class Groups extends Component {
route={getParentRoute(pathname)}
name="Project Settings"
/>
<BreadCrumbItem route={pathname} name="Groups" />
<BreadCrumbItem route={pathname} name="Team Groups" />
<div className="Margin-vertical--12">
<div>
<div id="settingsPage">

View File

@@ -121,6 +121,7 @@ import {
VERIFY_DOMAIN_FAILURE,
VERIFY_DOMAIN_REQUEST,
VERIFY_DOMAIN_SUCCESS,
RESET_VERIFY_DOMAIN,
CREATE_DOMAIN_REQUEST,
CREATE_DOMAIN_SUCCESS,
CREATE_DOMAIN_FAILURE,
@@ -773,6 +774,17 @@ export default function statusPage(state = INITIAL_STATE, action) {
addMoreDomain: false,
};
case RESET_VERIFY_DOMAIN:
return {
...state,
verifyDomain: {
...state.verifyDomain,
requesting: false,
success: false,
error: null,
},
};
case VERIFY_DOMAIN_REQUEST:
return {
...state,

View File

@@ -8,6 +8,14 @@
=======================================================
# CHECKLIST
=======================================================
- Backup your database.
- Run this script:
```
# Deploy to production
git checkout hotfix-release

View File

@@ -0,0 +1,5 @@
// This is basicaly meant to get a cookie by name
var getCookiebyName = function (name) {
var pair = document.cookie.match(new RegExp(name + '=([^;]+)'));
return !!pair ? pair[1] : null;
};

View File

@@ -1,6 +1,7 @@
let accountsUrl = window.location.origin+'/accounts';
let backendUrl = window.location.origin+'/api'
let backendUrl = window.location.hostname==='localhost'? 'http://localhost:3002': window.location.origin+'/api'
//eslint-disable-next-line
function loginUrl(extra) {

View File

@@ -57,7 +57,7 @@
<div class="content">
<header>
<h1 style="line-height: unset; font-size: 43px !important;" class="Header-title common-PageTitle header-font header-size black">Monitor any API. Get alerted instantly when things don't look right.</h1>
<h1 style="line-height: unset; font-size: 43px !important;" class="Header-title common-PageTitle header-font header-size black">Monitor your API. Get alerted instantly when things don't look right.</h1>
<div style="margin-top: -100px">
<%- include("stars", {
description1: '"...monitors and parses *ANY* API response"',
@@ -280,7 +280,7 @@
by: "Sanjay Malik, Mindspoke"
},
{
quote: "Fyipe lets us shave critical minutes during downtime by alerting the right team member and escalating effec",
quote: "Fyipe lets us shave critical minutes during downtime by alerting the right team member and escalating effectively",
by: "Sarah Yusuf, Genesys"
},
{

View File

@@ -14,7 +14,7 @@
</p>
</div>
<div class="flex-Box">
<button style="margin-right: 5px; font-size: 10px;" class="common-Button"
<button id="accept-cookies" style="margin-right: 5px; font-size: 10px;" class="common-Button"
onclick="setCookie('acceptCookies', true, 365);" data-analytics-action="contact_sales"
data-analytics-source="header_cta">
Accept Cookies

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -161,17 +161,17 @@
for (const param of params) {
sourceObj = { ...sourceObj, [`${param[0]}`]: param[1] }
}
// Set a Cookie
function setCookie(cName, cValue, expDays) {
let date = new Date();
date.setTime(date.getTime() + (expDays * 24 * 60 * 60 * 1000));
const expires = "expires=" + date.toUTCString();
// Remember to set back the domain since you used localhost for development purposes
document.cookie = cName + "=" + cValue + "; " + expires + ";" + "; " + "domain=" + window.location.hostname;
}
// Apply setCookie
setCookie('source', JSON.stringify(sourceObj), 1);
}
// Set a Cookie
function setCookie(cName, cValue, expDays) {
let date = new Date();
date.setTime(date.getTime() + (expDays * 24 * 60 * 60 * 1000));
const expires = "expires=" + date.toUTCString();
document.cookie = cName + "=" + cValue + "; " + expires + "; path=/" + "; " + "domain="+window.location.host;
}
// Apply setCookie
setCookie('source', JSON.stringify(sourceObj), 1);
</script>
<img height="1" width="1" style="display:none" alt="Facebook"
src="https://www.facebook.com/tr?id=244894116420899&ev=PageView&noscript=1" />

View File

@@ -245,6 +245,9 @@
<img src="/img/v3/pricing/header/plan-star.svg" width="26" height="26" alt="fees icon"> Basic
On-Call Scheduling.
</li>
<li class="Plan-listItem">
<img src="/img/v3/pricing/header/plan-star.svg" width="26" height="26" alt="fees icon">Unlimited Subscribers and Status Page Viewers
</li>
</ul>
<div class="Plan-cardPricing">
@@ -308,6 +311,9 @@
<img src="/img/v3/pricing/header/plan-star.svg" width="26" height="26" alt="fees icon"> Advanced
On-Call Scheduling.
</li>
<li class="Plan-listItem">
<img src="/img/v3/pricing/header/plan-star.svg" width="26" height="26" alt="fees icon">Unlimited Subscribers and Status Page Viewers
</li>
</ul>
<div class="Plan-cardPricing">
@@ -370,6 +376,9 @@
<img src="/img/v3/pricing/header/plan-star.svg" width="26" height="26" alt="fees icon"> Advanced
On-Call Scheduling.
</li>
<li class="Plan-listItem">
<img src="/img/v3/pricing/header/plan-star.svg" width="26" height="26" alt="fees icon">Unlimited Subscribers and Status Page Viewers
</li>
</ul>
<div class="Plan-cardPricing">
@@ -659,6 +668,22 @@
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
</tr>
<tr class="pricing-compare-table">
<td class="pricing-compare-table">
<span class="pricing-compare-table font14">
Unlimited Private Status Page viewers <br />
<i>Add unlimited internal employees to view your private status page.</i>
</span>
</td>
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
<td class="default pricing-compare-table"><span class="tick pricing-compare-table"><img height="20"
width="20" src="/img/tick.svg" alt="yes" /></span></td>
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
</tr>
<tr class="pricing-compare-table">
<td class="pricing-compare-table">
<span class="pricing-compare-table font14">
@@ -1115,7 +1140,40 @@
</span>
</td>
</tr>
<tr class="pricing-compare-table">
<td class="pricing-compare-table">
<span class="pricing-compare-table font14">
Cancel Anytime <br />
<i>Cancel anytime, no questions asked.</i>
</span>
</td>
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
<td class="default pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
</tr>
<tr class="pricing-compare-table">
<td class="pricing-compare-table">
<span class="pricing-compare-table font14">
Discounted Annual Plan <br />
<i>Save 18% with an annual plan.</i>
</span>
</td>
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
<td class="default pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
<td class="pricing-compare-table"><span class="tick pricing-compare-table"><img height="20" width="20"
src="/img/tick.svg" alt="yes" /></span></td>
</tr>
<tr class="pricing-compare-table">
<td class="pricing-compare-table">
<span class="pricing-compare-table font14">

View File

@@ -2,7 +2,7 @@
<html lang="en" id="contact">
<head>
<title>Fyipe | Monitor your Website's every second</title>
<title>Fyipe | Monitor your website every second</title>
<meta name="description"
content="Fyipe monitors websites, API's, and servers and alerts your team if something goes wrong. It also keeps your customers updated about any downtime.">
@@ -17,26 +17,28 @@
amplitude.getInstance().logEvent('PAGE VIEW: HOME > PRODUCT > WEBSITE MONITORING');
}
</script>
<script>
function onClickFunc() {
let playButton = document.getElementById("playButton");
let gif = document.getElementById("uptime-monitoring-gif");
let videoDiv= document.getElementById("videoDiv");
let video= document.getElementById("video");
<script>
function onClickFunc(index) {
let playButton = document.getElementById(`playButton-${index}`);
let gif = document.getElementById(`gif-${index}`);
let videoDiv= document.getElementById(`videoDiv-${index}`);
let video= document.getElementById(`video-${index}`);
let grid = document.getElementById(`grid-${index}`);
playButton.style.display = "none";
gif.style.display="none";
videoDiv.style.display="block";
// grid.style.width="1000px";
video.src += "?autoplay=1";
}
function mouseOverFunc()
function mouseOverFunc(index)
{
let playButton = document.getElementById("playButton");
let playButton = document.getElementById(`playButton-${index}`);
playButton.src="/img/play-button-black.svg";
playButton.style.cursor="pointer";
}
function mouseOutFunc()
function mouseOutFunc(index)
{
let playButton = document.getElementById("playButton");
let playButton = document.getElementById(`playButton-${index}`);
playButton.src="/img/play-button-grey.svg";
}
</script>
@@ -49,29 +51,86 @@
<div class="globalContent">
<div id="main-content">
<section class="marquee nav-animation-element">
<div id="bubble-overlay">
<div class="content">
<header>
<h1 class="Header-title common-PageTitle header-font header-size black">Website Monitoring</h1>
<p style="margin-top:50px"><i>Downtime happens.</i> <span class="highlight">&nbsp; Fyipe's monitoring lets you know exactly what is wrong with your service, at the right time, </span> saving you critical minutes during downtime. </p>
<h1 style="line-height: unset; font-size: 43px !important;" class="Header-title common-PageTitle header-font header-size black">Monitor your website. Get alerted instantly when things don't look right.</h1>
<div style="margin-top: -100px">
<%- include("stars", {
description1: '"...monitors a lot of website metrics"',
name1: "- AlternativeTo",
link1: "https://alternativeto.net/software/fyipe/about",
description2: '"...does so much more than just monitoring."',
name2: "- Capterra",
link2: "https://www.capterra.com/p/176513/Fyipe/",
description3: '"...alerts the second our website goes down."',
name3: "- G2 Crowd",
link3: "https://www.g2.com/products/fyipe/reviews"
}) %>
</div>
<p style="margin-top:50px; font-size: 20px"><span class="highlight">Analyze uptime &amp; performance of your website.</span> Improve poorly performing pages to improve your app. Get alerted by SMS, Email or Call when things go wrong.</p>
</header>
</div>
</div>
<div style="
margin: 0 auto;
;">
<div class="checkbox-items" style="max-width: 1000px; margin: auto;">
<div class="checkbox-feature top-margin-10">
<div><img height="22" src="/img/check-mark-button.svg" /> </div> <div
class="checkbox-text">Unlimited Website Monitoring</div>
</div>
<div class="checkbox-feature top-margin-10">
<div><img height="22" src="/img/check-mark-button.svg" /></div> <div
class="checkbox-text">Get Email, SMS &amp; Call alerts</div>
</div>
<div class="checkbox-feature top-margin-10">
<div><img height="22" src="/img/check-mark-button.svg" /></div> <div
class="checkbox-text">Monitor on any criteria </div>
</div>
</div>
<div class="checkbox-items" style="max-width: 1000px; margin: auto;">
<div class="checkbox-feature top-margin-10">
<div><img height="22" src="/img/check-mark-button.svg" /> </div> <div
class="checkbox-text">Performance Reports</div>
</div>
<div class="checkbox-feature top-margin-10">
<div><img height="22" src="/img/check-mark-button.svg" /></div> <div
class="checkbox-text">SSL Monitoring</div>
</div>
<div class="checkbox-feature top-margin-10">
<div><img height="22" src="/img/check-mark-button.svg" /></div> <div
class="checkbox-text">SEO Monitoring</div>
</div>
</div>
</div>
</div>
</section>
<div class="gif-container">
<img src="/img/animated-gif/uptime-monitoring.gif" class="imageshadow" alt="Uptime Monitoring" id="uptime-monitoring-gif" style="border-radius: 5px;width: 100%;" />
<img class="play" id="playButton" onmouseout="mouseOutFunc()"onmouseover="mouseOverFunc()"onclick="onClickFunc()" src="/img/play-button-grey.svg"/>
<div id="videoDiv" class="videoClass">
<iframe id="video" width="850" height="500" src="https://www.youtube.com/embed/C1WcqtlG0AM?modestbranding=1&rel=0&enablejsapi=1&controls=1&frameborder=0&allowfullscreen=1" rel="0" enablejsapi="1" modestbranding="1" controls="1" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<div class="vid_section">
<div class="grid-1"></div>
<div class="grid-2 text-center" id="grid-1">
<img src="/img/animated-gif/uptime-monitoring.gif" class="imageshadow" alt="Uptime Monitoring" id="gif-1" style="border-radius: 5px;width: 100%;" />
<img class="play" style="z-index: 3" id="playButton-1" onmouseout="mouseOutFunc(1)"onmouseover="mouseOverFunc(1)"onclick="onClickFunc(1)" src="/img/play-button-grey.svg"/>
<div id="videoDiv-1" class="videoClass">
<iframe id="video" width="850" height="500" src="https://www.youtube.com/embed/C1WcqtlG0AM?modestbranding=1&rel=0&enablejsapi=1&controls=1&frameborder=0&allowfullscreen=1" rel="0" enablejsapi="1" modestbranding="1" controls="1" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
</div>
<div class="grid-3">
<img src="/img/3mindemobg.png" />
</div>
</div>
<% include ./cta-buttons %>
@@ -115,6 +174,10 @@
<% include ./cta-buttons %>
<% include ./customer-section %>
<%- include('feature-table', {
title:"We don't just do Website Monitoring, <br/><span class='highlight'> We do <i>everything</i> in terms of SRE and DevOps. </span>",
description: "Start with website monitoring and then expand into whole lot more without increasing your bottom line. <br/> One interface. One conversation. One permission model. Thousands of features. "
}) %>
<div class="sectionBorderTop">
<% include ./comparision-chart %>
</div>
@@ -209,6 +272,33 @@
</div>
<% include ./cta-buttons %>
<%- include("testimonials", {
title: "Software teams around the world love Fyipe!",
description: "Software development teams around the world have switched to Fyipe to fundamentally improve their SRE workflow to build better and more reliable software.",
quotes: [
{
quote: "Having website and app reliability alerts lets our team be more accountable and responsible to software they are building. They now own the code.",
by: "Sanjay Malik, Mindspoke"
},
{
quote: "Fyipe lets us shave critical minutes during downtime by alerting the right team member and escalating effectively",
by: "Sarah Yusuf, Genesys"
},
{
quote: "No more writing and maintaining internal tools to monitor our software. Fyipe let's us focus on our business.",
by: "Jennifer Jones, Improbable"
},
{
quote: "Incident management is now a breeze. Everyone in the company knows what is wrong and what needs to be fixed.",
by: "Shubham Aggarwal, CloudBoost"
},
{
quote: "We can see exactly what the state of the software and our service is. Less confusion and we're much more efficient.",
by: "Trevos Crosse, PNI Digital"
}
]
}) %>
<header style="padding-top:50px; border-top: 1px solid rgba(0,0,0,.07); border-bottom:unset">
<h2 class="header">How monitoring helps your business?</h2>

View File

@@ -41,7 +41,7 @@ async function run() {
await removeField(
statusPageCollection,
{ _id: statusPage._id },
{ domain: '' }
'domain'
);
}

View File

@@ -37,7 +37,7 @@ async function run() {
await removeField(
statusPageCollection,
{ _id: statusPage._id },
{ monitorIds: '' }
'monitorIds'
);
}
}

View File

@@ -21,7 +21,7 @@ async function run() {
await removeField(
scheduledEventCollection,
{ _id: event._id },
{ monitorId: '' }
'monitorId'
);
});

View File

@@ -19,7 +19,7 @@ async function run() {
await removeField(
scheduledEventNoteCollection,
{ _id: eventNote._id },
{ incident_state: '' }
'incident_state'
);
});
}

View File

@@ -26,7 +26,7 @@ async function run() {
await removeField(
monitorCollection,
{ _id: monitor._id },
{ monitorCategoryId: '' }
'monitorCategoryId'
);
});

View File

@@ -15,11 +15,7 @@ async function run() {
await update(monitorCollection, { _id: monitor._id }, data);
await removeField(
monitorCollection,
{ _id: monitor._id },
{ variables: '' }
);
await removeField(monitorCollection, { _id: monitor._id }, 'variables');
}
return `Script ran for ${monitors.length} monitors`;

View File

@@ -23,7 +23,19 @@ async function run() {
await removeField(
incomingRequestCollection,
{ _id: request._id },
{ filterCriteria: '', filterCondition: '', filterText: '' }
'filterCriteria'
);
await removeField(
incomingRequestCollection,
{ _id: request._id },
'filterCondition'
);
await removeField(
incomingRequestCollection,
{ _id: request._id },
'filterText'
);
}

View File

@@ -6,7 +6,7 @@ async function run() {
const monitors = await find(monitorCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (let i = 0; i < monitors.length; i++) {

View File

@@ -6,7 +6,7 @@ async function run() {
const applicationSecurities = await find(applicationSecurityCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (let i = 0; i < applicationSecurities.length; i++) {

View File

@@ -6,7 +6,7 @@ async function run() {
const containerSecurities = await find(containerSecurityCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (let i = 0; i < containerSecurities.length; i++) {

View File

@@ -6,7 +6,7 @@ async function run() {
const logContainers = await find(logContainerCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (let i = 0; i < logContainers.length; i++) {

View File

@@ -6,7 +6,7 @@ async function run() {
const errorTrackers = await find(errortrackerCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (let i = 0; i < errorTrackers.length; i++) {

View File

@@ -6,7 +6,7 @@ async function run() {
const components = await find(componentCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (let i = 0; i < components.length; i++) {

View File

@@ -6,7 +6,7 @@ async function run() {
const projects = await find(projectCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (const project of projects) {

View File

@@ -14,7 +14,7 @@ async function run() {
{ remoteLoginUrl: sso.samlSsoUrl }
);
await removeField(ssoCollection, { _id: sso._id }, { samlSsoUrl: '' });
await removeField(ssoCollection, { _id: sso._id }, 'samlSsoUrl');
}
return `Script ran for ${ssos.length} ssos`;

View File

@@ -6,7 +6,7 @@ async function run() {
const statusPages = await find(statusPageCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (let i = 0; i < statusPages.length; i++) {

View File

@@ -6,7 +6,7 @@ async function run() {
const schedules = await find(schedulesCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (let i = 0; i < schedules.length; i++) {

View File

@@ -7,7 +7,7 @@ async function run() {
const items = await find(scheduledCollection, {
$or: [
{ slug: { $exists: false } },
{ slug: { $regex: /[*+~.()'"!:@]+/g } },
{ slug: { $regex: /[&*+~.,\\/()|'"!:@]+/g } },
],
});
for (let i = 0; i < items.length; i++) {

View File

@@ -33,12 +33,12 @@ async function removeMany(collection, query) {
async function removeField(collection, query, field) {
return global.db
.collection(collection)
.updateOne(query, { $unset: field }, { multi: true });
.updateOne(query, { $unset: { [field]: '' } }, { multi: true });
}
async function removeFieldsFromMany(collection, query, field) {
return global.db
.collection(collection)
.updateMany(query, { $unset: field }, { multi: true });
.updateMany(query, { $unset: { [field]: '' } }, { multi: true });
}
async function rename(oldCollectionName, newCollectionName) {

View File

@@ -5,7 +5,7 @@ module.exports = function getSlug(name) {
name = String(name);
if (!name || !name.trim()) return;
let slug = slugify(name, { remove: /[*+~.()'"!:@]+/g });
let slug = slugify(name, { remove: /[&*+~.,\\/()|'"!:@]+/g });
slug = `${slug}-${generate('1234567890', 8)}`;
slug = slug.toLowerCase();

View File

@@ -1,42 +1,13 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="451.847px" height="451.847px" viewBox="0 0 451.847 451.847" style="enable-background:new 0 0 451.847 451.847;"
xml:space="preserve">
<g>
<path d="M225.923,354.706c-8.098,0-16.195-3.092-22.369-9.263L9.27,151.157c-12.359-12.359-12.359-32.397,0-44.751
c12.354-12.354,32.388-12.354,44.748,0l171.905,171.915l171.906-171.909c12.359-12.354,32.391-12.354,44.744,0
c12.365,12.354,12.365,32.392,0,44.751L248.292,345.449C242.115,351.621,234.018,354.706,225.923,354.706z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
<g>
<polyline fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="bevel" stroke-miterlimit="10" points="15,24 32,41
49,24 "/>
</g>
<g>
<circle fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" cx="32" cy="32" r="30.999"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 988 B

After

Width:  |  Height:  |  Size: 734 B

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
<g>
<polyline fill="none" stroke="#000000" stroke-width="2" stroke-linejoin="bevel" stroke-miterlimit="10" points="15,40 32,23
49,40 "/>
</g>
<g>
<circle fill="none" stroke="#000000" stroke-width="2" stroke-miterlimit="10" cx="32" cy="32" r="30.999"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 734 B

View File

@@ -1135,9 +1135,16 @@ margin-left:10px;
padding-left: 30px;
}
.sp__icon--forward {
background-image: url('../images/right-arrow.svg');
.sp__icon--down {
background-image: url('../images/arrow-down.svg');
background-repeat: no-repeat;
margin-top:0
}
.sp__icon--up {
background-image: url('../images/up-arrow.svg');
background-repeat: no-repeat;
margin-top:0
}
.sp__icon--forward:hover {

View File

@@ -0,0 +1,376 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import setInTransition from './setInTransition';
class Collapsible extends Component {
constructor(props) {
super(props);
this.timeout = undefined;
// Defaults the dropdown to be closed
if (props.open) {
this.state = {
isClosed: false,
shouldSwitchAutoOnNextCycle: false,
height: 'auto',
transition: 'none',
hasBeenOpened: true,
overflow: props.overflowWhenOpen,
inTransition: false,
};
} else {
this.state = {
isClosed: true,
shouldSwitchAutoOnNextCycle: false,
height: 0,
transition: `height ${props.transitionTime}ms ${props.easing}`,
hasBeenOpened: false,
overflow: 'hidden',
inTransition: false,
};
}
}
componentDidUpdate(prevProps, prevState) {
if (this.state.shouldOpenOnNextCycle) {
this.continueOpenCollapsible();
}
if (
(prevState.height === 'auto' || prevState.height === 0) &&
this.state.shouldSwitchAutoOnNextCycle === true
) {
window.clearTimeout(this.timeout);
this.timeout = window.setTimeout(() => {
// Set small timeout to ensure a true re-render
this.setState({
height: 0,
overflow: 'hidden',
isClosed: true,
shouldSwitchAutoOnNextCycle: false,
});
}, 50);
}
// If there has been a change in the open prop (controlled by accordion)
if (prevProps.open !== this.props.open) {
if (this.props.open === true) {
this.openCollapsible();
this.props.onOpening();
} else {
this.closeCollapsible();
this.props.onClosing();
}
}
}
componentWillUnmount() {
window.clearTimeout(this.timeout);
}
closeCollapsible() {
const { innerRef } = this;
this.setState({
shouldSwitchAutoOnNextCycle: true,
height: innerRef.scrollHeight,
transition: `height ${
this.props.transitionCloseTime
? this.props.transitionCloseTime
: this.props.transitionTime
}ms ${this.props.easing}`,
inTransition: setInTransition(innerRef.scrollHeight),
});
}
openCollapsible() {
this.setState({
inTransition: setInTransition(this.innerRef.scrollHeight),
shouldOpenOnNextCycle: true,
});
}
continueOpenCollapsible = () => {
const { innerRef } = this;
this.setState({
height: innerRef.scrollHeight,
transition: `height ${this.props.transitionTime}ms ${this.props.easing}`,
isClosed: false,
hasBeenOpened: true,
inTransition: setInTransition(innerRef.scrollHeight),
shouldOpenOnNextCycle: false,
});
};
handleTriggerClick = event => {
if (this.props.triggerDisabled || this.state.inTransition) {
return;
}
event.preventDefault();
if (this.props.handleTriggerClick) {
this.props.handleTriggerClick(this.props.accordionPosition);
} else {
if (this.state.isClosed === true) {
this.openCollapsible();
this.props.onOpening();
this.props.onTriggerOpening();
} else {
this.closeCollapsible();
this.props.onClosing();
this.props.onTriggerClosing();
}
}
};
renderNonClickableTriggerElement() {
if (
this.props.triggerSibling &&
typeof this.props.triggerSibling === 'string'
) {
return (
<span
className={`${this.props.classParentString}__trigger-sibling`}
>
{this.props.triggerSibling}
</span>
);
} else if (
this.props.triggerSibling &&
typeof this.props.triggerSibling === 'function'
) {
return this.props.triggerSibling();
} else if (this.props.triggerSibling) {
return <this.props.triggerSibling />;
}
return null;
}
handleTransitionEnd = e => {
// only handle transitions that origin from the container of this component
if (e.target !== this.innerRef) {
return;
}
// Switch to height auto to make the container responsive
if (!this.state.isClosed) {
this.setState({
height: 'auto',
overflow: this.props.overflowWhenOpen,
inTransition: false,
});
this.props.onOpen();
} else {
this.setState({ inTransition: false });
this.props.onClose();
}
};
setInnerRef = ref => (this.innerRef = ref);
render() {
const dropdownStyle = {
height: this.state.height,
WebkitTransition: this.state.transition,
msTransition: this.state.transition,
transition: this.state.transition,
overflow: this.state.overflow,
margin: '0 35px',
};
const openClass = this.state.isClosed ? 'is-closed' : 'is-open';
const disabledClass = this.props.triggerDisabled ? 'is-disabled' : '';
//If user wants different text when tray is open
const trigger =
this.state.isClosed === false &&
this.props.triggerWhenOpen !== undefined
? this.props.triggerWhenOpen
: this.props.trigger;
const ContentContainerElement = this.props.contentContainerTagName;
// If user wants a trigger wrapping element different than 'span'
const TriggerElement = this.props.triggerTagName;
// Don't render children until the first opening of the Collapsible if lazy rendering is enabled
const children =
this.props.lazyRender &&
!this.state.hasBeenOpened &&
this.state.isClosed &&
!this.state.inTransition
? null
: this.props.children;
// Construct CSS classes strings
const triggerClassString = `${
this.props.classParentString
}__trigger ${openClass} ${disabledClass} ${
this.state.isClosed
? this.props.triggerClassName
: this.props.triggerOpenedClassName
}`;
const parentClassString = `${this.props.classParentString} ${
this.state.isClosed
? this.props.className
: this.props.openedClassName
}`;
const outerClassString = `${this.props.classParentString}__contentOuter ${this.props.contentOuterClassName}`;
const innerClassString = `${this.props.classParentString}__contentInner ${this.props.contentInnerClassName}`;
return (
<ContentContainerElement
className={parentClassString.trim()}
{...this.props.containerElementProps}
>
<TriggerElement
className={triggerClassString.trim()}
onClick={this.handleTriggerClick}
style={
!this.state.isClosed
? { ...this.props.triggerStyle, marginBottom: 25 }
: this.props.triggerStyle
}
onKeyPress={event => {
const { key } = event;
if (
(key === ' ' &&
this.props.triggerTagName.toLowerCase() !==
'button') ||
key === 'Enter'
) {
this.handleTriggerClick(event);
}
}}
tabIndex={this.props.tabIndex && this.props.tabIndex}
{...this.props.triggerElementProps}
>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<div
className={
this.state.isClosed
? this.props.closedIconClass
: this.props.openIconClass
}
style={{ marginRight: '5px ' }}
/>
{trigger}
</div>
<div>
<div style={this.props.statusColorStyle}></div>
</div>
</TriggerElement>
{this.renderNonClickableTriggerElement()}
<div
className={outerClassString.trim()}
style={dropdownStyle}
onTransitionEnd={this.handleTransitionEnd}
ref={this.setInnerRef}
hidden={
this.props.contentHiddenWhenClosed &&
this.state.isClosed &&
!this.state.inTransition
}
>
<div className={innerClassString.trim()}>{children}</div>
</div>
</ContentContainerElement>
);
}
}
Collapsible.propTypes = {
transitionTime: PropTypes.number,
transitionCloseTime: PropTypes.number,
triggerTagName: PropTypes.string,
easing: PropTypes.string,
open: PropTypes.bool,
containerElementProps: PropTypes.object,
triggerElementProps: PropTypes.object,
classParentString: PropTypes.string,
openedClassName: PropTypes.string,
triggerStyle: PropTypes.object,
statusColorStyle: PropTypes.object,
triggerClassName: PropTypes.string,
triggerOpenedClassName: PropTypes.string,
contentOuterClassName: PropTypes.string,
contentInnerClassName: PropTypes.string,
accordionPosition: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
handleTriggerClick: PropTypes.func,
onOpen: PropTypes.func,
onClose: PropTypes.func,
onOpening: PropTypes.func,
onClosing: PropTypes.func,
onTriggerOpening: PropTypes.func,
onTriggerClosing: PropTypes.func,
trigger: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
triggerWhenOpen: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
triggerDisabled: PropTypes.bool,
lazyRender: PropTypes.bool,
overflowWhenOpen: PropTypes.oneOf([
'hidden',
'visible',
'auto',
'scroll',
'inherit',
'initial',
'unset',
]),
contentHiddenWhenClosed: PropTypes.bool,
className: PropTypes.string,
children: PropTypes.object,
triggerSibling: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
tabIndex: PropTypes.number,
contentContainerTagName: PropTypes.string,
closedIconClass: PropTypes.string,
openIconClass: PropTypes.string,
};
Collapsible.defaultProps = {
transitionTime: 400,
transitionCloseTime: null,
triggerTagName: 'span',
easing: 'linear',
open: false,
classParentString: 'Collapsible',
triggerDisabled: false,
lazyRender: false,
overflowWhenOpen: 'hidden',
contentHiddenWhenClosed: false,
openedClassName: '',
triggerStyle: null,
triggerClassName: '',
triggerOpenedClassName: '',
contentOuterClassName: '',
contentInnerClassName: '',
className: '',
triggerSibling: null,
onOpen: () => {},
onClose: () => {},
onOpening: () => {},
onClosing: () => {},
onTriggerOpening: () => {},
onTriggerClosing: () => {},
tabIndex: null,
contentContainerTagName: 'div',
closedIconClass: '',
openIconClass: '',
statusColorStyle: {},
};
export default Collapsible;

View File

@@ -0,0 +1,3 @@
const setInTransition = innerRefScrollHeight => innerRefScrollHeight !== 0;
export default setInTransition;

View File

@@ -7,7 +7,13 @@ import ShouldRender from './ShouldRender';
import Footer from './Footer';
import NotesMain from './NotesMain';
import EventsMain from './EventsMain';
import { API_URL, ACCOUNTS_URL, getServiceStatus } from '../config';
import {
API_URL,
ACCOUNTS_URL,
getServiceStatus,
filterProbeData,
getMonitorStatus,
} from '../config';
import moment from 'moment';
import { Helmet } from 'react-helmet';
import { bindActionCreators } from 'redux';
@@ -27,6 +33,7 @@ import AnnouncementLogs from './AnnouncementLogs';
import PastEvent from './PastEvent';
import { fetchFutureEvents, fetchPastEvents } from '../actions/status';
import OngoingSchedule from './OngoingSchedule';
import Collapsible from './Collapsible/Collapsible';
const greenBackground = {
display: 'inline-block',
@@ -183,26 +190,17 @@ class Main extends Component {
this.setLastAlive();
}
groupBy(collection, property) {
let i = 0,
val,
index;
const values = [],
result = [];
for (; i < collection.length; i++) {
val = collection[i][property]
? collection[i][property]['name']
: 'no-category';
index = values.indexOf(val);
if (index > -1) {
result[index].push(collection[i]);
} else {
values.push(val);
result.push([collection[i]]);
getCategories(collection, property) {
const collectionArray = [];
collection.forEach(monitor => {
if (
monitor[property] &&
collectionArray.indexOf(monitor[property].name) === -1
) {
collectionArray.push(monitor[property].name);
}
}
return result;
});
return collectionArray;
}
groupedMonitors = () => {
@@ -212,83 +210,168 @@ class Main extends Component {
this.props.statusData.monitorsData.length > 0
) {
const monitorData = this.props.statusData.monitorsData;
const groupedMonitorData = this.groupBy(
const resourceCategories = this.getCategories(
monitorData,
'monitorCategoryId'
'resourceCategory'
);
const uncategorized = monitorData.filter(
mon =>
mon.resourceCategory === undefined || !mon.resourceCategory
);
return (
<div
className="uptime-graph-header"
style={{ flexDirection: 'column' }}
>
{resourceCategories.map(categoryName => {
const filteredResource = monitorData.filter(
resource =>
resource.resourceCategory &&
resource.resourceCategory.name === categoryName
);
return this.CollapsableGroup(
categoryName,
filteredResource
);
})}
{uncategorized &&
uncategorized.length > 0 &&
this.CollapsableGroup('Uncategorized', uncategorized)}
</div>
);
return groupedMonitorData.map((groupedMonitors, i) => {
return (
<div
key={i}
className="uptime-graph-header"
style={{ flexDirection: 'column' }}
>
{groupedMonitors.map((monitor, i) => {
return (
<>
<MonitorInfo
monitor={monitor}
selectedCharts={
this.props.monitors.filter(
m =>
monitor._id ===
m.monitor._id
)[0]
}
key={i}
id={`monitor${i}`}
resourceCategory={
monitor.resourceCategory
}
isGroupedByMonitorCategory={
this.props.statusData
.isGroupedByMonitorCategory
}
theme={
this.props.statusData.theme ===
'Clean Theme'
? true
: false
}
/>
{this.props.monitors.some(
m => monitor._id === m.monitor._id
) && (
<LineChartsContainer
monitor={monitor}
selectedCharts={
this.props.monitors.filter(
m =>
monitor._id ===
m.monitor._id
)[0]
}
/>
)}
{i <
this.props.statusData.monitorsData
.length -
1 && (
<div
style={{
margin: '30px 0px',
backgroundColor:
'rgb(232, 232, 232)',
height: '1px',
}}
/>
)}
</>
);
})}
</div>
);
});
} else {
return <NoMonitor />;
}
};
CollapsableGroup = (categoryName, monitors) => {
const { probes, activeProbe, statusData } = this.props;
const theme = statusData.theme === 'Clean Theme' ? true : false;
const categoryStatuses = monitors.map(monitor => {
const probe =
probes && probes.length > 0
? probes[probes.length < 2 ? 0 : activeProbe]
: null;
const statuses = filterProbeData(monitor, probe);
const monitorStatus = getMonitorStatus(statuses);
return monitorStatus;
});
const categoryStatusBk = categoryStatuses.includes('offline')
? 'rgba(250, 109, 70, 1)'
: categoryStatuses.includes('degraded')
? 'rgba(255, 222, 36, 1)'
: 'rgba(108, 219, 86, 1)';
const collapsibleStyle = {
backgroundColor: 'rgb(246 246 246)',
width: '100%',
padding: '7px 10px',
fontSize: ' 12px',
fontWeight: '400',
color: 'black',
marginBottom: '0',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
border: '1px solid rgb(236 236 236)',
};
if (!theme) {
//if its a classic theme then, change some styles.
collapsibleStyle.backgroundColor = 'rgb(247 247 247)';
collapsibleStyle.border = '1px solid rgb(228 228 228)';
}
return (
<Collapsible
trigger={
categoryName.charAt(0).toUpperCase() + categoryName.slice(1)
}
triggerStyle={collapsibleStyle}
open={true}
contentContainerTagName="div"
triggerTagName="div"
transitionTime="200"
lazyRender={true}
closedIconClass="sp__icon sp__icon--down"
openIconClass="sp__icon sp__icon--up"
statusColorStyle={{
borderRadius: ' 100px',
height: '8px',
width: '8px',
backgroundColor: categoryStatusBk,
}}
>
{monitors.map((monitor, i) => {
return (
<>
<MonitorInfo
monitor={monitor}
selectedCharts={
this.props.monitors.filter(
m => monitor._id === m.monitor._id
)[0]
}
key={i}
id={`monitor${i}`}
resourceCategory={monitor.resourceCategory}
isGroupedByMonitorCategory={false}
theme={
this.props.statusData.theme ===
'Clean Theme'
? true
: false
}
/>
{this.props.monitors.some(
m => monitor._id === m.monitor._id
) && (
<LineChartsContainer
monitor={monitor}
selectedCharts={
this.props.monitors.filter(
m => monitor._id === m.monitor._id
)[0]
}
/>
)}
{i < monitors.length - 1 ||
(i === monitors.length - 1 &&
categoryName.toLowerCase() ===
'uncategorized' &&
!theme) ? (
<div
style={{
margin: '30px 0px',
backgroundColor: 'rgb(232, 232, 232)',
height: '1px',
}}
/>
) : (i === monitors.length - 1 &&
categoryName.toLowerCase() !==
'uncategorized') ||
(i === monitors.length - 1 &&
categoryName.toLowerCase() ===
'uncategorized' &&
theme) ? (
<div
style={{
marginBottom: '30px',
}}
/>
) : null}
</>
);
})}
</Collapsible>
);
};
selectbutton = index => {
this.props.selectedProbe(index);
};
@@ -478,7 +561,9 @@ class Main extends Component {
{ name: 'Future Scheduled Events', key: 'maintenance' },
{ name: 'Footer', key: 'footer' },
],
invisible: [{ name: 'Scheduled Events Completed', key: 'pastEvents' }],
invisible: [
{ name: 'Scheduled Events Completed', key: 'pastEvents' },
],
};
let visibleLayout =
@@ -487,16 +572,7 @@ class Main extends Component {
if (!visibleLayout) {
visibleLayout = defaultLayout;
}
let resourcesServiceOverlap = false;
visibleLayout.visible.forEach((item, i) => {
if (
item.key === 'services' &&
visibleLayout.visible[i - 1] &&
visibleLayout.visible[i - 1].key === 'resources'
) {
resourcesServiceOverlap = true;
}
});
const layoutObj = {
header: (
<>
@@ -654,6 +730,7 @@ class Main extends Component {
style={{
borderTopWidth: '1px',
...contentBackground,
padding: 0,
}}
>
{this.groupedMonitors()}
@@ -886,11 +963,7 @@ class Main extends Component {
className="white box"
style={{
...contentBackground,
borderBottomLeftRadius:
resourcesServiceOverlap && 0,
borderBottomRightRadius:
resourcesServiceOverlap && 0,
marginTop: resourcesServiceOverlap ? 0 : '50px',
marginTop: '50px',
}}
>
<div className="largestatus">
@@ -929,17 +1002,13 @@ class Main extends Component {
className="content"
style={{
position: 'relative',
marginTop: resourcesServiceOverlap ? 0 : '50px',
marginTop: '50px',
}}
>
<div
className="white box"
style={{
...contentBackground,
borderTopLeftRadius:
resourcesServiceOverlap && 0,
borderTopRightRadius:
resourcesServiceOverlap && 0,
}}
>
<ShouldRender
@@ -976,7 +1045,7 @@ class Main extends Component {
className="uptime-graphs box-inner"
style={
isGroupedByMonitorCategory
? { paddingBottom: 0 }
? { padding: 0 }
: { paddingBottom: 35 }
}
>

View File

@@ -273,6 +273,7 @@ describe('Member Restriction', () => {
});
// adding a subProject is only allowed on growth plan and above
await init.addSubProject(subProjectName, page);
await init.saasLogout(page);
done();
});
@@ -284,10 +285,7 @@ describe('Member Restriction', () => {
test(
'should show unauthorised modal when a team member who is not an admin or owner of the project tries to update alert option',
async done => {
browser = await puppeteer.launch(utils.puppeteerLaunchConfig);
page = await browser.newPage();
await page.setUserAgent(utils.agent);
async done => {
await init.registerAndLoggingTeamMember(
{ email: teamEmail, password },

View File

@@ -8,6 +8,7 @@ let browser, page;
const email = utils.generateRandomBusinessEmail();
const password = '1234567890';
let slaName = 'fxPro';
let newSlaName = 'newFxPro';
const duration = '15';
const alertTime = '10';
const component = utils.generateRandomString();
@@ -265,14 +266,7 @@ describe('Incident Communication SLA', () => {
await init.pageClick(page, '#alertTime');
await init.pageType(page, '#alertTime', alertTime);
await init.page$Eval(page, '#isDefault', elem => elem.click());
await init.pageClick(page, '#createSlaBtn');
await init.pageWaitForSelector(page, '.ball-beat', {
visible: true,
timeout: init.timeout,
});
await init.pageWaitForSelector(page, '.ball-beat', {
hidden: true,
});
await init.pageClick(page, '#createSlaBtn');
const sla = await init.pageWaitForSelector(
page,
@@ -332,7 +326,7 @@ describe('Incident Communication SLA', () => {
visible: true,
timeout: init.timeout,
});
await init.pageClick(page, '#name');
await init.pageClick(page, '#name', {clickCount: 3});
await init.pageType(page, '#name', ' ');
await init.pageClick(page, '#editSlaBtn');
@@ -393,22 +387,14 @@ describe('Incident Communication SLA', () => {
await init.pageWaitForSelector(page, '#communicationSlaForm', {
visible: true,
timeout: init.timeout,
});
slaName = 'newFxPro';
await init.pageClick(page, '#name');
await init.pageType(page, '#name', slaName);
await init.pageClick(page, '#editSlaBtn');
await init.pageWaitForSelector(page, '.ball-beat', {
visible: true,
timeout: init.timeout,
});
await init.pageWaitForSelector(page, '.ball-beat', {
hidden: true,
});
});
await init.pageClick(page, '#name', {clickCount: 3});
await init.pageType(page, '#name', newSlaName);
await init.pageClick(page, '#editSlaBtn');
const sla = await init.pageWaitForSelector(
page,
`#incidentSla_${slaName}`,
`#incidentSla_${newSlaName}`,
{
visible: true,
timeout: init.timeout,
@@ -535,30 +521,23 @@ describe('Incident Communication SLA', () => {
await init.pageWaitForSelector(
page,
`#deleteIncidentSlaBtn_${slaName}`,
`#deleteIncidentSlaBtn_${newSlaName}`,
{
visible: true,
timeout: init.timeout,
}
);
await init.pageClick(page, `#deleteIncidentSlaBtn_${slaName}`);
await init.pageClick(page, `#deleteIncidentSlaBtn_${newSlaName}`);
await init.pageWaitForSelector(page, '#deleteIncidentSlaBtn', {
visible: true,
timeout: init.timeout,
});
await init.pageClick(page, '#deleteIncidentSlaBtn');
await init.pageWaitForSelector(page, '.ball-beat', {
visible: true,
timeout: init.timeout,
});
await init.pageWaitForSelector(page, '.ball-beat', {
hidden: true,
});
await init.pageClick(page, '#deleteIncidentSlaBtn');
const sla = await init.pageWaitForSelector(
page,
`#incidentSla_${slaName}`,
`#incidentSla_${newSlaName}`,
{
hidden: true,
}

View File

@@ -78,8 +78,7 @@ describe('Incident Reports API', () => {
timeout: init.timeout,
});
// Navigate to Component details
await init.navigateToComponentDetails(componentName, page);
await page.goto(utils.DASHBOARD_URL, { waitUntil : 'networkidle2'}); // Incident Status is present on Dashboard and Incident Detail Page
await init.pageWaitForSelector(page, '#closeIncident_0', {
visible: true,
timeout: 100000,
@@ -124,7 +123,7 @@ describe('Incident Reports API', () => {
});
// Navigate to Component details
await init.navigateToComponentDetails(componentName, page);
await page.goto(utils.DASHBOARD_URL, { waitUntil : 'networkidle2'});
await init.pageWaitForSelector(page, '#closeIncident_1', {
visible: true,
timeout: 100000,

View File

@@ -48,7 +48,7 @@ describe('Incident Timeline API', () => {
timeout: init.timeout,
});
await init.page$Eval(page, '#url', e => e.click());
await init.pageType(page, '#url', utils.HTTP_TEST_SERVER_URL);
await init.pageType(page, '#url', 'https://google.com'); //'HTTP_TEST_SERVER' auto generates incidents and this breaks the test. Also, the tests are not dependent on HTTP_TEST_SERVER
await init.page$Eval(page, 'button[type=submit]', e => e.click());
await init.pageWaitForSelector(
page,
@@ -390,9 +390,10 @@ describe('Incident Timeline API', () => {
hidden: true,
});
const incidentMessage = await init.page$(
const incidentMessage = await init.pageWaitForSelector(
page,
`#content_${type}_incident_message_0`
`#content_${type}_incident_message_0`,
{hidden : true}
);
expect(incidentMessage).toEqual(null);

View File

@@ -142,7 +142,7 @@ describe('Incoming HTTP Request', () => {
visible: true,
timeout: init.timeout,
});
await init.pageClick(page, '#name');
await init.pageClick(page, '#name', {clickCount : 3});
// change the name of the incoming http request
await init.pageType(page, '#name', 'newName');
await init.pageClick(page, '#editIncomingRequest');

View File

@@ -151,7 +151,7 @@ describe('Incident Priority API', () => {
await init.pageWaitForSelector(page, editButtonLastRowIndentifier);
await init.pageClick(page, editButtonLastRowIndentifier);
await init.pageWaitForSelector(page, '#EditIncidentPriority');
await init.pageClick(page, 'input[name=name]');
await init.pageClick(page, 'input[name=name]', {clickCount:3});
await page.keyboard.press('Backspace');
await init.pageType(page, 'input[name=name]', newPriorityName);
await init.pageClick(page, '#EditIncidentPriority');

View File

@@ -0,0 +1,95 @@
const puppeteer = require('puppeteer');
const utils = require('../../test-utils');
const init = require('../../test-init');
const axios = require('axios');
let page, browser;
// user credentials
const email = utils.generateRandomBusinessEmail();
const queryString = '?utm_source=runningtest&good=thankyou&kill=love&ion=pure';
let queryObj = {};
describe('Download Whitepaper form', () => {
beforeAll(async done => {
jest.setTimeout(init.timeout);
browser = await puppeteer.launch(utils.puppeteerLaunchConfig);
page = await browser.newPage();
await page.setUserAgent(utils.agent);
await page.goto(`${utils.HOME_URL}${queryString}`, {
waitUntil: 'networkidle2',
});
await init.pageClick(page, '#accept-cookies');
await page.goto(`${utils.HOME_URL}/enterprise/resources`, {
waitUntil: 'networkidle2',
});
await page.goto(
`${utils.HOME_URL}/enterprise/download-resource/website-monitoring`,
{
waitUntil: 'networkidle2',
}
);
await page.goto(`${utils.HOME_URL}/enterprise/resources`);
await init.pageWaitForSelector(page, '#website-monitoring', {
visible: true,
timeout: init.timeout,
});
await Promise.all([
page.waitForNavigation(),
init.pageClick(page, '#website-monitoring'),
]);
await init.pageWaitForSelector(page, '#form-section', {
visible: true,
timeout: init.timeout,
});
await init.pageType(page, '#fullname', utils.user.name);
await init.pageType(page, '#email', email);
await init.pageType(page, '#phone', utils.user.phone);
await init.pageType(page, '#website', utils.user.website);
await init.pageClick(page, '#country');
await page.keyboard.press('ArrowDown');
await page.keyboard.down('Enter');
await init.pageClick(page, '#volume');
await page.keyboard.press('ArrowDown');
await page.keyboard.down('Enter');
await init.pageClick(page, '#request-resource-btn');
const params = new URLSearchParams(queryString);
// formating query string to an object
for (const param of params) {
queryObj = { ...queryObj, [`${param[0]}`]: param[1] };
}
done();
});
afterAll(async done => {
await browser.close();
done();
});
test(
'redirected query string should be save as source in the leads schema',
async done => {
const data = {
collection: 'leads',
query: { email: email },
};
const config = {
method: 'post',
url: utils.INIT_SCRIPT_URL + '/find',
headers: {
'Content-Type': 'application/json',
},
data: data,
};
const res = await axios(config);
const sourceObj = res.data[0].source;
for (const key in sourceObj) {
expect(sourceObj[key]).toEqual(queryObj[key]);
}
done();
},
init.timeout
);
});

View File

@@ -0,0 +1,83 @@
const puppeteer = require('puppeteer');
const utils = require('../../test-utils');
const init = require('../../test-init');
const axios = require('axios');
let page, browser;
// user credentials
const email = utils.generateRandomBusinessEmail();
const queryString = '?utm_source=runningtest&good=thankyou&kill=love&ion=pure';
let queryObj = {};
describe('Demo form', () => {
beforeAll(async done => {
jest.setTimeout(init.timeout);
browser = await puppeteer.launch(utils.puppeteerLaunchConfig);
page = await browser.newPage();
await page.setUserAgent(utils.agent);
await page.goto(`${utils.HOME_URL}${queryString}`, {
waitUntil: 'networkidle2',
});
await init.pageClick(page, '#accept-cookies');
await page.goto(`${utils.HOME_URL}/enterprise/demo`, {
waitUntil: 'networkidle2',
});
await init.pageWaitForSelector(page, '#form-section');
await init.pageType(page, '#fullname', utils.user.name);
await init.pageType(page, '#email', email);
await init.pageType(page, '#Phone', utils.user.phone);
await init.pageType(page, '#website', utils.user.website);
await init.pageClick(page, '#country');
await page.keyboard.press('ArrowDown');
await page.keyboard.down('Enter');
await init.pageClick(page, '#volume');
await page.keyboard.press('ArrowDown');
await page.keyboard.down('Enter');
await init.pageType(page, '#message', utils.user.message);
await init.pageClick(page, '#request-demo-btn');
await init.pageWaitForSelector(page, '#success');
// Check if user's email is submitted successfully
await init.pageWaitForSelector(page, '.submitted-email', {
visible: true,
timeout: init.timeout,
});
const params = new URLSearchParams(queryString);
// formating query string to an object
for (const param of params) {
queryObj = { ...queryObj, [`${param[0]}`]: param[1] };
}
done();
});
afterAll(async done => {
await browser.close();
done();
});
test(
'redirected query string should be save as source in the leads schema',
async () => {
const data = {
collection: 'leads',
query: { email: email },
};
const config = {
method: 'post',
url: utils.INIT_SCRIPT_URL + '/find',
headers: {
'Content-Type': 'application/json',
},
data: data,
};
const res = await axios(config);
const sourceObj = res.data[0].source;
for (const key in sourceObj) {
expect(sourceObj[key]).toEqual(queryObj[key]);
}
},
init.timeout
);
});

View File

@@ -0,0 +1,83 @@
const utils = require('../../test-utils');
const puppeteer = require('puppeteer');
const init = require('../../test-init');
let page, browser;
const email = utils.generateRandomBusinessEmail();
const password = '1234567890';
const user = {
email,
password,
};
const projectName = utils.generateRandomString();
const statusPageName = utils.generateRandomString();
describe('Probe bar test', () => {
beforeAll(async done => {
jest.setTimeout(init.timeout);
browser = await puppeteer.launch(utils.puppeteerLaunchConfig);
page = await browser.newPage();
await page.setUserAgent(utils.agent);
done();
});
afterAll(async done => {
await browser.close();
done();
});
test(
'Probe bar should not show by default',
async done => {
await init.registerUser(user, page);
await init.renameProject(projectName, page);
await init.growthPlanUpgrade(page); // Only Monthly growth plan can enable subscribers in status-page
// Create a Status-Page and Scheduled Maintenance to display in the Status-Page Url
await page.goto(utils.DASHBOARD_URL, {
waitUntil: 'networkidle2',
});
await init.pageWaitForSelector(page, '#statusPages');
await init.pageClick(page, '#statusPages');
await init.pageWaitForSelector(
page,
`#btnCreateStatusPage_${projectName}`
);
await init.pageClick(page, `#btnCreateStatusPage_${projectName}`);
await init.pageWaitForSelector(page, '#name');
await init.pageWaitForSelector(page, 'input[id=name]', {
visible: true,
timeout: init.timeout,
});
await init.pageClick(page, 'input[id=name]');
await page.focus('input[id=name]');
await init.pageType(page, 'input[id=name]', statusPageName);
await init.pageClick(page, '#btnCreateStatusPage');
await init.pageWaitForSelector(page, '#statusPagesListContainer');
await init.pageWaitForSelector(page, '#viewStatusPage');
await init.pageClick(page, '#viewStatusPage');
await init.pageWaitForSelector(page, `#header-${statusPageName}`);
await init.pageWaitForSelector(page, '#publicStatusPageUrl');
let link = await init.page$(
page,
'#publicStatusPageUrl > span > a'
);
link = await link.getProperty('href');
link = await link.jsonValue();
await page.goto(link);
// To confirm if the probe shows after creating a status.
const probeBar = await page.evaluate(() => {
const el = document.querySelector('.bs-probes');
return el ? el.innerText : '';
});
expect(probeBar).toMatch('');
done();
},
init.timeout
);
});

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