Merge pull request #313 from OneUptime/refactor-ui

Refactor UI
This commit is contained in:
Simon Larsen
2022-12-12 21:32:30 +05:30
committed by GitHub
143 changed files with 32454 additions and 28247 deletions

11
.gitignore vendored
View File

@@ -8,7 +8,7 @@ node_modules
*/build/dist/*
# Build Dist
*/dist/*
**/dist/*
.idea
# testing
@@ -70,3 +70,12 @@ config.env.tmp
config.env.temp
docker-compose.yml
logs.txt
*/Cert.crt
*/Key.key
Nginx/default.conf
Certs/StatusPageCerts/*.crt
Certs/StatusPageCerts/*.key
Backups/*.backup

14
.vscode/launch.json vendored
View File

@@ -97,6 +97,20 @@
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/StatusPage",
"name": "Status Page API${cwd}: Debug with Docker",
"port": 9764,
"remoteRoot": "/usr/src/app",
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"restart": true,
"autoAttachChildProcesses": true
},
{
"address": "127.0.0.1",
"localRoot": "${workspaceFolder}/ProbeAPI",

View File

@@ -16,11 +16,6 @@ app.use(ExpressStatic(path.join(__dirname, 'public')));
app.use(`/${APP_NAME}`, ExpressStatic(path.join(__dirname, 'public')));
app.use(
[`/${APP_NAME}/assets`, `/${APP_NAME}/${APP_NAME}/assets`],
ExpressStatic(path.join(__dirname, 'dist'))
);
app.get('/*', (_req: ExpressRequest, res: ExpressResponse) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

4
Accounts/nodemon.json Normal file
View File

@@ -0,0 +1,4 @@
{
"watch": ["webpack.config.js"],
"exec": "export DEBUG=express:* && webpack-dev-server --port=3003 --mode=development"
}

View File

@@ -102,8 +102,9 @@
"pg": "^8.7.3",
"redis": "^4.2.0",
"socket.io": "^4.4.1",
"typeorm": "^0.3.6",
"typeorm-extension": "^2.1.0",
"stripe": "^10.17.0",
"typeorm": "^0.3.10",
"typeorm-extension": "^2.2.13",
"winston": "^3.6.0"
},
"devDependencies": {
@@ -159,7 +160,7 @@
"react-router-dom": "^6.3.0",
"react-select": "^5.4.0",
"react-simple-code-editor": "^0.13.0",
"react-spinners": "^0.12.0",
"react-spinners": "^0.13.6",
"react-toggle": "^4.1.3",
"reactstrap": "^9.1.1",
"redux": "^4.2.0",
@@ -2375,6 +2376,11 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@eslint/eslintrc/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
@@ -5364,6 +5370,11 @@
"ms": "2.0.0"
}
},
"node_modules/compression/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -5678,6 +5689,11 @@
"ajv": "^8.8.2"
}
},
"node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
@@ -6529,6 +6545,18 @@
"source-map": "~0.6.1"
}
},
"node_modules/escodegen/node_modules/levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
"dependencies": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/escodegen/node_modules/optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
@@ -7247,6 +7275,11 @@
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/express/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -9444,6 +9477,17 @@
"@types/yargs-parser": "*"
}
},
"node_modules/jest-watch-typeahead/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/jest-watch-typeahead/node_modules/emittery": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz",
@@ -9455,6 +9499,33 @@
"url": "https://github.com/sindresorhus/emittery?sponsor=1"
}
},
"node_modules/jest-watch-typeahead/node_modules/jest-message-util": {
"version": "28.1.3",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz",
"integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==",
"dependencies": {
"@babel/code-frame": "^7.12.13",
"@jest/types": "^28.1.3",
"@types/stack-utils": "^2.0.0",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"micromatch": "^4.0.4",
"pretty-format": "^28.1.3",
"slash": "^3.0.0",
"stack-utils": "^2.0.3"
},
"engines": {
"node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
}
},
"node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"engines": {
"node": ">=8"
}
},
"node_modules/jest-watch-typeahead/node_modules/jest-regex-util": {
"version": "28.0.2",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz",
@@ -9520,6 +9591,25 @@
"node": ">=8"
}
},
"node_modules/jest-watch-typeahead/node_modules/pretty-format": {
"version": "28.1.3",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz",
"integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==",
"dependencies": {
"@jest/schemas": "^28.1.3",
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
"react-is": "^18.0.0"
},
"engines": {
"node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
}
},
"node_modules/jest-watch-typeahead/node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/jest-watch-typeahead/node_modules/slash": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
@@ -10748,6 +10838,42 @@
"node": ">=8"
}
},
"node_modules/pkg-dir/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/pkg-dir/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pkg-dir/node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/pkg-up": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
@@ -10770,6 +10896,51 @@
"node": ">=6"
}
},
"node_modules/pkg-up/node_modules/locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dependencies": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/pkg-up/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pkg-up/node_modules/p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dependencies": {
"p-limit": "^2.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/pkg-up/node_modules/path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
"engines": {
"node": ">=4"
}
},
"node_modules/postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
@@ -11783,6 +11954,14 @@
"postcss": "^8.2.15"
}
},
"node_modules/postcss-svgo/node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"engines": {
"node": ">= 10"
}
},
"node_modules/postcss-svgo/node_modules/css-tree": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
@@ -12935,6 +13114,14 @@
"ms": "2.0.0"
}
},
"node_modules/serve-index/node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/serve-index/node_modules/http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
@@ -13525,6 +13712,17 @@
"nth-check": "^1.0.2"
}
},
"node_modules/svgo/node_modules/css-what": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
"integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/svgo/node_modules/dom-serializer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
@@ -14650,6 +14848,14 @@
"node": ">=8.0.0"
}
},
"node_modules/webpack/node_modules/estraverse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"engines": {
"node": ">=4.0"
}
},
"node_modules/websocket-driver": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
@@ -16626,6 +16832,11 @@
"strip-json-comments": "^3.1.1"
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"globals": {
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
@@ -18892,9 +19103,10 @@
"pg": "^8.7.3",
"redis": "^4.2.0",
"socket.io": "^4.4.1",
"stripe": "^10.17.0",
"ts-jest": "^27.1.4",
"typeorm": "^0.3.6",
"typeorm-extension": "^2.1.0",
"typeorm": "^0.3.10",
"typeorm-extension": "^2.2.13",
"winston": "^3.6.0"
}
},
@@ -18950,7 +19162,7 @@
"react-router-dom": "^6.3.0",
"react-select": "^5.4.0",
"react-simple-code-editor": "^0.13.0",
"react-spinners": "^0.12.0",
"react-spinners": "^0.13.6",
"react-test-renderer": "^18.2.0",
"react-toggle": "^4.1.3",
"reactstrap": "^9.1.1",
@@ -18994,6 +19206,11 @@
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}
}
},
@@ -19199,6 +19416,11 @@
"fast-deep-equal": "^3.1.3"
}
},
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"schema-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz",
@@ -19832,6 +20054,15 @@
"source-map": "~0.6.1"
},
"dependencies": {
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
}
},
"optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
@@ -20366,6 +20597,11 @@
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -21955,11 +22191,39 @@
"@types/yargs-parser": "*"
}
},
"ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
},
"emittery": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz",
"integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw=="
},
"jest-message-util": {
"version": "28.1.3",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz",
"integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==",
"requires": {
"@babel/code-frame": "^7.12.13",
"@jest/types": "^28.1.3",
"@types/stack-utils": "^2.0.0",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"micromatch": "^4.0.4",
"pretty-format": "^28.1.3",
"slash": "^3.0.0",
"stack-utils": "^2.0.3"
},
"dependencies": {
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
}
}
},
"jest-regex-util": {
"version": "28.0.2",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz",
@@ -22012,6 +22276,22 @@
}
}
},
"pretty-format": {
"version": "28.1.3",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz",
"integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==",
"requires": {
"@jest/schemas": "^28.1.3",
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
"react-is": "^18.0.0"
}
},
"react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"slash": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
@@ -22929,6 +23209,30 @@
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"requires": {
"p-locate": "^4.1.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"requires": {
"p-limit": "^2.2.0"
}
}
}
},
@@ -22947,6 +23251,36 @@
"requires": {
"locate-path": "^3.0.0"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"requires": {
"p-limit": "^2.0.0"
}
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="
}
}
},
@@ -23533,6 +23867,11 @@
"svgo": "^2.7.0"
},
"dependencies": {
"commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
},
"css-tree": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
@@ -24381,6 +24720,11 @@
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="
},
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
@@ -24833,6 +25177,11 @@
"nth-check": "^1.0.2"
}
},
"css-what": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz",
"integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ=="
},
"dom-serializer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
@@ -25451,6 +25800,11 @@
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
}
},
"estraverse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
}
}
},

View File

@@ -18,11 +18,11 @@
"typescript": "^4.6.4"
},
"scripts": {
"dev": "webpack-dev-server --port=3003 --mode=development",
"dev": "nodemon",
"build": "webpack build --mode=production",
"test": "",
"compile": "tsc",
"start": "node --require ts-node/register Index.ts",
"start": "node --require ts-node/register Serve.ts",
"audit": "npm audit --audit-level=low",
"preinstall": "npx npm-force-resolutions || echo 'No package-lock.json file. Skipping force resolutions'",
"dep-check": "depcheck ./ --skip-missing=true'"

View File

@@ -66,6 +66,6 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script src="/accounts/accounts/assets/bundle.js"></script>
<script src="/accounts/dist/bundle.js"></script>
</body>
</html>

View File

@@ -11,11 +11,9 @@ import NotFound from './Pages/NotFound';
import SsoLoginPage from './Pages/SsoLogin';
import ForgotPasswordPage from './Pages/ForgotPassword';
import RegisterPage from './Pages/Register';
import { DASHBOARD_URL } from 'CommonUI/src/Config';
import 'CommonUI/src/Styles/theme.scss';
import Navigation from 'CommonUI/src/Utils/Navigation';
import VerifyEmail from './Pages/VerifyEmail';
import User from 'CommonUI/src/Utils/User';
import ResetPasswordPage from './Pages/ResetPassword';
function App(): ReactElement {
@@ -23,10 +21,6 @@ function App(): ReactElement {
Navigation.setLocation(useLocation());
Navigation.setParams(useParams());
if (User.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
}
return (
<div className="App">
<Routes>

View File

@@ -30,7 +30,7 @@ const ForgotPassword: FunctionComponent = () => {
>
<img
style={{ height: '40px' }}
src={`/accounts/public/${OneUptimeLogo}`}
src={`${OneUptimeLogo}`}
/>
</div>
<div className="text-center">

View File

@@ -9,10 +9,17 @@ import { LOGIN_API_URL } from '../Utils/ApiPaths';
import URL from 'Common/Types/API/URL';
import { JSONObject } from 'Common/Types/JSON';
import LoginUtil from '../Utils/Login';
import UserUtil from 'CommonUI/src/Utils/User';
import Navigation from 'CommonUI/src/Utils/Navigation';
import { DASHBOARD_URL } from 'CommonUI/src/Config';
const LoginPage: FunctionComponent = () => {
const apiUrl: URL = LOGIN_API_URL;
if (UserUtil.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
}
return (
<div className="auth-page">
<div className="container-fluid p-0">
@@ -30,7 +37,7 @@ const LoginPage: FunctionComponent = () => {
>
<img
style={{ height: '40px' }}
src={`/accounts/public/${OneUptimeLogo}`}
src={`${OneUptimeLogo}`}
/>
</div>
<div className="text-center">

View File

@@ -7,13 +7,19 @@ import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSc
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimePNG/7.png';
import LoginUtil from '../Utils/Login';
import { JSONObject } from 'Common/Types/JSON';
import UserUtil from 'CommonUI/src/Utils/User';
import Navigation from 'CommonUI/src/Utils/Navigation';
import { DASHBOARD_URL } from 'CommonUI/src/Config';
import URL from 'Common/Types/API/URL';
import { SIGNUP_API_URL } from '../Utils/ApiPaths';
const RegisterPage: FunctionComponent = () => {
const apiUrl: URL = SIGNUP_API_URL;
if (UserUtil.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
}
return (
<div className="auth-page">
<div className="container-fluid p-0">
@@ -31,7 +37,7 @@ const RegisterPage: FunctionComponent = () => {
>
<img
style={{ height: '40px' }}
src={`/accounts/public/${OneUptimeLogo}`}
src={`${OneUptimeLogo}`}
/>
</div>
<div className="text-center">

View File

@@ -31,7 +31,7 @@ const RegisterPage: FunctionComponent = () => {
>
<img
style={{ height: '40px' }}
src={`/accounts/public/${OneUptimeLogo}`}
src={`${OneUptimeLogo}`}
/>
</div>
<div className="text-center">

View File

@@ -27,7 +27,7 @@ const SsoLoginPage: FunctionComponent = () => {
>
<img
style={{ height: '40px' }}
src={`/accounts/public/${OneUptimeLogo}`}
src={`${OneUptimeLogo}`}
/>
</div>
<div className="text-center">

View File

@@ -75,7 +75,7 @@ const VerifyEmail: FunctionComponent = () => {
>
<img
style={{ height: '40px' }}
src={`/accounts/public/${OneUptimeLogo}`}
src={`${OneUptimeLogo}`}
/>
</div>
{!error && (

View File

@@ -1,15 +1,19 @@
const path = require("path");
const webpack = require("webpack");
const dotenv = require('dotenv');
const express = require('express');
const readEnvFile = (pathToFile) => {
const parsed = dotenv.config({ path: pathToFile }).parsed;
const env = {};
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
return env;
}
@@ -18,8 +22,8 @@ module.exports = {
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
publicPath: "/assets/",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/accounts/dist/",
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'],
@@ -56,7 +60,7 @@ module.exports = {
use: ['style-loader', 'css-loader']
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
test: /\.(jpe?g|png|gif|svg)$/i,
loader: 'file-loader'
}
],
@@ -66,6 +70,11 @@ module.exports = {
devMiddleware: {
writeToDisk: true,
},
allowedHosts: "all",
setupMiddlewares: (middlewares, devServer) => {
devServer.app.use('/accounts/assets', express.static(path.resolve(__dirname, 'public', 'assets')));
return middlewares;
}
},
devtool: 'eval-source-map',
}

View File

@@ -1 +1 @@
ONEUPTIME_SECRET=ae60cf8f0cec2df8d05aaf0b
ONEUPTIME_SECRET=fd57b59aa8f3f516d2f6cb06

1
Backups/README.md Normal file
View File

@@ -0,0 +1 @@
This is where all the database backups will be stored.

3
Certs/Readme.md Normal file
View File

@@ -0,0 +1,3 @@
This directory is used for hosting SSL Certs for nginx.
If you need a new certificate. Please have Cert.pem and Key.pem in this folder.

View File

@@ -0,0 +1 @@
Certs for Status page with custom domains.

View File

@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFKTCCBBGgAwIBAgISBI+ZMOTLreLlsVt7PF9qX1u2MA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMjEyMTAxMjA2MTJaFw0yMzAzMTAxMjA2MTFaMB0xGzAZBgNVBAMT
EnN0YXR1cy5nZW5vc3luLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAMgb7+Qhe/f9YCIATTMF7kVGO4Zwd2nJ167vEczBOsHkNspiVD9UPdL0KpFV
L37sqiclcVbUds5QO0h81ckBMNwsRiqSeEj5V5FcRII1PdduROBB7mVfgtuwG2jQ
NtU2IEDAIuxQVTOep3ciZBK4iiwgYttOKbvw5EtjlYKMk2+ZJy2eUjvEstNGkm5b
iekNEvt5ZTInYfWoyCaD9gsjMEaB/ueL21M7jbgJ049n64CYv2robhU9JEQW5p8h
B849YvO+nzhGdCuWRU4FPXzI4aDPs6ONceodg0IIeEbEZMntc1AKXyL7TJp3+kLI
eBpmtwXqQvNuCeqV7QXMK7X7QEsCAwEAAaOCAkwwggJIMA4GA1UdDwEB/wQEAwIF
oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAd
BgNVHQ4EFgQUvnstsk3lPsOYn1nKiQGwdeXVGscwHwYDVR0jBBgwFoAUFC6zF7dY
VsuuUAlA5h+vnYsUwsYwVQYIKwYBBQUHAQEESTBHMCEGCCsGAQUFBzABhhVodHRw
Oi8vcjMuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6Ly9yMy5pLmxlbmNy
Lm9yZy8wHQYDVR0RBBYwFIISc3RhdHVzLmdlbm9zeW4uY29tMEwGA1UdIARFMEMw
CAYGZ4EMAQIBMDcGCysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9j
cHMubGV0c2VuY3J5cHQub3JnMIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHYAtz77
JN+cTbp18jnFulj0bF38Qs96nzXEnh0JgSXttJkAAAGE/CSqhwAABAMARzBFAiAm
PBXbjWWdkehEstdTQ5LoAS1omXJQNnuo1nt2YsoL3gIhAJSewh6nfHL+lXhckXtk
pSkI2qNdq2otCyq3Z9W/Z4YpAHUArfe++nz/EMiLnT2cHj4YarRnKV3PsQwkyoWG
NOvcgooAAAGE/CSqvQAABAMARjBEAiAwAB2istEPxzNrMuw04guD5NyOt/RKDX5l
0y71+H9ekQIgaBg24zPbRNw01G5Y7WUsS9/vBgAEpEtfqpMcedNVwXAwDQYJKoZI
hvcNAQELBQADggEBALU11dZ+HIWxGUz0Q4IqVLfN9dP4cTVdee5GOX8KNh2rmIBv
7YZtuGJVVbILW+D5w/r+3l3z3MFg1ocEHUtfDCtmZes4WyBHXntaJe9JuqY6/ArN
VUIR1r0S6iKmZ17S0/FjruyHOyOw5Zhxix1Q5dMCpuCIfOtOaG8cAf8Qi1re3br9
6dwDpgW5mUQBLA9DGnnXYKvkd/EcEUnf39o2UaDCfFSeQgSu46/98GcOQQ0SpN6c
OqEJ4Dnc6NP/Trmb1nDQbIMx4c5y/q7miDb8URdK3H+EGdmmBie7M46se3i11oGC
u7agSvvW32dShImvoC8pJFAS0cEaAPdODHgg5U0=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAyBvv5CF79/1gIgBNMwXuRUY7hnB3acnXru8RzME6weQ2ymJU
P1Q90vQqkVUvfuyqJyVxVtR2zlA7SHzVyQEw3CxGKpJ4SPlXkVxEgjU9125E4EHu
ZV+C27AbaNA21TYgQMAi7FBVM56ndyJkEriKLCBi204pu/DkS2OVgoyTb5knLZ5S
O8Sy00aSbluJ6Q0S+3llMidh9ajIJoP2CyMwRoH+54vbUzuNuAnTj2frgJi/auhu
FT0kRBbmnyEHzj1i876fOEZ0K5ZFTgU9fMjhoM+zo41x6h2DQgh4RsRkye1zUApf
IvtMmnf6Qsh4Gma3BepC824J6pXtBcwrtftASwIDAQABAoIBABmBohq4f+Y0uCet
VSm/REc1NAonVLk5vpGwLFsmeBhVv/wc+3MVCEpW0AQ1UPgDL48M0T0JmNkkVeIf
81oLGlC+HfV4NPfMPHKtSZg1NBw9FG9nR/1I5tOcx2mdPJgBravDMdBgTvPk8aCY
VBwkxIvqVt9wP5aSlm7bkyeQRoyvRYP+cWnLo1sR6sIEO4SyjtoFzKtp/CrP8MBs
67mVyLPurEqCB0wVDCAr/G2SkJhCUXGvqSzfDU0oGEVGWmb95PQGsOe1KjqegY7A
BTFKMNjbOyncYau4EkZcPAKpv/9Qgo+52bYDKekSxk/ZHitE0TmW4WlC73UMkIQK
yG+Si+kCgYEA8R7+roCHqaxT30bNlJe+FCkVQuXsBJp3TAPUxBSnZZCp0mifEhQc
WGvvG9AOSVmoCPDMGw1xUo+zsUNkxmi+FcLUeAM5hdiV68D1QfjhUlrPidEv74l9
0WlW3+z1tJVbN2WYdXD1JHg5nyq/hEMg4Q/FLEAMj6daIK1PFc+INuMCgYEA1HUS
6q89LzFMSGrGxksxQIoT4cgg1pUA3QLWijEnnT2YDk3Z5iB7F+a1jYQFH1SXBYMx
pIwiHM+qXsumcg0upLPj+ulqGKMt0qPkJTb4w1rGr73BKygDdmFbym3jQxbAqpIY
gW1ZvuOBgR8hstBc0gLUOsgl5TeSel1h/swzpXkCgYEA4ZsmoRAR32gmcdtFr6rr
ZuGpyxZmZ0hAJxfOlEje9+ELhJvvenLmsrUK3PMm6urAltz3nLhPN/jNISb1u891
S9coBcK+p8WnQRciY8AC05O0bDcWqwHyf2YYqxyEKZs15fdhV0GBncX/5DWTTKWi
tfKTgnvLRP5JDhoazUWJJhECgYEAwJaf3z2bKPx3Oe4Q4g+nRenku/a+TcYkUjQQ
ZpTIZDFBdTX9IC6xZqksSmwyeIQlokma5p5hDdzxg5z39MseTQ8Eyp5sHolNMHSA
i3uZZP0Uvpo0UPqkqNr4ajfSmy402Go27JxDjlaNPo8J7R4UBguqdt6X+4C0t1eP
TXmuF4ECgYEAzK8INorcqp4a+rHrtIrBqS6I6q/NtBydZ6QC03qOL/6/6ITdf894
6+S5cksZZqr8HC86f/FOxjGvtLFuuuWoHLTgEra2Blm9lAwrSXIRIgbTDI19rQGq
WaNnUaRke0jQMkNXndToTlU2j7OBI1vGMA5UwrXAD8LWAMrkgzPeKUc=
-----END RSA PRIVATE KEY-----

View File

@@ -6,7 +6,7 @@ e2e_docker_compose_enterprise_test:
# Install Docker Compose.
- sudo curl -L "https://github.com/docker/compose/releases/download/1.25.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
- sudo chmod +x /usr/local/bin/docker-compose
- sudo docker-compose down -v
- sudo docker compose down -v
# Install the cluster.
- chmod +x ./env-setup.sh
- ./env-setup.sh

View File

@@ -7,7 +7,7 @@ e2e_docker_compose_test:
# Install Docker Compose.
- sudo curl -L "https://github.com/docker/compose/releases/download/1.25.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
- sudo chmod +x /usr/local/bin/docker-compose
- sudo docker-compose down -v
- sudo docker compose down -v
- export BILLING_ENABLED=true
# Install the cluster.
- chmod +x ./env-setup.sh

View File

@@ -4,6 +4,7 @@ enum Protocol {
MONGO_DB = 'mongodb://',
WS = 'ws://',
WSS = 'wss://',
MAIL = 'mailto:',
}
export default Protocol;

View File

@@ -5,6 +5,7 @@ import DatabaseProperty from '../Database/DatabaseProperty';
import { FindOperator } from 'typeorm';
import Dictionary from '../Dictionary';
import Typeof from '../Typeof';
import Email from '../Email';
export default class URL extends DatabaseProperty {
private _route: Route = new Route();
@@ -23,6 +24,14 @@ export default class URL extends DatabaseProperty {
this._params = v;
}
private _email!: Email;
public get email(): Email {
return this._email;
}
public set email(v: Email) {
this._email = v;
}
private _hostname!: Hostname;
public get hostname(): Hostname {
return this._hostname;
@@ -41,12 +50,20 @@ export default class URL extends DatabaseProperty {
public constructor(
protocol: Protocol,
hostname: Hostname | string,
hostname: Hostname | string | Email,
route?: Route,
queryString?: string
) {
super();
if (hostname instanceof Hostname) {
if (
typeof hostname === Typeof.String &&
Email.isValid(hostname as string)
) {
this.email = new Email(hostname as string);
} else if (hostname instanceof Email) {
this.email = hostname;
} else if (hostname instanceof Hostname) {
this.hostname = hostname;
} else if (typeof hostname === Typeof.String) {
this.hostname = Hostname.fromString(hostname);
@@ -77,21 +94,25 @@ export default class URL extends DatabaseProperty {
}
public override toString(): string {
let urlString: string = `${this.protocol}${this.hostname}`;
if (this.route.toString().startsWith('/')) {
urlString += this.route.toString();
} else {
urlString += '/' + this.route.toString();
}
if (Object.keys(this.params).length > 0) {
urlString += '?';
for (const key of Object.keys(this.params)) {
urlString += key + '=' + this.params[key] + '&';
let urlString: string = `${this.protocol}${
this.hostname || this.email
}`;
if (!this.email) {
if (this.route && this.route.toString().startsWith('/')) {
urlString += this.route.toString();
} else {
urlString += '/' + this.route.toString();
}
urlString = urlString.substring(0, urlString.length - 1); // remove last &
if (Object.keys(this.params).length > 0) {
urlString += '?';
for (const key of Object.keys(this.params)) {
urlString += key + '=' + this.params[key] + '&';
}
urlString = urlString.substring(0, urlString.length - 1); // remove last &
}
}
return urlString;
@@ -129,6 +150,11 @@ export default class URL extends DatabaseProperty {
url = url.replace('mongodb://', '');
}
if (url.startsWith('mailto:')) {
protocol = Protocol.MAIL;
url = url.replace('mailto:', '');
}
const hostname: Hostname = new Hostname(url.split('/')[0] || '');
let route: Route | undefined;

View File

@@ -31,6 +31,7 @@ enum ColumnType {
BigNumber = 'bigint',
Markdown = 'text',
File = 'bytea',
JSON = 'jsonb',
}
export default ColumnType;

View File

@@ -32,6 +32,7 @@ enum ColumnType {
BigNumber,
Entity,
EntityArray,
JSON,
}
export default ColumnType;

View File

@@ -4,6 +4,7 @@ enum EmailTemplateType {
EmailVerified = 'EmailVerified.hbs',
PasswordChanged = 'PasswordChanged.hbs',
InviteMember = 'InviteMember.hbs',
EmailChanged = 'EmailChanged.hbs',
}
export default EmailTemplateType;

View File

@@ -9,6 +9,7 @@ enum ExceptionCode {
NotAuthorizedException = 403,
NotAuthenticatedxception = 401,
PaymentRequiredException = 402,
NotFoundException = 404,
}
export default ExceptionCode;

View File

@@ -0,0 +1,8 @@
import Exception from './Exception';
import ExceptionCode from './ExceptionCode';
export default class NotFoundException extends Exception {
public constructor(message: string) {
super(ExceptionCode.NotFoundException, message);
}
}

View File

@@ -227,6 +227,13 @@ export class PermissionHelper {
permissions1: Array<Permission>,
permissions2: Array<Permission>
): boolean {
if (!permissions1) {
permissions1 = [];
}
if (!permissions2) {
permissions2 = [];
}
return (
permissions1.filter((value: Permission) => {
return permissions2.includes(value);

View File

@@ -21,6 +21,7 @@ PROBE_API_HOSTNAME={{ .Env.DATA_INGESTOR_HOSTNAME }}
DATA_INGESTOR_HOSTNAME={{ .Env.DATA_INGESTOR_HOSTNAME }}
ACCOUNTS_HOSTNAME={{ .Env.ACCOUNTS_HOSTNAME }}
HOME_HOSTNAME={{ .Env.HOME_HOSTNAME }}
WORKER_HOSTNAME={{ .Env.WORKER_HOSTNAME }}
BILLING_PRIVATE_KEY={{ .Env.BILLING_PRIVATE_KEY }}
BILLING_PUBLIC_KEY={{ .Env.BILLING_PUBLIC_KEY }}
@@ -41,4 +42,6 @@ HELMCHARTS_ROUTE={{ .Env.HELMCHARTS_ROUTE }}
APIDOCS_ROUTE={{ .Env.APIDOCS_ROUTE }}
IDENTITY_ROUTE={{ .Env.IDENTITY_ROUTE }}
FILE_ROUTE={{ .Env.FILE_ROUTE }}
STATUS_PAGE_ROUTE={{ .Env.STATUS_PAGE_ROUTE }}
STATUS_PAGE_ROUTE={{ .Env.STATUS_PAGE_ROUTE }}
IS_SERVER=true

View File

@@ -14,21 +14,24 @@ export const BillingPrivateKey: string =
process.env['BILLING_PRIVATE_KEY'] || '';
export const DatabaseHost: Hostname = Hostname.fromString(
process.env['DATABASE_HOST'] || ''
process.env['DATABASE_HOST'] || 'postgres'
);
export const DatabasePort: Port = new Port(process.env['DATABASE_PORT'] || '');
export const DatabasePort: Port = new Port(
process.env['DATABASE_PORT'] || '5432'
);
export const DatabaseUsername: string =
process.env['DATABASE_USERNAME'] || 'oneuptimedbuser';
export const DatabasePassword: string = process.env['DATABASE_PASSWORD'] || '';
export const DatabasePassword: string =
process.env['DATABASE_PASSWORD'] || 'password';
export const DatabaseName: string =
process.env['DATABASE_NAME'] || 'oneuptimedb';
export const EncryptionSecret: ObjectID = new ObjectID(
process.env['ENCRYPTION_SECRET'] || ''
process.env['ENCRYPTION_SECRET'] || 'secret'
);
export const AirtableApiKey: string = process.env['AIRTABLE_API_KEY'] || '';
@@ -36,93 +39,100 @@ export const AirtableApiKey: string = process.env['AIRTABLE_API_KEY'] || '';
export const AirtableBaseId: string = process.env['AIRTABLE_BASE_ID'] || '';
export const ClusterKey: ObjectID = new ObjectID(
process.env['ONEUPTIME_SECRET'] || ''
process.env['ONEUPTIME_SECRET'] || 'secret'
);
export const Domain: Hostname = Hostname.fromString(
process.env['DOMAIN'] || ''
process.env['DOMAIN'] || 'localhost'
);
export const RealtimeHostname: Hostname = Hostname.fromString(
process.env['REALTIME_HOSTNAME'] || ''
process.env['REALTIME_HOSTNAME'] || 'realtime'
);
export const MailHostname: Hostname = Hostname.fromString(
process.env['MAIL_HOSTNAME'] || ''
process.env['MAIL_HOSTNAME'] || 'mail'
);
export const WorkerHostname: Hostname = Hostname.fromString(
process.env['WORKER_HOSTNAME'] || 'mail'
);
export const DashboardApiHostname: Hostname = Hostname.fromString(
process.env['DASHBOARD_API_HOSTNAME'] || ''
process.env['DASHBOARD_API_HOSTNAME'] || 'dashboard-api'
);
export const ProbeApiHostname: Hostname = Hostname.fromString(
process.env['PROBE_API_HOSTNAME'] || ''
process.env['PROBE_API_HOSTNAME'] || 'probe-api'
);
export const DataIngestorHostname: Hostname = Hostname.fromString(
process.env['DATA_INGESTOR_HOSTNAME'] || ''
process.env['DATA_INGESTOR_HOSTNAME'] || 'daat-ingestor'
);
export const AccountsHostname: Hostname = Hostname.fromString(
process.env['ACCOUNTS_HOSTNAME'] || ''
process.env['ACCOUNTS_HOSTNAME'] || 'accounts'
);
export const HomeHostname: Hostname = Hostname.fromString(
process.env['HOME_HOSTNAME'] || ''
process.env['HOME_HOSTNAME'] || 'home'
);
export const DashboardHostname: Hostname = Hostname.fromString(
process.env['DASHBOARD_HOSTNAME'] || ''
process.env['DASHBOARD_HOSTNAME'] || 'dashboard'
);
export const Env: string = process.env['NODE_ENV'] || '';
export const Env: string = process.env['NODE_ENV'] || 'production';
export const Version: string = process.env['npm_package_version'] || '';
export const Version: string = process.env['npm_package_version'] || '1.0.0';
export const HttpProtocol: Protocol = (
process.env['HTTP_PROTOCOL'] || ''
process.env['HTTP_PROTOCOL'] || 'https'
).includes('https')
? Protocol.HTTPS
: Protocol.HTTP;
// Redis does not require password.
export const RedisHostname: string = process.env['REDIS_HOST'] || '';
export const RedisPassword: string = process.env['REDIS_PASSWORD'] || '';
export const RedisPort: Port = new Port(process.env['REDIS_PORT'] || '');
export const RedisHostname: string = process.env['REDIS_HOST'] || 'redis';
export const RedisPassword: string =
process.env['REDIS_PASSWORD'] || 'password';
export const RedisPort: Port = new Port(process.env['REDIS_PORT'] || '6379');
export const DashboardApiRoute: Route = new Route(
process.env['DASHBOARD_API_ROUTE'] || ''
process.env['DASHBOARD_API_ROUTE'] || '/dashboard-api'
);
export const IdentityRoute: Route = new Route(
process.env['IDENTITY_ROUTE'] || ''
process.env['IDENTITY_ROUTE'] || '/identity'
);
export const FileRoute: Route = new Route(process.env['FILE_ROUTE'] || '');
export const FileRoute: Route = new Route(process.env['FILE_ROUTE'] || '/file');
export const StausPageRoute: Route = new Route(
process.env['STATUS_PAGE_ROUTE'] || ''
process.env['STATUS_PAGE_ROUTE'] || '/status-page'
);
export const DashboardRoute: Route = new Route(
process.env['DASHBOARD_ROUTE'] || ''
process.env['DASHBOARD_ROUTE'] || '/dashboard'
);
export const IntegrationRoute: Route = new Route(
process.env['INTEGRATION_ROUTE'] || ''
process.env['INTEGRATION_ROUTE'] || '/integration'
);
export const HelmRoute: Route = new Route(process.env['HELMCHART_ROUTE'] || '');
export const HelmRoute: Route = new Route(
process.env['HELMCHART_ROUTE'] || '/helm-chart'
);
export const AccountsRoute: Route = new Route(
process.env['ACCOUNTS_ROUTE'] || ''
process.env['ACCOUNTS_ROUTE'] || '/accounts'
);
export const ApiDocsRoute: Route = new Route(
process.env['APIDOCS_ROUTE'] || ''
process.env['APIDOCS_ROUTE'] || '/api-docs'
);
export const AdminDashboardRoute: Route = new Route(
process.env['ADMINDASHBOARD_ROUTE'] || ''
process.env['ADMINDASHBOARD_ROUTE'] || '/admin-dashboard'
);
export const IsProduction: boolean =

View File

@@ -26,7 +26,7 @@ export default class Database {
password: DatabasePassword,
database: DatabaseName,
entities: Entities,
logging: 'all',
//logging: 'all',
synchronize:
Env === AppEnvironment.Test ||
Env === AppEnvironment.Development,

View File

@@ -17,7 +17,6 @@ export class Service extends DatabaseService<Model> {
): Promise<OnCreate<Model>> {
createBy.data.domainVerificationText =
'oneuptime-verification-' + Text.generateRandomText(20);
createBy.data.isVerified = false;
return Promise.resolve({ createBy, carryForward: null });
}

View File

@@ -0,0 +1,11 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/GreenlockCertificate';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -0,0 +1,11 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/GreenlockChallenge';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -7,6 +7,13 @@ import DeleteBy from '../Types/Database/DeleteBy';
import ObjectID from 'Common/Types/ObjectID';
import PositiveNumber from 'Common/Types/PositiveNumber';
import SortOrder from 'Common/Types/Database/SortOrder';
import IncidentState from 'Model/Models/IncidentState';
import IncidentStateService from './IncidentStateService';
import Incident from 'Model/Models/Incident';
import MonitorStatusService from './MonitorStatusService';
import MonitorStatus from 'Model/Models/MonitorStatus';
import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline';
import MonitorStatusTimelineService from './MonitorStatusTimelineService';
export class Service extends DatabaseService<IncidentStateTimeline> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -35,6 +42,79 @@ export class Service extends DatabaseService<IncidentStateTimeline> {
props: onCreate.createBy.props,
});
// TODO: DELETE THIS WHEN WORKFLOW IS IMPLEMENMTED.
// check if this is resolved state, and if it is then resolve all the monitors.
const isResolvedState: IncidentState | null =
await IncidentStateService.findOneBy({
query: {
_id: createdItem.incidentStateId.toString()!,
isResolvedState: true,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (isResolvedState) {
// resolve all the monitors.
const incident: Incident | null = await IncidentService.findOneBy({
query: {
_id: createdItem.incidentId?.toString(),
},
select: {
_id: true,
projectId: true,
},
populate: {
monitors: {
_id: true,
},
},
props: {
isRoot: true,
},
});
if (incident && incident.monitors && incident.monitors.length > 0) {
// get resolved monitor state.
const resolvedMonitorState: MonitorStatus | null =
await MonitorStatusService.findOneBy({
query: {
projectId: incident.projectId!,
isOperationalState: true,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (resolvedMonitorState) {
for (const monitor of incident.monitors) {
const monitorStausTimeline: MonitorStatusTimeline =
new MonitorStatusTimeline();
monitorStausTimeline.monitorId = monitor.id!;
monitorStausTimeline.projectId = incident.projectId!;
monitorStausTimeline.monitorStatusId =
resolvedMonitorState.id!;
await MonitorStatusTimelineService.create({
data: monitorStausTimeline,
props: {
isRoot: true,
},
});
}
}
}
}
return createdItem;
}

View File

@@ -537,6 +537,15 @@ export class Service extends DatabaseService<Model> {
},
});
await TeamMemberService.refreshTokens(
createdItem.createdByUserId!,
createdItem.id!
);
await TeamMemberService.updateSubscriptionSeatsByUnqiqueTeamMembersInProject(
createdItem.id!
);
return createdItem;
}

View File

@@ -7,6 +7,13 @@ import DeleteBy from '../Types/Database/DeleteBy';
import ObjectID from 'Common/Types/ObjectID';
import PositiveNumber from 'Common/Types/PositiveNumber';
import SortOrder from 'Common/Types/Database/SortOrder';
import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState';
import ScheduledMaintenanceStateService from './ScheduledMaintenanceStateService';
import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance';
import MonitorStatus from 'Model/Models/MonitorStatus';
import MonitorStatusService from './MonitorStatusService';
import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline';
import MonitorStatusTimelineService from './MonitorStatusTimelineService';
export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -14,7 +21,7 @@ export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline>
}
protected override async onCreateSuccess(
onCreate: OnCreate<ScheduledMaintenanceStateTimeline>,
_onCreate: OnCreate<ScheduledMaintenanceStateTimeline>,
createdItem: ScheduledMaintenanceStateTimeline
): Promise<ScheduledMaintenanceStateTimeline> {
if (!createdItem.scheduledMaintenanceId) {
@@ -33,9 +40,90 @@ export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline>
currentScheduledMaintenanceStateId:
createdItem.scheduledMaintenanceStateId,
},
props: onCreate.createBy.props,
props: {
isRoot: true,
},
});
// TODO: DELETE THIS WHEN WORKFLOW IS IMPLEMENMTED.
// check if this is resolved state, and if it is then resolve all the monitors.
const isResolvedState: ScheduledMaintenanceState | null =
await ScheduledMaintenanceStateService.findOneBy({
query: {
_id: createdItem.scheduledMaintenanceStateId.toString()!,
isResolvedState: true,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (isResolvedState) {
// resolve all the monitors.
const scheduledMaintenanceService: ScheduledMaintenance | null =
await ScheduledMaintenanceService.findOneBy({
query: {
_id: createdItem.scheduledMaintenanceId?.toString(),
},
select: {
_id: true,
projectId: true,
},
populate: {
monitors: {
_id: true,
},
},
props: {
isRoot: true,
},
});
if (
scheduledMaintenanceService &&
scheduledMaintenanceService.monitors &&
scheduledMaintenanceService.monitors.length > 0
) {
// get resolved monitor state.
const resolvedMonitorState: MonitorStatus | null =
await MonitorStatusService.findOneBy({
query: {
projectId: scheduledMaintenanceService.projectId!,
isOperationalState: true,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (resolvedMonitorState) {
for (const monitor of scheduledMaintenanceService.monitors) {
const monitorStausTimeline: MonitorStatusTimeline =
new MonitorStatusTimeline();
monitorStausTimeline.monitorId = monitor.id!;
monitorStausTimeline.projectId =
scheduledMaintenanceService.projectId!;
monitorStausTimeline.monitorStatusId =
resolvedMonitorState.id!;
await MonitorStatusTimelineService.create({
data: monitorStausTimeline,
props: {
isRoot: true,
},
});
}
}
}
}
return createdItem;
}

View File

@@ -0,0 +1,46 @@
import EmptyResponseData from 'Common/Types/API/EmptyResponse';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import { JSONObject } from 'Common/Types/JSON';
import API from 'Common/Utils/API';
import { HttpProtocol, WorkerHostname } from '../Config';
export default class StatusPageCertificateService {
public static async add(
domain: string
): Promise<HTTPResponse<EmptyResponseData>> {
const body: JSONObject = {
domain: domain,
};
return await API.post<EmptyResponseData>(
new URL(HttpProtocol, WorkerHostname, new Route('/cert')),
body
);
}
public static async remove(
domain: string
): Promise<HTTPResponse<EmptyResponseData>> {
const body: JSONObject = {
domain: domain,
};
return await API.delete<EmptyResponseData>(
new URL(HttpProtocol, WorkerHostname, new Route('/cert')),
body
);
}
public static async get(domain: string): Promise<HTTPResponse<JSONObject>> {
const body: JSONObject = {
domain: domain,
};
return await API.get<JSONObject>(
new URL(HttpProtocol, WorkerHostname, new Route('/cert')),
body
);
}
}

View File

@@ -1,9 +1,14 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/StatusPageDomain';
import DatabaseService, { OnCreate } from './DatabaseService';
import DatabaseService, { OnCreate, OnDelete } from './DatabaseService';
import CreateBy from '../Types/Database/CreateBy';
import DomainService from './DomainService';
import Domain from 'Model/Models/Domain';
import BadDataException from 'Common/Types/Exception/BadDataException';
import StatusPageCertificateService from './StatusPageCertificateService';
import DeleteBy from '../Types/Database/DeleteBy';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import ObjectID from 'Common/Types/ObjectID';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -21,18 +26,59 @@ export class Service extends DatabaseService<Model> {
'',
},
populate: {},
select: { domain: true },
select: { domain: true, isVerified: true },
props: {
isRoot: true,
},
});
if (!domain?.isVerified) {
throw new BadDataException(
'This domain is not verified. Please verify it by going to Settings > Domains'
);
}
if (domain) {
createBy.data.fullDomain =
createBy.data.subdomain + '.' + domain.domain;
}
createBy.data.cnameVerificationToken = ObjectID.generate().toString();
return { createBy, carryForward: null };
}
protected override async onBeforeDelete(
deleteBy: DeleteBy<Model>
): Promise<OnDelete<Model>> {
const domains: Array<Model> = await this.findBy({
query: {
...deleteBy.query,
isAddedtoGreenlock: true,
},
populate: {},
skip: 0,
limit: LIMIT_MAX,
select: { fullDomain: true },
props: {
isRoot: true,
},
});
return { deleteBy, carryForward: domains };
}
protected override async onDeleteSuccess(
onDelete: OnDelete<Model>,
_itemIdsBeforeDelete: ObjectID[]
): Promise<OnDelete<Model>> {
for (const domain of onDelete.carryForward) {
await StatusPageCertificateService.remove(
domain.fullDomain as string
);
}
return onDelete;
}
}
export default new Service();

View File

@@ -40,9 +40,7 @@ export class Service extends DatabaseService<TeamMember> {
protected override async onBeforeCreate(
createBy: CreateBy<TeamMember>
): Promise<OnCreate<TeamMember>> {
if (!createBy.data.hasAcceptedInvitation) {
createBy.data.hasAcceptedInvitation = false;
}
createBy.data.hasAcceptedInvitation = false;
if (createBy.miscDataProps && createBy.miscDataProps['email']) {
const email: Email = new Email(
@@ -91,10 +89,31 @@ export class Service extends DatabaseService<TeamMember> {
}
}
//check if this user is already ivnited.
const member: TeamMember | null = await this.findOneBy({
query: {
userId: createBy.data.userId!,
teamId: createBy.data.teamId!,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (member) {
throw new BadDataException(
'This user has already been invited to this team'
);
}
return { createBy, carryForward: null };
}
private async refreshTokens(
public async refreshTokens(
userId: ObjectID,
projectId: ObjectID
): Promise<void> {
@@ -161,6 +180,7 @@ export class Service extends DatabaseService<TeamMember> {
projectId: true,
team: true,
teamId: true,
hasAcceptedInvitation: true,
},
limit: LIMIT_MAX,
skip: 0,
@@ -178,9 +198,14 @@ export class Service extends DatabaseService<TeamMember> {
// check if there's one member in the team.
for (const member of members) {
if (member.team?.shouldHaveAtleastOneMember) {
if (!member.hasAcceptedInvitation) {
continue;
}
const membersInTeam: PositiveNumber = await this.countBy({
query: {
_id: member.teamId?.toString() as string,
teamId: member.teamId!,
hasAcceptedInvitation: true,
},
skip: 0,
limit: LIMIT_MAX,
@@ -191,7 +216,7 @@ export class Service extends DatabaseService<TeamMember> {
if (membersInTeam.toNumber() <= 1) {
throw new BadDataException(
'This team should have atleast 1 member'
'This team should have atleast 1 member who has accepted the invitation.'
);
}
}

View File

@@ -1,8 +1,20 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/User';
import DatabaseService from './DatabaseService';
import DatabaseService, { OnUpdate } from './DatabaseService';
import DatabaseCommonInteractionProps from 'Common/Types/Database/DatabaseCommonInteractionProps';
import Email from 'Common/Types/Email';
import ObjectID from 'Common/Types/ObjectID';
import MailService from './MailService';
import UpdateBy from '../Types/Database/UpdateBy';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import { AccountsRoute, Domain, HttpProtocol } from '../Config';
import logger from '../Utils/Logger';
import URL from 'Common/Types/API/URL';
import EmailVerificationToken from 'Model/Models/EmailVerificationToken';
import OneUptimeDate from 'Common/Types/Date';
import EmailVerificationTokenService from './EmailVerificationTokenService';
import Route from 'Common/Types/API/Route';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -24,6 +36,129 @@ export class Service extends DatabaseService<Model> {
});
}
protected override async onBeforeUpdate(
updateBy: UpdateBy<Model>
): Promise<OnUpdate<Model>> {
if (updateBy.data.password || updateBy.data.email) {
const users: Array<Model> = await this.findBy({
query: updateBy.query,
select: {
_id: true,
email: true,
},
props: {
isRoot: true,
},
limit: LIMIT_MAX,
skip: 0,
});
return { updateBy, carryForward: users };
}
return { updateBy, carryForward: [] };
}
protected override async onUpdateSuccess(
onUpdate: OnUpdate<Model>,
_updatedItemIds: ObjectID[]
): Promise<OnUpdate<Model>> {
if (onUpdate && onUpdate.updateBy.data.password) {
for (const user of onUpdate.carryForward) {
// password changed, send password changed mail
MailService.sendMail({
toEmail: user.email!,
subject: 'Password Changed.',
templateType: EmailTemplateType.PasswordChanged,
vars: {
homeURL: new URL(HttpProtocol, Domain).toString(),
},
}).catch((err: Error) => {
logger.error(err);
});
}
}
if (onUpdate && onUpdate.updateBy.data.email) {
const newUsers: Array<Model> = await this.findBy({
query: onUpdate.updateBy.query,
select: {
_id: true,
email: true,
name: true,
},
props: {
isRoot: true,
},
limit: LIMIT_MAX,
skip: 0,
});
for (const user of onUpdate.carryForward) {
const newUser: Model | undefined = newUsers.find((u: Model) => {
return u._id?.toString() === user._id.toString();
});
if (
newUser &&
newUser.email?.toString() !== user.email.toString()
) {
// password changed, send password changed mail
const generatedToken: ObjectID = ObjectID.generate();
const emailVerificationToken: EmailVerificationToken =
new EmailVerificationToken();
emailVerificationToken.userId = user?.id!;
emailVerificationToken.email = newUser?.email!;
emailVerificationToken.token = generatedToken;
emailVerificationToken.expires =
OneUptimeDate.getOneDayAfter();
await EmailVerificationTokenService.create({
data: emailVerificationToken,
props: {
isRoot: true,
},
});
MailService.sendMail({
toEmail: newUser.email!,
subject:
'You have changed your email. Please verify your email.',
templateType: EmailTemplateType.EmailChanged,
vars: {
name: newUser.name!.toString(),
tokenVerifyUrl: new URL(
HttpProtocol,
Domain,
new Route(AccountsRoute.toString()).addRoute(
'/verify-email/' + generatedToken.toString()
)
).toString(),
homeUrl: new URL(HttpProtocol, Domain).toString(),
},
}).catch((err: Error) => {
logger.error(err);
});
await this.updateBy({
query: {
_id: user.id.toString(),
},
data: {
isEmailVerified: false,
},
props: {
isRoot: true,
ignoreHooks: true,
},
});
}
}
}
return onUpdate;
}
public async createByEmail(
email: Email,
props: DatabaseCommonInteractionProps

View File

@@ -10,6 +10,7 @@ import {
} from 'Common/Types/Permission';
import UserType from 'Common/Types/UserType';
import Dictionary from 'Common/Types/Dictionary';
import Port from 'Common/Types/Port';
export type RequestHandler = express.RequestHandler;
export type NextFunction = express.NextFunction;
@@ -63,16 +64,17 @@ class Express {
}
public static async launchApplication(
appName: string
appName: string,
port?: Port
): Promise<express.Application> {
return new Promise<express.Application>((resolve: Function) => {
if (!this.app) {
this.setupExpress();
}
if (!this.app) {
this.setupExpress();
}
this.app.listen(this.app.get('port'), () => {
return new Promise<express.Application>((resolve: Function) => {
this.app.listen(port?.toNumber() || this.app.get('port'), () => {
// eslint-disable-next-line
logger.info(`${appName} server started on port: ${this.app.get('port')}`);
logger.info(`${appName} server started on port: ${port?.toNumber() || this.app.get('port')}`);
return resolve(this.app);
});
});

View File

@@ -243,4 +243,24 @@ export default class Response {
oneUptimeResponse.status(200).send(item);
this.logResponse(req, res, item as JSONObject);
}
public static sendTextResponse(
req: ExpressRequest,
res: ExpressResponse,
text: string
): void {
const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
oneUptimeResponse.set(
'ExpressRequest-Id',
oneUptimeRequest.id.toString()
);
oneUptimeResponse.set('Pod-Id', process.env['POD_NAME']);
oneUptimeResponse.logBody = { text: text as string };
oneUptimeResponse.status(200).send(text);
this.logResponse(req, res, { text: text as string });
}
}

View File

@@ -2,7 +2,7 @@ import './Envrionment';
import './Process';
import logger from './Logger';
import cors from 'cors';
import Port from 'Common/Types/Port';
import Express, {
ExpressRequest,
ExpressResponse,
@@ -16,6 +16,7 @@ import Express, {
// Connect common api's.
import CommonAPI from '../API/Index';
import NotFoundException from 'Common/Types/Exception/NotFoundException';
import OneUptimeDate from 'Common/Types/Date';
import LocalCache from '../Infrastructure/LocalCache';
@@ -23,6 +24,7 @@ import Exception from 'Common/Types/Exception/Exception';
import ObjectID from 'Common/Types/ObjectID';
import StatusCode from 'Common/Types/API/StatusCode';
import Typeof from 'Common/Types/Typeof';
import Response from './Response';
// import OpenTelemetrySDK from "./OpenTelemetry";
const app: ExpressApplication = Express.getExpressApp();
@@ -89,8 +91,11 @@ app.use(ExpressUrlEncoded({ limit: '50mb' }));
app.use(logRequest);
const init: Function = async (appName: string): Promise<ExpressApplication> => {
await Express.launchApplication(appName);
const init: Function = async (
appName: string,
port?: Port
): Promise<ExpressApplication> => {
await Express.launchApplication(appName, port);
LocalCache.setString('app', 'name', appName);
CommonAPI(appName);
@@ -132,20 +137,36 @@ const init: Function = async (appName: string): Promise<ExpressApplication> => {
}
);
app.post('*', (_req: ExpressRequest, res: ExpressResponse) => {
res.status(404).json({ error: '404 - Not Found.' });
app.post('*', (req: ExpressRequest, res: ExpressResponse) => {
return Response.sendErrorResponse(
req,
res,
new NotFoundException('Not found')
);
});
app.put('*', (_req: ExpressRequest, res: ExpressResponse) => {
res.status(404).json({ error: '404 - Not Found.' });
app.put('*', (req: ExpressRequest, res: ExpressResponse) => {
return Response.sendErrorResponse(
req,
res,
new NotFoundException('Not found')
);
});
app.delete('*', (_req: ExpressRequest, res: ExpressResponse) => {
res.status(404).json({ error: '404 - Not Found.' });
app.delete('*', (req: ExpressRequest, res: ExpressResponse) => {
return Response.sendErrorResponse(
req,
res,
new NotFoundException('Not found')
);
});
app.get('*', (_req: ExpressRequest, res: ExpressResponse) => {
res.status(404).json({ error: '404 - Not Found.' });
app.get('*', (req: ExpressRequest, res: ExpressResponse) => {
return Response.sendErrorResponse(
req,
res,
new NotFoundException('Not found')
);
});
// await OpenTelemetrySDK.start();

View File

@@ -6,4 +6,4 @@ cd ..
# Run Preinstall.
npm run prerun
# Run Postgres
docker-compose up -d postgres
docker compose up -d postgres

View File

@@ -11,4 +11,6 @@ APIDOCS_ROUTE={{ .Env.APIDOCS_ROUTE }}
IDENTITY_ROUTE={{ .Env.IDENTITY_ROUTE }}
FILE_ROUTE={{ .Env.FILE_ROUTE }}
STATUS_PAGE_ROUTE={{ .Env.STATUS_PAGE_ROUTE }}
IS_SERVER=false
STATUS_PAGE_CNAME_RECORD={{ .Env.STATUS_PAGE_CNAME_RECORD }}

View File

@@ -25,6 +25,7 @@ export interface ComponentProps {
size?: undefined | AlertSize;
color?: undefined | Color;
dataTestId?: string;
style?: React.CSSProperties | undefined;
}
const Alert: FunctionComponent<ComponentProps> = (
@@ -63,7 +64,7 @@ const Alert: FunctionComponent<ComponentProps> = (
const rgb: RGB = Color.colorToRgb(props.color || Black);
return (
<div className="row">
<div className="row" style={props.style}>
<div className="col-xl-12">
<div
data-testid={props.dataTestId}

View File

@@ -18,6 +18,8 @@ export interface ComponentProps {
buttons?: undefined | Array<CardButtonSchema>;
children?: undefined | Array<ReactElement> | ReactElement;
cardBodyStyle?: undefined | CSSProperties;
className?: string | undefined;
style?: React.CSSProperties | undefined;
}
const Card: FunctionComponent<ComponentProps> = (
@@ -25,7 +27,7 @@ const Card: FunctionComponent<ComponentProps> = (
): ReactElement => {
return (
<React.Fragment>
<div className="row">
<div className={`${props.className || ''} row`} style={props.style}>
<div className="col-xl-12">
<div className="card">
<div className="card-header justify-space-between">

View File

@@ -15,6 +15,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
onCountFetchInit?: (() => void) | undefined;
onClick?: (() => void) | undefined;
refreshToggle?: boolean | undefined;
style?: React.CSSProperties | undefined;
}
const CounterModelAlert: Function = <TBaseModel extends BaseModel>(
@@ -78,6 +79,7 @@ const CounterModelAlert: Function = <TBaseModel extends BaseModel>(
return (
<Alert
style={props.style}
onClick={props.onClick}
type={props.alertType}
title={`${count} ${

View File

@@ -58,6 +58,7 @@ export const DefaultValidateFunction: Function = (
export interface ComponentProps<T extends Object> {
id: string;
submitButtonStyleType?: ButtonStyleType | undefined;
initialValues: FormValues<T>;
onSubmit: (values: FormValues<T>) => void;
onValidate?: undefined | ((values: FormValues<T>) => JSONObject);
@@ -1102,7 +1103,10 @@ const BasicForm: Function = <T extends Object>(
type={ButtonTypes.Submit}
id={`${props.id}-submit-button`}
isLoading={props.isLoading || false}
buttonStyle={ButtonStyleType.PRIMARY}
buttonStyle={
props.submitButtonStyleType ||
ButtonStyleType.PRIMARY
}
style={{
width: props.maxPrimaryButtonWidth
? '100%'

View File

@@ -4,6 +4,7 @@ import BaseModel from 'Common/Models/BaseModel';
import FormValues from './Types/FormValues';
import Fields from './Types/Fields';
import BasicForm, { DefaultValidateFunction } from './BasicForm';
import { ButtonStyleType } from '../Button/Button';
export interface ComponentProps<TBaseModel extends BaseModel> {
model: TBaseModel;
@@ -16,6 +17,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
) => FormikErrors<FormValues<TBaseModel>>);
fields: Fields<TBaseModel>;
submitButtonText?: undefined | string;
submitButtonStyleType?: ButtonStyleType | undefined;
title?: undefined | string;
description?: undefined | string;
showAsColumns?: undefined | number;
@@ -77,6 +79,7 @@ const BasicModelForm: Function = <TBaseModel extends BaseModel>(
onValidate={
props.onValidate ? props.onValidate : DefaultValidateFunction
}
submitButtonStyleType={props.submitButtonStyleType}
onSubmit={props.onSubmit}
initialValues={initialValues}
submitButtonText={props.submitButtonText || 'Save'}

View File

@@ -36,6 +36,7 @@ import FileModel from 'Common/Models/FileModel';
import TableColumnType from 'Common/Types/Database/TableColumnType';
import Typeof from 'Common/Types/Typeof';
import { TableColumnMetadata } from 'Common/Types/Database/TableColumn';
import { ButtonStyleType } from '../Button/Button';
export enum FormType {
Create,
@@ -65,6 +66,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
apiUrl?: undefined | URL;
formType: FormType;
hideSubmitButton?: undefined | boolean;
submitButtonStyleType?: ButtonStyleType | undefined;
formRef?: undefined | MutableRefObject<FormikProps<FormikValues>>;
onLoadingChange?: undefined | ((isLoading: boolean) => void);
initialValues?: FormValues<TBaseModel> | undefined;
@@ -72,6 +74,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
onError?: ((error: string) => void) | undefined;
onBeforeCreate?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined;
saveRequestOptions?: RequestOptions | undefined;
doNotFetchExistingModel?: boolean | undefined;
}
const ModelForm: Function = <TBaseModel extends BaseModel>(
@@ -316,7 +319,11 @@ const ModelForm: Function = <TBaseModel extends BaseModel>(
};
useAsyncEffect(async () => {
if (props.modelIdToEdit && props.formType === FormType.Update) {
if (
props.modelIdToEdit &&
props.formType === FormType.Update &&
!props.doNotFetchExistingModel
) {
// get item.
setLoading(true);
setIsFetching(true);
@@ -476,26 +483,29 @@ const ModelForm: Function = <TBaseModel extends BaseModel>(
}
return (
<BasicModelForm<TBaseModel>
title={props.title}
description={props.description}
model={model}
id={props.id}
fields={fields}
showAsColumns={props.showAsColumns}
footer={props.footer}
isLoading={isLoading}
submitButtonText={props.submitButtonText}
cancelButtonText={props.cancelButtonText}
onSubmit={onSubmit}
onValidate={props.onValidate}
onCancel={props.onCancel}
maxPrimaryButtonWidth={props.maxPrimaryButtonWidth}
error={error}
hideSubmitButton={props.hideSubmitButton}
formRef={props.formRef}
initialValues={itemToEdit || props.initialValues}
></BasicModelForm>
<div>
<BasicModelForm<TBaseModel>
title={props.title}
description={props.description}
model={model}
id={props.id}
fields={fields}
showAsColumns={props.showAsColumns}
footer={props.footer}
isLoading={isLoading}
submitButtonText={props.submitButtonText}
cancelButtonText={props.cancelButtonText}
onSubmit={onSubmit}
submitButtonStyleType={props.submitButtonStyleType}
onValidate={props.onValidate}
onCancel={props.onCancel}
maxPrimaryButtonWidth={props.maxPrimaryButtonWidth}
error={error}
hideSubmitButton={props.hideSubmitButton}
formRef={props.formRef}
initialValues={itemToEdit || props.initialValues}
></BasicModelForm>
</div>
);
};

View File

@@ -3,11 +3,13 @@ import Name from 'Common/Types/Name';
import React, { FunctionComponent, ReactElement } from 'react';
import Icon, { IconProp } from '../../Icon/Icon';
import useComponentOutsideClick from '../../../Types/UseComponentOutsideClick';
import Route from 'Common/Types/API/Route';
import Image from '../../Image/Image';
export interface ComponentProps {
userFullName: Name;
children: ReactElement | Array<ReactElement>;
userProfilePicture: URL;
userProfilePicture: URL | Route;
}
const UserProfile: FunctionComponent<ComponentProps> = (
@@ -30,11 +32,11 @@ const UserProfile: FunctionComponent<ComponentProps> = (
alignItems: 'center',
}}
>
<img
<Image
className="rounded-circle header-profile-user"
src={props.userProfilePicture.toString()}
alt="Header Avatar"
imageUrl={props.userProfilePicture}
/>
<span className="d-none d-xl-inline-block ms-2 me-1">
{props.userFullName.toString()}
</span>

View File

@@ -7,7 +7,8 @@ import Link from '../../Link/Link';
export interface ComponentProps {
title: string;
badge?: undefined | number;
route: Route;
route?: Route | undefined;
onClick?: (() => void) | undefined;
icon: IconProp;
iconColor?: undefined | Color;
}
@@ -18,7 +19,7 @@ const UserProfile: FunctionComponent<ComponentProps> = (
return (
<Link
to={props.route}
onClick={undefined}
onClick={props.onClick}
className="dropdown-item flex"
>
{props.badge ? (

View File

@@ -2,12 +2,14 @@ import Route from 'Common/Types/API/Route';
import BadDataException from 'Common/Types/Exception/BadDataException';
import File from 'Model/Models/File';
import React, { FunctionComponent, ReactElement } from 'react';
import URLFromProject from 'Common/Types/API/URL';
export interface ComponentProps {
onClick: () => void;
imageUrl?: URL | Route | undefined;
onClick?: () => void | undefined;
imageUrl?: URLFromProject | Route | undefined;
height?: number | undefined;
file?: File | undefined;
className?: string | undefined;
}
export class ImageFunctions {
@@ -32,6 +34,7 @@ const Image: FunctionComponent<ComponentProps> = (
}}
src={props.imageUrl.toString()}
height={props.height}
className={props.className}
/>
);
}
@@ -46,6 +49,7 @@ const Image: FunctionComponent<ComponentProps> = (
}}
src={url}
height={props.height}
className={props.className}
/>
);
}

View File

@@ -23,6 +23,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
isEditable?: undefined | boolean;
editButtonText?: undefined | string;
formFields?: undefined | Fields<TBaseModel>;
className?: string | undefined;
}
const CardModelDetail: Function = <TBaseModel extends BaseModel>(
@@ -67,7 +68,11 @@ const CardModelDetail: Function = <TBaseModel extends BaseModel>(
return (
<>
<Card {...props.cardProps} buttons={cardButtons}>
<Card
{...props.cardProps}
className={props.className}
buttons={cardButtons}
>
<ModelDetail
refresher={refresher}
{...props.modelDetailProps}

View File

@@ -36,15 +36,10 @@ const ModelDetail: Function = <TBaseModel extends BaseModel>(
const [error, setError] = useState<string>('');
const [item, setItem] = useState<TBaseModel | null>(null);
const model: TBaseModel = new props.modelType();
const [onBeforeFetchData, setOnBeforeFetchData] = useState<
JSONObject | undefined
>(undefined);
useEffect(() => {
fetchItem();
}, [props.refresher]);
const getSelectFields: Function = (): Select<TBaseModel> => {
const select: Select<TBaseModel> = {};
for (const field of props.fields) {
@@ -68,14 +63,14 @@ const ModelDetail: Function = <TBaseModel extends BaseModel>(
? (Object.keys(field.field)[0] as string)
: null;
if (key && model.isFileColumn(key)) {
if (key && new props.modelType()?.isFileColumn(key)) {
(populate as JSONObject)[key] = {
file: true,
_id: true,
type: true,
name: true,
};
} else if (key && model.isEntityColumn(key)) {
} else if (key && new props.modelType()?.isEntityColumn(key)) {
(populate as JSONObject)[key] = (field.field as any)[key];
}
}
@@ -89,8 +84,10 @@ const ModelDetail: Function = <TBaseModel extends BaseModel>(
const userPermissions: Array<Permission> =
PermissionUtil.getAllPermissions();
const model: BaseModel = new props.modelType();
const accessControl: Dictionary<ColumnAccessControl> =
model.getColumnAccessControlForAllColumns();
model.getColumnAccessControlForAllColumns() || {};
const fieldsToSet: Array<Field<TBaseModel>> = [];
@@ -147,12 +144,10 @@ const ModelDetail: Function = <TBaseModel extends BaseModel>(
};
useEffect(() => {
setDetailFields();
}, []);
useEffect(() => {
setDetailFields();
}, [onBeforeFetchData]);
if (props.modelType) {
setDetailFields();
}
}, [onBeforeFetchData, props.modelType]);
const fetchItem: Function = async (): Promise<void> => {
// get item.
@@ -175,9 +170,9 @@ const ModelDetail: Function = <TBaseModel extends BaseModel>(
if (!item) {
setError(
`Cannot load ${(
model.singularName || 'item'
new props.modelType()?.singularName || 'item'
).toLowerCase()}. It could be because you don't have enough permissions to read this ${(
model.singularName || 'item'
new props.modelType()?.singularName || 'item'
).toLowerCase()}.`
);
}
@@ -204,8 +199,10 @@ const ModelDetail: Function = <TBaseModel extends BaseModel>(
};
useEffect(() => {
fetchItem();
}, []);
if (props.modelId && props.modelType) {
fetchItem();
}
}, [props.modelId, props.refresher, props.modelType]);
if (isLoading) {
return (

View File

@@ -22,6 +22,7 @@ export default interface Columns<TEntity> {
isFilterable: boolean;
filterEntityType?: { new (): BaseModel } | undefined;
filterQuery?: Query<BaseModel> | undefined;
tooltipText?: ((item: TEntity) => string) | undefined;
filterDropdownField?:
| {
label: string;

View File

@@ -322,6 +322,14 @@ const ModelTable: Function = <TBaseModel extends BaseModel>(
) as string,
});
}
if (column.tooltipText) {
classicColumn.tooltipText = (item: JSONObject): string => {
return column.tooltipText!(
BaseModel.fromJSONObject(item, props.modelType)
);
};
}
}
setColumns(classicColumns);
@@ -642,12 +650,24 @@ const ModelTable: Function = <TBaseModel extends BaseModel>(
const key: string | null = getColumnKey(column);
if (hasPermission) {
let tooltipText: ((item: JSONObject) => string) | undefined =
undefined;
if (column.tooltipText) {
tooltipText = (item: JSONObject): string => {
return column.tooltipText!(
BaseModel.fromJSONObject(item, props.modelType)
);
};
}
columns.push({
...column,
disableSort: column.disableSort || shouldDisableSort(key),
key: column.selectedProperty
? key + '.' + column.selectedProperty
: key,
tooltipText,
});
if (key) {

View File

@@ -1,7 +1,7 @@
import OneUptimeDate from 'Common/Types/Date';
import { JSONObject } from 'Common/Types/JSON';
import React, { FunctionComponent, ReactElement, useState } from 'react';
import Button, { ButtonSize } from '../Button/Button';
import Button, { ButtonSize, ButtonStyleType } from '../Button/Button';
import Icon, { IconProp, ThickProp } from '../Icon/Icon';
import ActionButtonSchema from '../ActionButton/ActionButtonSchema';
import Column from './Types/Column';
@@ -30,199 +30,229 @@ const TableRow: FunctionComponent<ComponentProps> = (
}) || []
);
const [tooltipModalText, setTooltipModalText] = useState<string>('');
const [error, setError] = useState<string>('');
const getRow: Function = (provided?: DraggableProvided): ReactElement => {
return (
<tr
className="table-row"
{...provided?.draggableProps}
ref={provided?.innerRef}
>
{props.enableDragAndDrop && (
<td
style={{ width: '20px' }}
className="grabbable"
{...provided?.dragHandleProps}
>
<Icon
icon={IconProp.Drag}
thick={ThickProp.Thick}
<>
<tr
className="table-row"
{...provided?.draggableProps}
ref={provided?.innerRef}
>
{props.enableDragAndDrop && (
<td
style={{ width: '20px' }}
className="grabbable"
/>
</td>
)}
{props.columns &&
props.columns.map((column: Column, i: number) => {
return (
<td
key={i}
style={{
textAlign:
column.type === FieldType.Actions
? 'right'
: 'left',
}}
>
{column.key && !column.getElement ? (
column.type === FieldType.Date ? (
props.item[column.key] ? (
OneUptimeDate.getDateAsLocalFormattedString(
props.item[
column.key
] as string,
true
{...provided?.dragHandleProps}
>
<Icon
icon={IconProp.Drag}
thick={ThickProp.Thick}
className="grabbable"
/>
</td>
)}
{props.columns &&
props.columns.map((column: Column, i: number) => {
return (
<td
key={i}
style={{
textAlign:
column.type === FieldType.Actions
? 'right'
: 'left',
}}
onClick={() => {
if (column.tooltipText) {
setTooltipModalText(
column.tooltipText(props.item)
);
}
}}
>
{column.key && !column.getElement ? (
column.type === FieldType.Date ? (
props.item[column.key] ? (
OneUptimeDate.getDateAsLocalFormattedString(
props.item[
column.key
] as string,
true
)
) : (
''
)
) : column.type ===
FieldType.DateTime ? (
props.item[column.key] ? (
OneUptimeDate.getDateAsLocalFormattedString(
props.item[
column.key
] as string,
false
)
) : (
''
)
) : column.type ===
FieldType.Boolean ? (
props.item[column.key] ? (
<Icon
icon={IconProp.True}
thick={ThickProp.Thick}
/>
) : (
<Icon
icon={IconProp.False}
thick={ThickProp.Thick}
/>
)
) : (
''
)
) : column.type === FieldType.DateTime ? (
props.item[column.key] ? (
OneUptimeDate.getDateAsLocalFormattedString(
props.item[
column.key
] as string,
false
)
) : (
''
)
) : column.type === FieldType.Boolean ? (
props.item[column.key] ? (
<Icon
icon={IconProp.True}
thick={ThickProp.Thick}
/>
) : (
<Icon
icon={IconProp.False}
thick={ThickProp.Thick}
/>
_.get(
props.item,
column.key,
''
)?.toString() || ''
)
) : (
_.get(
props.item,
column.key,
''
)?.toString() || ''
)
) : (
<></>
)}
<></>
)}
{column.key && column.getElement ? (
column.getElement(props.item)
) : (
<></>
)}
{column.type === FieldType.Actions && (
<div>
{error && (
<div className="text-align-left">
<ConfirmModal
title={`Error`}
description={error}
submitButtonText={'Close'}
onSubmit={() => {
return setError('');
}}
/>
</div>
)}
{props.actionButtons?.map(
(
button: ActionButtonSchema,
i: number
) => {
if (
button.isVisible &&
!button.isVisible(
props.item
)
) {
return <></>;
}
return (
<span
style={
i > 0
? {
marginLeft:
'10px',
}
: {}
{column.key && column.getElement ? (
column.getElement(props.item)
) : (
<></>
)}
{column.type === FieldType.Actions && (
<div>
{error && (
<div className="text-align-left">
<ConfirmModal
title={`Error`}
description={error}
submitButtonText={
'Close'
}
key={i}
>
<Button
buttonSize={
ButtonSize.Small
onSubmit={() => {
return setError('');
}}
/>
</div>
)}
{props.actionButtons?.map(
(
button: ActionButtonSchema,
i: number
) => {
if (
button.isVisible &&
!button.isVisible(
props.item
)
) {
return <></>;
}
return (
<span
style={
i > 0
? {
marginLeft:
'10px',
}
: {}
}
title={button.title}
icon={button.icon}
buttonStyle={
button.buttonStyleType
}
isLoading={
isButtonLoading[
i
]
}
onClick={() => {
if (
button.onClick
) {
key={i}
>
<Button
buttonSize={
ButtonSize.Small
}
title={
button.title
}
icon={
button.icon
}
buttonStyle={
button.buttonStyleType
}
isLoading={
isButtonLoading[
i
] = true;
setIsButtonLoading(
isButtonLoading
);
button.onClick(
props.item,
() => {
// on aciton complete
isButtonLoading[
i
] =
false;
setIsButtonLoading(
isButtonLoading
);
},
(
err: Error
) => {
isButtonLoading[
i
] =
false;
setIsButtonLoading(
isButtonLoading
);
setError(
(
err as Error
)
.message
);
}
);
]
}
}}
/>
</span>
);
}
)}
</div>
)}
</td>
);
})}
</tr>
onClick={() => {
if (
button.onClick
) {
isButtonLoading[
i
] = true;
setIsButtonLoading(
isButtonLoading
);
button.onClick(
props.item,
() => {
// on aciton complete
isButtonLoading[
i
] =
false;
setIsButtonLoading(
isButtonLoading
);
},
(
err: Error
) => {
isButtonLoading[
i
] =
false;
setIsButtonLoading(
isButtonLoading
);
setError(
(
err as Error
)
.message
);
}
);
}
}}
/>
</span>
);
}
)}
</div>
)}
</td>
);
})}
</tr>
{tooltipModalText && (
<ConfirmModal
title={`Help`}
description={`${tooltipModalText}`}
submitButtonText={'Close'}
onSubmit={() => {
setTooltipModalText('');
}}
submitButtonType={ButtonStyleType.NORMAL}
/>
)}
</>
);
};

View File

@@ -7,6 +7,7 @@ export default interface Column {
title: string;
description?: string | undefined;
disableSort?: boolean;
tooltipText?: ((item: JSONObject) => string) | undefined;
type: FieldType;
isFilterable?: boolean;
filterDropdownOptions?: Array<DropdownOption> | undefined;

View File

@@ -140,3 +140,6 @@ export const HOME_URL: URL = new URL(HTTP_PROTOCOL, HOME_HOSTNAME, HOME_ROUTE);
export const SubscriptionPlans: Array<SubscriptionPlan> =
SubscriptionPlan.getSubscriptionPlans();
export const StatusPageCNameRecord: string =
env('STATUS_PAGE_CNAME_RECORD') || '';

View File

@@ -0,0 +1,8 @@
<?xml version='1.0' encoding='utf-8'?>
<svg xmlns="http://www.w3.org/2000/svg" width="401px" height="401px" enable-background="new 312.809 0 401 401" version="1.1" viewBox="312.809 0 401 401">
<g transform="matrix(1.223 0 0 1.223 -467.5 -843.44)">
<rect x="601.45" y="653.07" width="401" height="401" fill="#E4E6E7"/>
<path d="m802.38 908.08c-84.515 0-153.52 48.185-157.38 108.62h314.79c-3.87-60.44-72.9-108.62-157.41-108.62z" fill="#AEB4B7"/>
<path d="m881.37 818.86c0 46.746-35.106 84.641-78.41 84.641s-78.41-37.895-78.41-84.641 35.106-84.641 78.41-84.641c43.31 0 78.41 37.9 78.41 84.64z" fill="#AEB4B7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 622 B

View File

@@ -212,6 +212,11 @@
width: 100% !important;
}
.width-half {
width: 50% !important;
}
.text-center {
text-align: center;
justify-content: center;

View File

@@ -16,11 +16,6 @@ app.use(ExpressStatic(path.join(__dirname, 'public')));
app.use(`/${APP_NAME}`, ExpressStatic(path.join(__dirname, 'public')));
app.use(
[`/${APP_NAME}/assets`, `/${APP_NAME}/${APP_NAME}/assets`],
ExpressStatic(path.join(__dirname, 'dist'))
);
app.get('/*', (_req: ExpressRequest, res: ExpressResponse) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

4
Dashboard/nodemon.json Normal file
View File

@@ -0,0 +1,4 @@
{
"watch": ["webpack.config.js"],
"exec": "export DEBUG=express:* && webpack-dev-server --port=3009 --mode=development"
}

View File

@@ -42,6 +42,7 @@
"sass": "^1.51.0",
"sass-loader": "^12.6.0",
"ts-loader": "^9.3.0",
"ts-node": "^10.9.1",
"webpack": "^5.72.1",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0"
@@ -104,8 +105,8 @@
"redis": "^4.2.0",
"socket.io": "^4.4.1",
"stripe": "^10.17.0",
"typeorm": "^0.3.6",
"typeorm-extension": "^2.1.0",
"typeorm": "^0.3.10",
"typeorm-extension": "^2.2.13",
"winston": "^3.6.0"
},
"devDependencies": {
@@ -161,7 +162,7 @@
"react-router-dom": "^6.3.0",
"react-select": "^5.4.0",
"react-simple-code-editor": "^0.13.0",
"react-spinners": "^0.12.0",
"react-spinners": "^0.13.6",
"react-toggle": "^4.1.3",
"reactstrap": "^9.1.1",
"redux": "^4.2.0",
@@ -2383,6 +2384,28 @@
"dev": true,
"peer": true
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@csstools/normalize.css": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz",
@@ -3776,6 +3799,30 @@
"node": ">=10.13.0"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"node_modules/@types/aria-query": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
@@ -6242,6 +6289,12 @@
"node": ">=10"
}
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -6930,6 +6983,15 @@
"dev": true,
"peer": true
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/diff-sequences": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz",
@@ -11450,6 +11512,12 @@
"semver": "bin/semver.js"
}
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node_modules/makeerror": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
@@ -16084,6 +16152,64 @@
"webpack": "^5.0.0"
}
},
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"dev": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/ts-node/node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ts-node/node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"node_modules/tsconfig-paths": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@@ -16428,6 +16554,12 @@
"dev": true,
"peer": true
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"node_modules/v8-to-istanbul": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz",
@@ -17511,6 +17643,15 @@
"node": ">=10"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -19088,6 +19229,27 @@
"dev": true,
"peer": true
},
"@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"requires": {
"@jridgewell/trace-mapping": "0.3.9"
},
"dependencies": {
"@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
}
}
},
"@csstools/normalize.css": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz",
@@ -20061,6 +20223,30 @@
"dev": true,
"peer": true
},
"@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true
},
"@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true
},
"@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true
},
"@tsconfig/node16": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"@types/aria-query": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
@@ -21932,8 +22118,8 @@
"socket.io": "^4.4.1",
"stripe": "^10.17.0",
"ts-jest": "^27.1.4",
"typeorm": "^0.3.6",
"typeorm-extension": "^2.1.0",
"typeorm": "^0.3.10",
"typeorm-extension": "^2.2.13",
"winston": "^3.6.0"
}
},
@@ -21989,7 +22175,7 @@
"react-router-dom": "^6.3.0",
"react-select": "^5.4.0",
"react-simple-code-editor": "^0.13.0",
"react-spinners": "^0.12.0",
"react-spinners": "^0.13.6",
"react-test-renderer": "^18.2.0",
"react-toggle": "^4.1.3",
"reactstrap": "^9.1.1",
@@ -22163,6 +22349,12 @@
"yaml": "^1.10.0"
}
},
"create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -22667,6 +22859,12 @@
"dev": true,
"peer": true
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
"diff-sequences": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz",
@@ -26140,6 +26338,12 @@
}
}
},
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"makeerror": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
@@ -29505,6 +29709,41 @@
"semver": "^7.3.4"
}
},
"ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"dev": true,
"requires": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"dependencies": {
"acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
}
}
},
"tsconfig-paths": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
@@ -29764,6 +30003,12 @@
"dev": true,
"peer": true
},
"v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"v8-to-istanbul": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz",
@@ -30631,6 +30876,12 @@
"dev": true,
"peer": true
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
},
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@@ -22,12 +22,12 @@
"use-async-effect": "^2.2.6"
},
"scripts": {
"dev": "webpack-dev-server --port=3009 --mode=development",
"dev": "nodemon",
"build": "webpack build --mode=production",
"test": "react-app-rewired test",
"eject": "webpack eject",
"compile": "tsc",
"start": "node --require ts-node/register Index.ts",
"start": "node --require ts-node/register Serve.ts",
"audit": "npm audit --audit-level=low",
"preinstall": "npx npm-force-resolutions || echo 'No package-lock.json file. Skipping force resolutions'",
"dep-check": "depcheck ./ --skip-missing=true'"
@@ -59,6 +59,7 @@
"sass": "^1.51.0",
"sass-loader": "^12.6.0",
"ts-loader": "^9.3.0",
"ts-node": "^10.9.1",
"webpack": "^5.72.1",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0"

View File

@@ -65,6 +65,6 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script src="/dashboard/dashboard/assets/bundle.js"></script>
<script src="/dashboard/dist/bundle.js"></script>
</body>
</html>

View File

@@ -28,6 +28,7 @@ import Navigation from 'CommonUI/src/Utils/Navigation';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import PageMap from '../../Utils/PageMap';
import Route from 'Common/Types/API/Route';
import UserProfileModal from './UserProfileModal';
export interface ComponentProps {
projects: Array<Project>;
@@ -57,6 +58,8 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
null
);
const [showProfileModal, setShowProfileModal] = useState<boolean>(false);
useAsyncEffect(async () => {
if (
props.selectedProject &&
@@ -93,6 +96,7 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
onChange={(_value: string) => {}}
/> */}
<div
className="flex"
style={{
marginLeft: '15px',
marginTop: '15px',
@@ -114,6 +118,9 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
onClick={() => {
setShowProjectInvitationModal(true);
}}
style={{
marginRight: '10px',
}}
/>
<CounterModelAlert<Incident>
alertType={AlertType.DANGER}
@@ -131,6 +138,9 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
onClick={() => {
setShowActiveIncidentsModal(true);
}}
style={{
marginRight: '10px',
}}
/>
{props.selectedProject?.trialEndsAt &&
@@ -153,6 +163,9 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
? 'days'
: 'day'
}`}
style={{
marginRight: '10px',
}}
/>
)}
</div>
@@ -203,7 +216,11 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
<></>
)}
<Help />
<UserProfile />
<UserProfile
onClickUserProfle={() => {
setShowProfileModal(true);
}}
/>
</>
}
/>
@@ -228,6 +245,14 @@ const DashboardHeader: FunctionComponent<ComponentProps> = (
/>
)}
{showProfileModal && (
<UserProfileModal
onClose={() => {
setShowProfileModal(false);
}}
/>
)}
{showActiveIncidentsModal && (
<ActiveIncidentsModal
onClose={() => {

View File

@@ -14,12 +14,14 @@ const Help: FunctionComponent = (): ReactElement => {
<IconDropdwonItem
title="Support Email"
icon={IconProp.Email}
url={URL.fromString('https://google.com')}
url={URL.fromString('mailto:support@oneuptime.com')}
/>
<IconDropdwonItem
title="Chat on Slack"
icon={IconProp.Slack}
url={URL.fromString('https://google.com')}
url={URL.fromString(
'https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ'
)}
/>
</IconDropdwonRow>
</IconDropdwonMenu>

View File

@@ -1,5 +1,5 @@
import React, { FunctionComponent, ReactElement } from 'react';
import FullLogo from 'CommonUI/src/Components/Image/Image';
import Image from 'CommonUI/src/Components/Image/Image';
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimePNG/7.png';
import Route from 'Common/Types/API/Route';
@@ -12,14 +12,12 @@ const Logo: FunctionComponent<ComponentProps> = (
): ReactElement => {
return (
<div className="flex items-center" style={{ marginLeft: '-25px' }}>
<FullLogo
<Image
height={30}
onClick={() => {
props.onClick && props.onClick();
}}
imageUrl={Route.fromString(
`/dashboard/public/${OneUptimeLogo}`
)}
imageUrl={Route.fromString(`${OneUptimeLogo}`)}
/>
</div>
);

View File

@@ -4,46 +4,44 @@ import UserProfileMenu from 'CommonUI/src/Components/Header/UserProfile/UserProf
import UserProfileMenuItem from 'CommonUI/src/Components/Header/UserProfile/UserProfileMenuItem';
import UserProfileDropdownDivider from 'CommonUI/src/Components/Header/UserProfile/UserProfileDropdownDivider';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import URL from 'Common/Types/API/URL';
import Route from 'Common/Types/API/Route';
import { Red } from 'Common/Types/BrandColors';
import RouteMap from '../../Utils/RouteMap';
import PageMap from '../../Utils/PageMap';
import UserUtil from 'CommonUI/src/Utils/User';
import OneUptimeLogo from 'CommonUI/src/Images/users/blank-profile.svg';
const DashboardUserProfile: FunctionComponent = (): ReactElement => {
export interface ComponentProps {
onClickUserProfle: () => void;
}
const DashboardUserProfile: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
return (
<UserProfile
userFullName={UserUtil.getName()}
userProfilePicture={URL.fromString(
'https://blog.media.io/images/images2021/cool-good-tiktok-profile-pictures.jpg'
)}
>
<UserProfileMenu>
<UserProfileMenuItem
title="Profile"
route={new Route('/logout')}
icon={IconProp.User}
/>
{/* <UserProfileMenuItem
title="Billing"
route={new Route('/logout')}
icon={IconProp.Billing}
/>
<UserProfileMenuItem
title="User Settings"
route={new Route('/logout')}
icon={IconProp.Settings}
/> */}
<UserProfileDropdownDivider />
<UserProfileMenuItem
title="Log out"
route={RouteMap[PageMap.LOGOUT] as Route}
icon={IconProp.Logout}
iconColor={Red}
/>
</UserProfileMenu>
</UserProfile>
<>
<UserProfile
userFullName={UserUtil.getName()}
userProfilePicture={Route.fromString(`${OneUptimeLogo}`)}
>
<UserProfileMenu>
<UserProfileMenuItem
title="Profile"
onClick={() => {
props.onClickUserProfle();
}}
icon={IconProp.User}
/>
<UserProfileDropdownDivider />
<UserProfileMenuItem
title="Log out"
route={RouteMap[PageMap.LOGOUT] as Route}
icon={IconProp.Logout}
iconColor={Red}
/>
</UserProfileMenu>
</UserProfile>
</>
);
};

View File

@@ -0,0 +1,220 @@
import React, { FunctionComponent, ReactElement, useState } from 'react';
import User from 'Model/Models/User';
import UserUtil from 'CommonUI/src/Utils/User';
import FullPageModal from 'CommonUI/src/Components/FullPageModal/FullPageModal';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import Card from 'CommonUI/src/Components/Card/Card';
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
export interface ComponentProps {
onClose: () => void;
}
const UserProfileModal: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const [hasPasswordChanged, setHasPasswordChanged] =
useState<boolean>(false);
return (
<>
<FullPageModal
onClose={() => {
props.onClose && props.onClose();
}}
>
<CardModelDetail
cardProps={{
title: 'Basic Info',
description: "Here's are some of your details.",
icon: IconProp.User,
}}
isEditable={true}
formFields={[
{
field: {
email: true,
},
fieldType: FormFieldSchemaType.Email,
placeholder: 'jeff@example.com',
required: true,
title: 'Email',
description:
'You will have to verify your email again if you change it',
},
{
field: {
name: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: 'Jeff Smith',
required: true,
title: 'Full Name',
},
{
field: {
companyName: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: 'Acme, Inc.',
required: true,
title: 'Company Name',
},
{
field: {
companyPhoneNumber: true,
},
fieldType: FormFieldSchemaType.Phone,
required: true,
placeholder: '+1-123-456-7890',
title: 'Phone Number',
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 2,
modelType: User,
id: 'user-profile',
fields: [
{
field: {
name: true,
},
title: 'Name',
},
{
field: {
email: true,
},
title: 'Email',
},
{
field: {
companyName: true,
},
title: 'Company Name',
},
{
field: {
companyPhoneNumber: true,
},
title: 'Company Phone Number',
},
],
modelId: UserUtil.getUserId(),
}}
/>
<div className="flex width-max">
<CardModelDetail<User>
cardProps={{
title: 'Profile Picture',
description: 'Please update your profile pic here.',
icon: IconProp.Image,
}}
isEditable={true}
editButtonText={'Update Profile Picture'}
formFields={[
{
field: {
profilePictureFile: true,
},
title: 'Profile Picture',
fieldType: FormFieldSchemaType.ImageFile,
required: false,
placeholder:
'Please upload your profile picture here.',
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 1,
modelType: User,
id: 'model-detail-user-profile-picture',
fields: [
{
field: {
profilePictureFile: {
file: true,
type: true,
},
},
fieldType: FieldType.ImageFile,
title: 'Profile Picture',
placeholder: 'No profile picture uploaded.',
},
],
modelId: UserUtil.getUserId(),
}}
className="width-half"
/>
<Card
style={{ marginLeft: '30px' }}
className="width-half"
title={'Update Password'}
description={
'You can set a new password here if you wish to do so.'
}
icon={IconProp.Lock}
>
{!hasPasswordChanged ? (
<ModelForm<User>
modelType={User}
onSuccess={() => {
setHasPasswordChanged(true);
}}
submitButtonStyleType={ButtonStyleType.OUTLINE}
id="change-password-form"
showAsColumns={1}
doNotFetchExistingModel={true}
modelIdToEdit={UserUtil.getUserId()}
maxPrimaryButtonWidth={true}
initialValues={{
password: '',
confirmPassword: '',
}}
fields={[
{
field: {
password: true,
},
fieldType: FormFieldSchemaType.Password,
validation: {
minLength: 6,
},
placeholder: 'Password',
title: 'Password',
required: true,
},
{
field: {
password: true,
},
validation: {
minLength: 6,
toMatchField: 'password',
},
fieldType: FormFieldSchemaType.Password,
placeholder: 'Confirm Password',
title: 'Confirm Password',
overideFieldKey: 'confirmPassword',
required: true,
},
]}
formType={FormType.Update}
submitButtonText={'Update Password'}
/>
) : (
<p>Your password has been updated.</p>
)}
</Card>
</div>
</FullPageModal>
</>
);
};
export default UserProfileModal;

View File

@@ -4,7 +4,6 @@ import Monitor from 'Model/Models/Monitor';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import MonitorTypeUtil from '../../Utils/MonitorType';
import Label from 'Model/Models/Label';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import LabelsElement from '../../Components/Label/Labels';
@@ -15,6 +14,7 @@ import MonitorStatus from 'Model/Models/MonitorStatus';
import Query from 'CommonUI/src/Utils/ModelAPI/Query';
import Route from 'Common/Types/API/Route';
import Project from 'Model/Models/Project';
import MonitorType from 'Common/Types/Monitor/MonitorType';
export interface ComponentProps {
query?: Query<Monitor> | undefined;
@@ -37,6 +37,10 @@ const MonitorsTable: FunctionComponent<ComponentProps> = (
isCreateable={true}
isViewable={true}
query={props.query}
onBeforeCreate={async (item: Monitor) => {
item.monitorType = MonitorType.Manual;
return item;
}}
cardProps={{
icon: IconProp.Activity,
title: props.title || 'Monitors',
@@ -67,17 +71,17 @@ const MonitorsTable: FunctionComponent<ComponentProps> = (
required: true,
placeholder: 'Description',
},
{
field: {
monitorType: true,
},
title: 'Monitor Type',
fieldType: FormFieldSchemaType.Dropdown,
required: true,
placeholder: 'Select Monitor Type',
dropdownOptions:
MonitorTypeUtil.monitorTypesAsDropdownOptions(),
},
// {
// field: {
// monitorType: true,
// },
// title: 'Monitor Type',
// fieldType: FormFieldSchemaType.Dropdown,
// required: true,
// placeholder: 'Select Monitor Type',
// dropdownOptions:
// MonitorTypeUtil.monitorTypesAsDropdownOptions(),
// },
{
field: {
labels: true,
@@ -107,14 +111,14 @@ const MonitorsTable: FunctionComponent<ComponentProps> = (
type: FieldType.Text,
isFilterable: true,
},
{
field: {
monitorType: true,
},
title: 'Monitor Type',
type: FieldType.Text,
isFilterable: true,
},
// {
// field: {
// monitorType: true,
// },
// title: 'Monitor Type',
// type: FieldType.Text,
// isFilterable: true,
// },
{
field: {
currentMonitorStatus: {

View File

@@ -136,7 +136,7 @@ const ScheduledMaintenancesTable: FunctionComponent<ComponentProps> = (
labelField: 'name',
valueField: '_id',
},
required: true,
required: false,
placeholder: 'Select Status Pages',
},
{

View File

@@ -11,7 +11,7 @@ const StatusPagesElement: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
if (!props.statusPages || props.statusPages.length === 0) {
return <p>No statusPages.</p>;
return <p>No Status Pages.</p>;
}
return (

View File

@@ -143,7 +143,7 @@ const ScheduledMaintenanceView: FunctionComponent<PageComponentProps> = (
labelField: 'name',
valueField: '_id',
},
required: true,
required: false,
placeholder: 'Select Status Pages',
},
{

View File

@@ -189,7 +189,8 @@ const Settings: FunctionComponent<ComponentProps> = (
<></>
)}
{item['status'] !== 'paid' ? (
{item['status'] !== 'paid' &&
item['status'] !== 'draft' ? (
<Button
icon={IconProp.Billing}
onClick={async () => {

View File

@@ -15,6 +15,7 @@ import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import BadDataException from 'Common/Types/Exception/BadDataException';
import StatusPagePreviewLink from './StatusPagePreviewLink';
import { StatusPageCNameRecord } from 'CommonUI/src/Config';
const StatusPageDelete: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
@@ -71,8 +72,7 @@ const StatusPageDelete: FunctionComponent<PageComponentProps> = (
cardProps={{
icon: IconProp.Globe,
title: 'Custom Domains',
description:
'Important: Please add status-page.oneuptime.com as your CNAME for these domains for this to work.',
description: `Important: Please add ${StatusPageCNameRecord} as your CNAME for these domains for this to work.`,
}}
onBeforeCreate={(
item: StatusPageDomain
@@ -127,6 +127,31 @@ const StatusPageDelete: FunctionComponent<PageComponentProps> = (
type: FieldType.Text,
isFilterable: true,
},
{
field: {
isCnameVerified: true,
},
title: 'CNAME Valid',
type: FieldType.Boolean,
isFilterable: true,
tooltipText: (item: StatusPageDomain): string => {
if (item['isCnameVerified']) {
return 'We have verified your CNAME record.';
}
return `Please add a new CNAME record to your domain ${item['fullDomain']}. It should look like CNAME ${item['fullDomain']} ${StatusPageCNameRecord}`;
},
},
{
field: {
isSslProvisioned: true,
},
title: 'SSL Provisioned',
type: FieldType.Boolean,
isFilterable: true,
tooltipText: (_item: StatusPageDomain): string => {
return 'This will happen automatically after CNAME is verified. Please allow 24 hours for SSL to be provisioned after CNAME is verified. If it does not happen in 24 hours, please contact support.';
},
},
]}
/>
</Page>

View File

@@ -1,15 +1,19 @@
const path = require("path");
const webpack = require("webpack");
const dotenv = require('dotenv');
const express = require('express');
const readEnvFile = (pathToFile) => {
const parsed = dotenv.config({ path: pathToFile }).parsed;
const env = {};
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
return env;
}
@@ -18,8 +22,8 @@ module.exports = {
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
publicPath: "/assets/",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/dashboard/dist/",
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'],
@@ -56,7 +60,7 @@ module.exports = {
use: ['style-loader', 'css-loader']
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
test: /\.(jpe?g|png|gif|svg)$/i,
loader: 'file-loader'
}
],
@@ -66,6 +70,11 @@ module.exports = {
devMiddleware: {
writeToDisk: true,
},
allowedHosts: "all",
setupMiddlewares: (middlewares, devServer) => {
devServer.app.use('/dashboard/assets', express.static(path.resolve(__dirname, 'public', 'assets')));
return middlewares;
}
},
devtool: 'eval-source-map',
}

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,7 @@ npm run logs-dev accounts
## Running on: on-prem, staging, or production.
### Running with Docker Compose:
- Run `docker-compose up`
- Run `docker compose up`
### Running with Kubernetes and Helm
- Please check `README.md` in the `HelmChart` folder.

View File

@@ -1,73 +0,0 @@
# oneuptime-gl-manager
Manages SSL Certificate issuance and renewal for [Greenlock](https://git.rootprojects.org/root/greenlock-manager.js) on [OneUptime](https://oneuptime.com) platform.
Saves global and per-site config to a local File Sytem (current).
## Install
```bash
npm install --save oneuptime-gl-manager
```
# Usage
## Initialize the Manager
```js
Greenlock.create({
...
manager: "oneuptime-gl-manager",
configDir: "./greenlock.d",
packageRoot: __dirname
...
});
```
# Site Management
By "site" we mean a primary domain and, optionally, secondary domains, to be listed on an ssl certificate,
along with any configuration that is necessary for getting and renewing those certificates.
## Add a sites - domains and SSL certificates
```js
greenlock.add({
subject: 'example.com',
altnames: ['example.com', 'www.example.com'],
});
```
## View site config
```js
greenlock.get({
servername: 'www.example.com',
wildname: '*.example.com',
});
```
## Update site config
```js
greenlock.update({
subject: 'www.example.com',
challenges: {
'dns-01': {
module: 'acme-dns-01-ovh',
token: 'xxxx',
},
},
});
```
## Remove a site
To stop automatic renewal of SSL certificates for a particular site.
You to restart renewal you must use `add()`.
```js
greenlock.remove({
subject: 'example.com',
});
```

View File

@@ -1,141 +0,0 @@
import axios from 'axios';
const BASE_URL: string = `${process.env['BACKEND_PROTOCOL']}://${process.env['ONEUPTIME_HOST']}`;
const Manager: $TSFixMe = module.exports;
//eslint-disable-next-line @typescript-eslint/no-unused-vars
Manager.create = function (_opts: $TSFixMe): void {
const manager: $TSFixMe = {};
/*
*
* REQUIRED (basic issuance)
*
*/
manager.get = async function ({ servername }: $TSFixMe): void {
const url: string = `${BASE_URL}/api/manager/site?servername=${servername}`;
const response: $TSFixMe = await axios({
url,
method: 'get',
});
return response.data;
};
/*
*
* REQUIRED (basic issuance)
*
*/
manager.set = async function (opts: $TSFixMe): void {
const url: string = `${BASE_URL}/api/manager/site?subject=${opts.subject}`;
const response: $TSFixMe = await axios({
url,
method: 'put',
data: opts,
});
return response.data;
};
/*
*
* Optional (Fully Automatic Renewal)
*
*/
manager.find = async function (opts: $TSFixMe): void {
// { subject, servernames, altnames, renewBefore }
if (opts.subject) {
const url: string = `${BASE_URL}/api/manager/site?subject=${opts.subject}`;
const response: $TSFixMe = await axios({
url,
method: 'get',
});
if (!response.data || response.data.length === 0) {
return [];
}
return [response.data];
}
if (Array.isArray(opts.servernames) && opts.servernames.length > 0) {
const url: string = `${BASE_URL}/api/manager/site/servernames`;
const response: $TSFixMe = await axios({
url,
method: 'post',
data: opts.servernames,
});
return response.data;
}
// I.e. find certs more than 30 days old as default
opts.issuedBefore =
opts.issuedBefore || Date.now() - 30 * 24 * 60 * 60 * 1000;
// I.e. find certs that will expire in less than 45 days as default
opts.expiresBefore =
opts.expiresBefore || Date.now() + 45 * 24 * 60 * 60 * 1000;
// I.e. find certs that should be renewed within 21 days as default
opts.renewBefore =
opts.renewBefore || Date.now() + 21 * 24 * 60 * 60 * 1000;
const url: string = `${BASE_URL}/api/manager/site/opts`;
const response: $TSFixMe = await axios({
url,
method: 'post',
data: opts,
});
return response.data;
};
/*
*
* Optional (Special Remove Functionality)
* The default behavior is to set `deletedAt`
*
*/
manager.remove = async function (opts: $TSFixMe): void {
const url: string = `${BASE_URL}/api/manager/site?subject=${opts.subject}`;
const response: $TSFixMe = await axios({
url,
method: 'delete',
});
return response.data;
};
/*
*
* Optional (special settings save)
* Implemented here because this module IS the fallback
* This is a setter/getter function
*
*/
manager.defaults = async function (opts: $TSFixMe): void {
if (!opts) {
const url: string = `${BASE_URL}/api/manager/default`;
const response: $TSFixMe = await axios({
url,
method: 'get',
});
return response.data ? response.data : {};
}
const url: string = `${BASE_URL}/api/manager/default`;
const response: $TSFixMe = await axios({
url,
method: 'put',
data: opts,
});
return response.data || {};
};
return manager;
};

View File

@@ -1,58 +0,0 @@
{
"name": "oneuptime-gl-manager",
"version": "3.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "oneuptime-gl-manager",
"version": "3.0.0",
"license": "MIT",
"dependencies": {
"axios": "^0.26.1"
}
},
"node_modules/axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"dependencies": {
"follow-redirects": "^1.14.8"
}
},
"node_modules/follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
}
},
"dependencies": {
"axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"requires": {
"follow-redirects": "^1.14.8"
}
},
"follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
}
}
}

View File

@@ -1,29 +0,0 @@
{
"name": "oneuptime-gl-manager",
"version": "3.0.0",
"description": "FileSytem-based Manager with optional encrypted Cloud backup for Greenlock SSL",
"main": "manager.js",
"files": [
"*.js"
],
"scripts": {
"test": "node tests"
},
"keywords": [
"greenlock",
"manager",
"cloud",
"fs",
"ssl",
"oneuptime"
],
"author": "OneUptime Limited. <hello@oneuptime.com>",
"contributors": [
"OneUptime Limited. <hello@oneuptime.com>"
],
"license": "MIT",
"type": "module",
"dependencies": {
"axios": "^0.26.1"
}
}

View File

@@ -1,110 +0,0 @@
{
"ts-node": {
// these options are overrides used only by ts-node
// same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable
"compilerOptions": {
"module": "commonjs"
}
},
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react" /* Specify what JSX code is generated. */,
"experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
// "module": "es2022" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": [
], /* Specify multiple folders that act like `./node_modules/@types`. */
"types": ["node"], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./build/dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
"strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
"strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
"strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
"noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
"useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
"noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
"noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
"exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
"noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
"noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
"noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
"noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View File

@@ -414,6 +414,7 @@ router.post(
email: true,
isMasterAdmin: true,
isEmailVerified: true,
profilePictureId: true,
},
props: {
isRoot: true,

View File

@@ -1 +0,0 @@
/node_modules

View File

@@ -1,19 +0,0 @@
# oneuptime-le-store Package for Greenlock
This module implements a dead-simple, api call to OneUptime backend to store account or certificate details. This allows us to persist our [Let's Encrypt](https://letsencrypt.org/) data in mongo for automated TLS certificate issuance and use.
## Install
npm install oneuptime-le-store
## Usage
// make sure greenlock is already installed
import Greenlock from 'greenlock'
Greenlock.create({
store: {
module: 'oneuptime-le-store',
},
// ...
});

View File

@@ -1,143 +0,0 @@
import axios from 'axios';
const BASE_URL: string = `${process.env['BACKEND_PROTOCOL']}://${process.env['ONEUPTIME_HOST']}`;
/*
* Make api call to designated endpoints
* To make the necessary updates to the db
*/
module.exports.create = function (config: $TSFixMe): void {
const store: $TSFixMe = {};
store.options = config;
store.accounts = {
setKeypair: function (opts: $TSFixMe): void {
const id: $TSFixMe =
(opts.account && opts.account.id) || opts.email || 'default';
const url: string = `${BASE_URL}/api/account/store/${id}`;
const data: $TSFixMe = {
id: id,
privateKeyPem: opts.keypair.privateKeyPem,
privateKeyJwk: opts.keypair.privateKeyJwk,
publickKeyPem: opts.keypair.publickeyPem,
publicKeyJwk: opts.keypair.publicKeyJwk,
key: opts.keypair.key,
};
return axios({
url,
method: 'put',
data,
})
.then((res: $TSFixMe) => {
return res.data;
})
.finally(() => {
return null;
});
},
checkKeypair: function (opts: $TSFixMe): void {
const id: $TSFixMe =
(opts.account && opts.account.id) || opts.email || 'default';
const url: string = `${BASE_URL}/api/account/store/${id}`;
return axios({
url,
method: 'get',
})
.then((res: $TSFixMe) => {
return res.data;
})
.finally(() => {
return null;
});
},
options: config,
};
store.certificates = {
setKeypair: function (opts: $TSFixMe): void {
const id: $TSFixMe =
(opts.certificate &&
(opts.certificate.kid || opts.certificate.id)) ||
opts.subject;
const url: string = `${BASE_URL}/api/certificate/store/${id}`;
const data: $TSFixMe = {
id: id,
deleted: false,
...opts.keypair,
};
return axios({
url,
method: 'put',
data,
})
.then((res: $TSFixMe) => {
return res.data;
})
.finally(() => {
return null;
});
},
checkKeypair: function (opts: $TSFixMe): void {
const id: $TSFixMe =
(opts.certificate &&
(opts.certificate.kid || opts.certificate.id)) ||
opts.subject;
const url: string = `${BASE_URL}/api/certificate/store/${id}`;
return axios({
url,
method: 'get',
})
.then((res: $TSFixMe) => {
return res.data;
})
.finally(() => {
return null;
});
},
set: function (opts: $TSFixMe): void {
const id: $TSFixMe =
(opts.certificate && opts.certificate.id) || opts.subject;
const url: string = `${BASE_URL}/api/certificate/store/${id}`;
const data: $TSFixMe = {
id: id,
deleted: false,
...opts.pems,
};
return axios({
url,
method: 'put',
data,
})
.then((res: $TSFixMe) => {
return res.data;
})
.finally(() => {
return null;
});
},
check: function (opts: $TSFixMe): void {
const id: $TSFixMe =
(opts.certificate && opts.certificate.id) || opts.subject;
const url: string = `${BASE_URL}/api/certificate/store/${id}`;
return axios({
url,
method: 'get',
})
.then((res: $TSFixMe) => {
return res.data;
})
.finally(() => {
return null;
});
},
options: config,
};
return store;
};

View File

@@ -1,58 +0,0 @@
{
"name": "oneuptime-le-store",
"version": "3.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "oneuptime-le-store",
"version": "3.0.0",
"license": "MIT",
"dependencies": {
"axios": "^0.26.1"
}
},
"node_modules/axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"dependencies": {
"follow-redirects": "^1.14.8"
}
},
"node_modules/follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
}
},
"dependencies": {
"axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"requires": {
"follow-redirects": "^1.14.8"
}
},
"follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
}
}
}

View File

@@ -1,23 +0,0 @@
{
"name": "oneuptime-le-store",
"version": "3.0.0",
"description": "Greenlock store module ported for OneUptime",
"main": "index.ts",
"keywords": [
"le-store",
"greenlock",
"letsencrypt",
"mongodb",
"mongo",
"oneuptime"
],
"author": "OneUptime Limited. <hello@oneuptime.com>",
"contributors": [
"OneUptime Limited. <hello@oneuptime.com>"
],
"license": "MIT",
"type": "module",
"dependencies": {
"axios": "^0.26.1"
}
}

View File

@@ -1,110 +0,0 @@
{
"ts-node": {
// these options are overrides used only by ts-node
// same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable
"compilerOptions": {
"module": "commonjs"
}
},
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react" /* Specify what JSX code is generated. */,
"experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
// "module": "es2022" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": [
], /* Specify multiple folders that act like `./node_modules/@types`. */
"types": ["node"], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./build/dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
"strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
"strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
"strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
"noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
"useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
"noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
"noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
"exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
"noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
"noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
"noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
"noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View File

@@ -0,0 +1,595 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=3Dutf-8">
<meta name="viewport" content="width=3Ddevice-width">
<title>You have changed your email.</title>
<style>
/**
* IMPORTANT:
* Please read before changing anything, CSS involved in our HTML emails is
* extremely specific and written a certain way for a reason. It might not make
* sense in a normal setting but Outlook loves it this way.
*
* !!! [override] prevents Yahoo Mail breaking media queries. It must be used
* !!! at the beginning of every line of CSS inside a media query.
* !!! Do not remove.
*
* !!! div[style*="margin: 16px 0"] allows us to target a weird margin
* !!! bug in Android's email client.
* !!! Do not remove.
*
* Also, the img files are hosted on S3. Please don't break these URLs!
* The images are also versioned by date, so please update the URLs accordingly
* if you create new versions
*
***/
/**
* # Root
* - CSS resets and general styles go here.
**/
html,
body,
a,
span,
div[style*="margin: 16px 0"] {
border: 0 !important;
margin: 0 !important;
outline: 0 !important;
padding: 0 !important;
text-decoration: none !important;
}
a,
span,
td,
th {
-webkit-font-smoothing: antialiased !important;
-moz-osx-font-smoothing: grayscale !important;
}
/**
* # Delink
* - Classes for overriding clients which creates links out of things like
* emails, addresses, phone numbers, etc.
**/
span.st-Delink a {
color: #000000 !important;
text-decoration: none !important;
}
/** Modifier: preheader */
span.st-Delink.st-Delink--preheader a {
color: white !important;
text-decoration: none !important;
}
/** */
/** Modifier: title */
span.st-Delink.st-Delink--title a {
color: #000000 !important;
text-decoration: none !important;
}
/** */
/** Modifier: footer */
span.st-Delink.st-Delink--footer a {
color: #8898aa !important;
text-decoration: none !important;
}
/** */
.ii a[href] {
color: #000000;
}
/**
* # Mobile
* - This affects emails views in clients less than 600px wide.
**/
@media all and (max-width: 600px) {
/**
* # Wrapper
**/
body[override] table.st-Wrapper,
body[override] table.st-Width.st-Width--mobile {
min-width: 100% !important;
width: 100% !important;
}
/**
* # Spacer
**/
/** Modifier: gutter */
body[override] td.st-Spacer.st-Spacer--gutter {
width: 32px !important;
}
/** */
/** Modifier: kill */
body[override] td.st-Spacer.st-Spacer--kill {
width: 0 !important;
}
/** */
/** Modifier: emailEnd */
body[override] td.st-Spacer.st-Spacer--emailEnd {
height: 32px !important;
}
/** */
/**
* # Font
**/
/** Modifier: title */
body[override] td.st-Font.st-Font--title,
body[override] td.st-Font.st-Font--title span,
body[override] td.st-Font.st-Font--title a {
font-size: 28px !important;
line-height: 36px !important;
}
/** */
/** Modifier: header */
body[override] td.st-Font.st-Font--header,
body[override] td.st-Font.st-Font--header span,
body[override] td.st-Font.st-Font--header a {
font-size: 24px !important;
line-height: 32px !important;
}
/** */
/** Modifier: body */
body[override] td.st-Font.st-Font--body,
body[override] td.st-Font.st-Font--body span,
body[override] td.st-Font.st-Font--body a {
font-size: 18px !important;
line-height: 28px !important;
}
/** */
/** Modifier: caption */
body[override] td.st-Font.st-Font--caption,
body[override] td.st-Font.st-Font--caption span,
body[override] td.st-Font.st-Font--caption a {
font-size: 14px !important;
line-height: 20px !important;
}
/** */
/** Modifier: simplified */
body[override] table.st-Header.st-Header--simplified td.st-Header-logo {
width: auto !important;
}
body[override] table.st-Header.st-Header--simplified td.st-Header-spacing {
width: 0 !important;
}
/**
* # Divider
**/
body[override] table.st-Divider td.st-Spacer.st-Spacer--gutter,
body[override] tr.st-Divider td.st-Spacer.st-Spacer--gutter {
background-color: #000000;
}
/**
* # Blocks
**/
body[override] table.st-Blocks table.st-Blocks-inner {
border-radius: 0 !important;
}
body[override] table.st-Blocks table.st-Blocks-inner table.st-Blocks-item td.st-Blocks-item-cell {
display: block !important;
}
/**
* # Button
**/
body[override] table.st-Button {
margin: 0 auto !important;
width: 100% !important;
}
body[override] table.st-Button td.st-Button-area,
body[override] table.st-Button td.st-Button-area a.st-Button-link,
body[override] table.st-Button td.st-Button-area span.st-Button-internal {
height: 44px !important;
line-height: 44px !important;
font-size: 18px !important;
vertical-align: middle !important;
}
}
</style>
</head>
<body class="st-Email" bgcolor="f7f7f7"
style="border: 0; margin: 0; padding: 0; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; min-width: 100%; width: 100%;"
override="fix">
<!-- Background -->
<table class="st-Background" bgcolor="f7f7f7" border="0" cellpadding="0" cellspacing="0" width="100%"
style="border: 0; margin: 0; padding: 0;">
<tbody>
<tr>
<td style="border: 0; margin: 0; padding: 0;">
<!-- Wrapper -->
<table class="st-Wrapper" align="center" bgcolor="ffffff" border="0" cellpadding="0" cellspacing="0"
width="600"
style="border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; margin: 0 auto; min-width: 600px;">
<tbody>
<tr>
<td style="border: 0; margin: 0; padding: 0;">
<table class="st-Header st-Header--simplified st-Width st-Width--mobile" border="0" cellpadding="0"
cellspacing="0" width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--divider" colspan="4" height="19"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td align="left" style="height:80px; border: 0; margin: 0; padding: 0;">
<div>
<a style="border: 0; margin: 0; padding: 0; text-decoration: none;" href={{homeURL}}>
<img alt="OneUptime" border="0"
style="height:70px; width:70px; border: 0; margin: 0; padding: 0; color: #000000; display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 12px; font-weight: normal;"
src="https://res.cloudinary.com/deityhub/image/upload/v1637736803/1png.png">
</a>
</div>
</td>
<td class="st-Header-spacing" width="423" style="border: 0; margin: 0; padding: 0;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--divider" colspan="4" height="19"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr class="st-Divider">
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td bgcolor="#fdfdfd" colspan="2" height="1"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body"
style="color: #000000 !important; border:0;margin:0;padding:0; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Ubuntu,sans-serif;font-size:16px;line-height:24px">
<h3>You have changed your email. Please verify your email.</h3>
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body"
style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
Next step would be to verify your email account. Can you please click on 'Verify Email' button which will help us to get your email verified.
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body"
style="border: 0; margin: 0; padding: 0; color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
<!-- Button & Modifier: fullWidth -->
<table class="st-Button st-Button--fullWidth" border="0" cellpadding="0" cellspacing="0"
width="100%">
<tbody>
<tr>
<td align="center" class="st-Button-area" height="38" valign="middle"
style="border: 0; margin: 0; padding: 0; background-color: #000000; border-radius: 5px; text-align: center;">
<a class="st-Button-link"
style="border: 0; margin: 0; padding: 0; color: #ffffff; display: block; height: 38px; text-align: center; text-decoration: none;"
href={{tokenVerifyUrl}}>
<span class="st-Button-internal"
style="border: 0; margin: 0; padding: 0; color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; font-weight: bold; height: 38px; line-height: 38px; mso-line-height-rule: exactly; text-decoration: none; vertical-align: middle; white-space: nowrap; width: 100%;">Verify Email</span>
</a>
</td>
</tr>
</tbody>
</table>
<!-- /Button & Modifier: fullWidth -->
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body link-content"
style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
You can also copy and paste this link: {{tokenVerifyUrl}} on any browser of your choice if the button above does not work.<br/><br/><br/>If you have not signed up for the account, please send us an email at <a
style="color: #000000; text-decoration: none; font-weight: bold"
href="mailto:support@oneuptime.com" target="_blank">support@oneuptime.com</a> and let us
know.
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body"
style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
Thanks, have a great day.
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Copy st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--body"
style="border: 0; margin: 0; padding: 0; color: #000000 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 16px; line-height: 24px;">
OneUptime Team.
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--stacked" colspan="3" height="12"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
<table class="st-Footer st-Width st-Width--mobile" border="0" cellpadding="0" cellspacing="0"
width="600" style="min-width: 600px;">
<tbody>
<tr>
<td class="st-Spacer st-Spacer--divider" colspan="3" height="20"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr class="st-Divider">
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td bgcolor="#fdfdfd" colspan="2" height="1"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; max-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--divider" colspan="3" height="31"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
<td class="st-Font st-Font--caption"
style="border: 0; margin: 0;padding: 0; color: #8898aa; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, sans-serif; font-size: 12px; line-height: 16px;">
<span class="st-Delink st-Delink--footer"
style="border: 0; margin: 0; padding: 0; color: #8898aa; text-decoration: none;">
© {{year}} OneUptime Inc.
</span>
</td>
<td class="st-Spacer st-Spacer--gutter"
style="border: 0; margin:0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;"
width="64">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--emailEnd" colspan="3" height="64"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler"></div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!-- /Wrapper -->
</td>
</tr>
<tr>
<td class="st-Spacer st-Spacer--emailEnd" height="64"
style="border: 0; margin: 0; padding: 0; font-size: 1px; line-height: 1px; mso-line-height-rule: exactly;">
<div class="st-Spacer st-Spacer--filler">&nbsp;</div>
</td>
</tr>
</tbody>
</table>
<!-- /Background -->
</body>
</html>

View File

@@ -193,7 +193,7 @@ export default class Domain extends BaseModel {
public isVerified?: boolean = undefined;
@ColumnAccessControl({
create: [],
create: [Permission.ProjectOwner, Permission.CanCreateProjectDomain],
read: [Permission.ProjectOwner, Permission.CanReadProjectDomain],
update: [],
})

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