mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
297 Commits
ext-postgr
...
eval-over-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f09419ef50 | ||
|
|
4c90dc66a0 | ||
|
|
99c2e34ab5 | ||
|
|
0fb0e9b047 | ||
|
|
7ae6a3c5ea | ||
|
|
59f0526936 | ||
|
|
bfcd5c1753 | ||
|
|
76df1a5889 | ||
|
|
422ee6c192 | ||
|
|
808b3512f3 | ||
|
|
742796fd67 | ||
|
|
9027369e10 | ||
|
|
a87f3ac896 | ||
|
|
db75cc1a5b | ||
|
|
2deeb32b78 | ||
|
|
bc8b8eb982 | ||
|
|
ef3fe66e72 | ||
|
|
17a0b65a4b | ||
|
|
01a1a0f69e | ||
|
|
1260e482cf | ||
|
|
6e5081a4e3 | ||
|
|
dd93998296 | ||
|
|
94956b045a | ||
|
|
b2f650a865 | ||
|
|
4390a37184 | ||
|
|
9b08d1a9e4 | ||
|
|
dbef1071e0 | ||
|
|
98fbbe301e | ||
|
|
6805083725 | ||
|
|
27213fa235 | ||
|
|
baa98e333e | ||
|
|
ccdcf2c679 | ||
|
|
85edd12c2d | ||
|
|
97cc28b182 | ||
|
|
b0041e6993 | ||
|
|
a66d78743d | ||
|
|
bc78491478 | ||
|
|
7751870ccf | ||
|
|
df6ffb15d4 | ||
|
|
df20f343e9 | ||
|
|
e4ade513ce | ||
|
|
3c2af1dc38 | ||
|
|
af5d714642 | ||
|
|
3f315be279 | ||
|
|
095493cec9 | ||
|
|
3bdf87f34f | ||
|
|
26bb6f1e74 | ||
|
|
20db81a5f6 | ||
|
|
f9b097a112 | ||
|
|
b70b70e27e | ||
|
|
93df459662 | ||
|
|
1aceb81c85 | ||
|
|
023aea94c2 | ||
|
|
d8dff468ab | ||
|
|
082d800c34 | ||
|
|
e7f4c962b8 | ||
|
|
f6074fe8f4 | ||
|
|
b45678f167 | ||
|
|
6d53678135 | ||
|
|
7b77dd4a53 | ||
|
|
51bb54e771 | ||
|
|
48b095f548 | ||
|
|
786ec6ce7a | ||
|
|
401fbb58e3 | ||
|
|
5243ae6b8d | ||
|
|
a06a0f1d16 | ||
|
|
e0bc906484 | ||
|
|
139ff2d638 | ||
|
|
7be603ef08 | ||
|
|
d23548f984 | ||
|
|
3573e59634 | ||
|
|
c4e4e7e488 | ||
|
|
be3e8447ab | ||
|
|
2615e39c19 | ||
|
|
49b6550581 | ||
|
|
e870efc3c4 | ||
|
|
38862adf5a | ||
|
|
06b92e2745 | ||
|
|
1a8e0682c7 | ||
|
|
e0189356d5 | ||
|
|
dad07b9f80 | ||
|
|
8d59710306 | ||
|
|
5e9227eb4f | ||
|
|
1e63173564 | ||
|
|
76fded39ee | ||
|
|
c82f3c1f71 | ||
|
|
24d9219a62 | ||
|
|
6ed588d7aa | ||
|
|
17c1862eac | ||
|
|
37b1846363 | ||
|
|
da3e6bc3af | ||
|
|
a35ea2ba66 | ||
|
|
b2fa2e40f4 | ||
|
|
babecb5170 | ||
|
|
6e0e643337 | ||
|
|
2a59faa6b4 | ||
|
|
9d7d44afda | ||
|
|
8f1e67da3a | ||
|
|
c09f863d58 | ||
|
|
3c8c2a3feb | ||
|
|
4c4169e245 | ||
|
|
34c5cb1e94 | ||
|
|
f5fca46e5b | ||
|
|
48e3c24c6e | ||
|
|
e9c94876c0 | ||
|
|
fcd6c8ea7d | ||
|
|
81726ce7b2 | ||
|
|
6349c60bdb | ||
|
|
74cf119444 | ||
|
|
ba6ac2e32e | ||
|
|
1498656a43 | ||
|
|
70a2a3993b | ||
|
|
5152d5de12 | ||
|
|
2999e539b3 | ||
|
|
8ce432256c | ||
|
|
ddbfbac802 | ||
|
|
1d6a7ee1fa | ||
|
|
0d97f0447a | ||
|
|
36a13b60fe | ||
|
|
64f4fcf829 | ||
|
|
1ff7c84d82 | ||
|
|
da623d4d34 | ||
|
|
2efb630640 | ||
|
|
affa492ce3 | ||
|
|
5ae3a5b5ee | ||
|
|
5716ab2445 | ||
|
|
a66a04456b | ||
|
|
e97a78eaeb | ||
|
|
57f41a908a | ||
|
|
f42291a428 | ||
|
|
4fa9adfc7d | ||
|
|
3e2e581e52 | ||
|
|
2a3c34af95 | ||
|
|
170b79e4cf | ||
|
|
c15b8bb951 | ||
|
|
17c47f7d89 | ||
|
|
a406287215 | ||
|
|
a73b050a4d | ||
|
|
224fb2e887 | ||
|
|
96d4131614 | ||
|
|
cda00d5238 | ||
|
|
dea385ad44 | ||
|
|
21973401fb | ||
|
|
d153ad9bf7 | ||
|
|
2ce8ba6295 | ||
|
|
572130d349 | ||
|
|
f0cb049266 | ||
|
|
cab5af6645 | ||
|
|
e77b923dc1 | ||
|
|
8325c06ca3 | ||
|
|
b66f56bc12 | ||
|
|
2de2926d79 | ||
|
|
fd13e91aac | ||
|
|
cf7d4c7720 | ||
|
|
b1a76e97ce | ||
|
|
7d8036e3eb | ||
|
|
c7344a7624 | ||
|
|
42253e4e50 | ||
|
|
64acf372d6 | ||
|
|
80f4238618 | ||
|
|
2fe54c46c1 | ||
|
|
266b046b9e | ||
|
|
f311841df2 | ||
|
|
26319ff3c8 | ||
|
|
e719bb3b70 | ||
|
|
d0255c1e33 | ||
|
|
f66241d12f | ||
|
|
52f4c74908 | ||
|
|
eeb6904c2d | ||
|
|
6fd37c62b8 | ||
|
|
16b761e498 | ||
|
|
9b1e702c64 | ||
|
|
f10fa9a48e | ||
|
|
de91346155 | ||
|
|
629a442ff7 | ||
|
|
be54d782e5 | ||
|
|
029c1b0704 | ||
|
|
36cfb7e20f | ||
|
|
02046a525e | ||
|
|
c9a998da0b | ||
|
|
7fc192e466 | ||
|
|
eae8b79b2e | ||
|
|
08c6cf31a0 | ||
|
|
dfb7f2320c | ||
|
|
c53b14f88f | ||
|
|
c5680f6c26 | ||
|
|
d2a1385123 | ||
|
|
feda1b0426 | ||
|
|
b97cc46a1e | ||
|
|
41a8101b54 | ||
|
|
676a2b18b3 | ||
|
|
df7477929b | ||
|
|
c1ebe14c50 | ||
|
|
2b478e7a13 | ||
|
|
3bb1d93f3e | ||
|
|
703c4b7685 | ||
|
|
d7e9776a3c | ||
|
|
3cb29b63fe | ||
|
|
e7f4a36ec9 | ||
|
|
be78862e49 | ||
|
|
64ae5eeb89 | ||
|
|
78bdc42534 | ||
|
|
68c81734e8 | ||
|
|
bf081d1ebe | ||
|
|
3de407842e | ||
|
|
a4a9e45fe0 | ||
|
|
f9c9480434 | ||
|
|
eb644ad2f2 | ||
|
|
5186e193a8 | ||
|
|
55d947fb39 | ||
|
|
77f1262ff5 | ||
|
|
47bf8f9c89 | ||
|
|
4ca4f28d1c | ||
|
|
b6565ce2bb | ||
|
|
0704e1f556 | ||
|
|
87c16d7bc3 | ||
|
|
cc66820e7b | ||
|
|
a478f60a39 | ||
|
|
5637f12d3a | ||
|
|
27c28b17af | ||
|
|
c55b169488 | ||
|
|
9b584d69ff | ||
|
|
05c090445a | ||
|
|
597aeb74f4 | ||
|
|
b7191a9c2e | ||
|
|
c686030014 | ||
|
|
eed1078f06 | ||
|
|
051a3c43b2 | ||
|
|
a152813535 | ||
|
|
decea5acfc | ||
|
|
4c2dfb0f92 | ||
|
|
a24bf077ce | ||
|
|
2d82f50789 | ||
|
|
3e13776252 | ||
|
|
463bb32872 | ||
|
|
99dcee80cd | ||
|
|
c418dc41dd | ||
|
|
c0678c410d | ||
|
|
081359f7e0 | ||
|
|
cc0cfe4f8c | ||
|
|
dc87905f05 | ||
|
|
9c31047d52 | ||
|
|
91d196ddea | ||
|
|
78db5cab39 | ||
|
|
689e72e5ec | ||
|
|
bc9e97f67c | ||
|
|
19550c23ed | ||
|
|
2d09df2d87 | ||
|
|
2dfebdd2e4 | ||
|
|
ebec143c9c | ||
|
|
aa68a6316a | ||
|
|
27a37581e4 | ||
|
|
091622f8cf | ||
|
|
87caae077c | ||
|
|
a146691773 | ||
|
|
9fce103b11 | ||
|
|
2aa0ae89fc | ||
|
|
0a16f6bf44 | ||
|
|
0f49e6e100 | ||
|
|
d954b4a5df | ||
|
|
e762778fc6 | ||
|
|
4cced50857 | ||
|
|
26c900d8e2 | ||
|
|
63461343ba | ||
|
|
931abc522b | ||
|
|
054592eed3 | ||
|
|
82b2307b51 | ||
|
|
b1dba73842 | ||
|
|
babbf5f8a6 | ||
|
|
39c7da79ab | ||
|
|
938bd32915 | ||
|
|
f8e1ace311 | ||
|
|
05e2e22e1d | ||
|
|
9054c49b3e | ||
|
|
5d5468603f | ||
|
|
4c6979cfa1 | ||
|
|
714a4be2b0 | ||
|
|
b935443f96 | ||
|
|
d3a3f01f20 | ||
|
|
56b0fea04a | ||
|
|
2605140166 | ||
|
|
8b9611e145 | ||
|
|
21057038d1 | ||
|
|
e587d4ba19 | ||
|
|
14da201c8d | ||
|
|
f5584a5037 | ||
|
|
157f8e95d7 | ||
|
|
fb83126f37 | ||
|
|
964def0c45 | ||
|
|
650d7cc939 | ||
|
|
bfb4c46bd0 | ||
|
|
149c8c763d | ||
|
|
fdbcace48c | ||
|
|
3cfe0517a8 | ||
|
|
9a32a47146 | ||
|
|
fd83a71a56 | ||
|
|
070190dd31 |
@@ -1,28 +0,0 @@
|
||||
*/node_modules/*
|
||||
*/build/*
|
||||
*/coverage/*
|
||||
|
||||
*/dist/*
|
||||
|
||||
*/public/*
|
||||
*/views/*
|
||||
|
||||
*fonts*
|
||||
*logos*
|
||||
|
||||
.*
|
||||
*.png
|
||||
*.sh
|
||||
*.txt
|
||||
*.snap
|
||||
*.enc
|
||||
Dockerfile
|
||||
CHANGELOG
|
||||
LICENSE
|
||||
|
||||
marketing/*/*
|
||||
licenses/*
|
||||
certifications/*
|
||||
ApiReference/public/assets/*
|
||||
JavaScriptSDK/src/cli/server-monitor/out/scripts/prettify/*
|
||||
_test/*
|
||||
216
.eslintrc.json
216
.eslintrc.json
@@ -1,216 +0,0 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 8,
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true,
|
||||
"tsx": true,
|
||||
"spread": true
|
||||
},
|
||||
"sourceType": "module",
|
||||
"project": [
|
||||
"./tsconfig.json"
|
||||
]
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"jquery": true,
|
||||
"es6": true,
|
||||
"jest": true,
|
||||
"jasmine": true
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"jsx-a11y",
|
||||
"progress",
|
||||
"@typescript-eslint",
|
||||
"unused-imports"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"context": true,
|
||||
"before": true,
|
||||
"beforeEach": true,
|
||||
"after": true,
|
||||
"afterEach": true,
|
||||
"it": true,
|
||||
"expect": true,
|
||||
"workbox": true,
|
||||
"importScripts": true,
|
||||
"$TSFixMe": true,
|
||||
"NodeJS": true,
|
||||
"JSX": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"rules": {
|
||||
"no-fallthrough": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-cond-assign": "error",
|
||||
"valid-typeof": "error",
|
||||
"no-func-assign": "error",
|
||||
"curly": "error",
|
||||
"no-extra-semi": "error",
|
||||
"no-else-return": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-octal": "error",
|
||||
"no-extra-bind": "error",
|
||||
"unicode-bom": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"wrap-regex": "error",
|
||||
"wrap-iife": "error",
|
||||
"yield-star-spacing": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-extra-label": "error",
|
||||
"multiline-comment-style": "off",
|
||||
"no-lonely-if": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"eqeqeq": "error",
|
||||
"dot-notation": "off", // Off because it messes up with typescript compiler.
|
||||
"@typescript-eslint/dot-notation": "off", //temp off.
|
||||
"progress/activate": 1,
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"@typescript-eslint/no-empty-interface": [
|
||||
"error",
|
||||
{
|
||||
"allowSingleExtends": true
|
||||
}
|
||||
],
|
||||
// https://www.npmjs.com/package/eslint-plugin-unused-imports
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-extra-non-null-assertion": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^_",
|
||||
"args": "after-used",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"error"
|
||||
],
|
||||
"no-console": "error",
|
||||
"no-undef": "error",
|
||||
"no-empty": "error",
|
||||
"no-control-regex": "off",
|
||||
"prefer-arrow-callback": "error",
|
||||
"constructor-super": "error",
|
||||
"no-case-declarations": "error",
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"no-useless-escape": "error",
|
||||
"prettier/prettier": "error",
|
||||
"react/jsx-no-undef": "error",
|
||||
"react/jsx-no-bind": [
|
||||
"error",
|
||||
{
|
||||
"allowArrowFunctions": true,
|
||||
"allowBind": false,
|
||||
"ignoreRefs": false
|
||||
}
|
||||
],
|
||||
"react/no-children-prop": "error",
|
||||
"react/no-deprecated": "error",
|
||||
"react/boolean-prop-naming": "error",
|
||||
"react/no-is-mounted": "error",
|
||||
"react/no-find-dom-node": "error",
|
||||
"one-var-declaration-per-line": "error",
|
||||
"arrow-parens": "error",
|
||||
"arrow-body-style": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"@typescript-eslint/typedef": [
|
||||
"error",
|
||||
{
|
||||
"arrowParameter": true,
|
||||
"variableDeclaration": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/strict-boolean-expressions": "off", //Need to enable this very soon
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"error",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"react/no-did-update-set-state": "error",
|
||||
"react/no-unknown-property": "error",
|
||||
"react/no-unused-prop-types": "error",
|
||||
"react/jsx-no-duplicate-props": "error",
|
||||
"react/no-unused-state": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"react/prop-types": "error",
|
||||
"react/react-in-jsx-scope": "error",
|
||||
"react/no-string-refs": "error",
|
||||
"jsx-a11y/href-no-hash": [
|
||||
0
|
||||
],
|
||||
"react/no-unescaped-entities": "error",
|
||||
"react/display-name": "error",
|
||||
"react/jsx-pascal-case": "error",
|
||||
"array-callback-return": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-promise-executor-return": "error",
|
||||
"capitalized-comments": "off", // this is turned off because come commented code should not be capitalized.
|
||||
"for-direction": "error",
|
||||
"getter-return": "error",
|
||||
"jsx-a11y/anchor-is-valid": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"prefer-const": [
|
||||
"error",
|
||||
{
|
||||
"destructuring": "any",
|
||||
"ignoreReadBeforeAssign": false
|
||||
}
|
||||
],
|
||||
"no-var": "error",
|
||||
"object-curly-spacing": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-unneeded-ternary": "error",
|
||||
"@typescript-eslint/ban-types": [
|
||||
"error",
|
||||
{
|
||||
"types": {
|
||||
"String": true,
|
||||
"Boolean": true,
|
||||
"Number": true,
|
||||
"Symbol": false,
|
||||
"{}": true,
|
||||
"Object": true,
|
||||
"object": true,
|
||||
"Function": true
|
||||
},
|
||||
"extendDefaults": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "18.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -72,6 +72,22 @@ jobs:
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./App/Dockerfile .
|
||||
|
||||
|
||||
docker-build-copilot:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Preinstall
|
||||
run: npm run prerun
|
||||
|
||||
# build image for accounts service
|
||||
- name: build docker image
|
||||
run: sudo docker build -f ./Copilot/Dockerfile .
|
||||
|
||||
docker-build-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
language: [ 'javascript', 'typescript', 'go' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
|
||||
14
.github/workflows/compile.yml
vendored
14
.github/workflows/compile.yml
vendored
@@ -91,6 +91,20 @@ jobs:
|
||||
- run: cd CommonUI && npm install --force
|
||||
- run: cd App && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-copilot:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: cd Common && npm install
|
||||
- run: cd Model && npm install
|
||||
- run: cd CommonServer && npm install
|
||||
- run: cd Copilot && npm install && npm run compile && npm run dep-check
|
||||
|
||||
compile-nginx:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
|
||||
254
.github/workflows/release.yml
vendored
254
.github/workflows/release.yml
vendored
@@ -18,35 +18,12 @@ jobs:
|
||||
token: ${{secrets.github_token}}
|
||||
- run: echo "Build number is ${{ steps.buildnumber.outputs.build_number }}"
|
||||
|
||||
github-release:
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/release'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
- run: echo "${{needs.generate-build-number.outputs.build_number}}"
|
||||
- name: "Build Changelog"
|
||||
id: build_changelog
|
||||
uses: mikepenz/release-changelog-builder-action@v4.2.0
|
||||
with:
|
||||
configuration: "./Scripts/Release/ChangelogConfig.json"
|
||||
- run: echo "Changelog:"
|
||||
- run: echo "${{steps.build_changelog.outputs.changelog}}"
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: "7.0.${{needs.generate-build-number.outputs.build_number}}"
|
||||
artifactErrorsFailBuild: true
|
||||
body: |
|
||||
${{steps.build_changelog.outputs.changelog}}
|
||||
|
||||
|
||||
|
||||
helm-chart-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
env:
|
||||
CI_COMMIT_AUTHOR: Continuous Integration
|
||||
steps:
|
||||
@@ -92,7 +69,7 @@ jobs:
|
||||
git push origin master
|
||||
|
||||
nginx-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -152,7 +129,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
e2e-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -212,7 +189,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
isolated-vm-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -272,7 +249,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
test-server-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -332,7 +309,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
otel-collector-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -394,7 +371,7 @@ jobs:
|
||||
|
||||
|
||||
status-page-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -454,7 +431,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
test-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -514,7 +491,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
ingestor-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -574,7 +551,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
probe-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -635,7 +612,7 @@ jobs:
|
||||
|
||||
|
||||
haraka-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -695,7 +672,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
admin-dashboard-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -756,7 +733,7 @@ jobs:
|
||||
|
||||
|
||||
dashboard-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -816,7 +793,7 @@ jobs:
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
app-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -875,8 +852,69 @@ jobs:
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
copilot-docker-image-deploy:
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
oneuptime/copilot
|
||||
ghcr.io/oneuptime/copilot
|
||||
tags: |
|
||||
type=raw,value=release,enable=true
|
||||
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}},pattern={{version}},enable=true
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Generate Dockerfile from Dockerfile.tpl
|
||||
run: npm run prerun
|
||||
|
||||
# Build and deploy app.
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
file: ./Copilot/Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
accounts-docker-image-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
@@ -938,7 +976,7 @@ jobs:
|
||||
|
||||
publish-npm-packages:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [generate-build-number]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
NPM_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
|
||||
@@ -951,8 +989,140 @@ jobs:
|
||||
- name: Publish Infrastructure Agent
|
||||
run: bash ./Scripts/NPM/PublishAllPackages.sh
|
||||
|
||||
|
||||
|
||||
test-e2e-release-saas:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
test-e2e-release-self-hosted:
|
||||
runs-on: ubuntu-latest
|
||||
# After all the jobs runs
|
||||
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
github-release:
|
||||
needs: [test-e2e-release-saas, test-e2e-release-self-hosted, generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/release'
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
- run: echo "${{needs.generate-build-number.outputs.build_number}}"
|
||||
- name: "Build Changelog"
|
||||
id: build_changelog
|
||||
uses: mikepenz/release-changelog-builder-action@v4.2.0
|
||||
with:
|
||||
configuration: "./Scripts/Release/ChangelogConfig.json"
|
||||
- run: echo "Changelog:"
|
||||
- run: echo "${{steps.build_changelog.outputs.changelog}}"
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag: "7.0.${{needs.generate-build-number.outputs.build_number}}"
|
||||
artifactErrorsFailBuild: true
|
||||
body: |
|
||||
${{steps.build_changelog.outputs.changelog}}
|
||||
|
||||
|
||||
infrastructure-agent-deploy:
|
||||
needs: [generate-build-number, github-release]
|
||||
needs: [github-release, generate-build-number]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -983,3 +1153,5 @@ jobs:
|
||||
draft: false
|
||||
prerelease: false
|
||||
tag_name: 7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
|
||||
172
.github/workflows/test-release.yaml
vendored
172
.github/workflows/test-release.yaml
vendored
@@ -876,10 +876,72 @@ jobs:
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
copilot-docker-image-deploy:
|
||||
needs: generate-build-number
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Docker Meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
oneuptime/copilot
|
||||
ghcr.io/oneuptime/copilot
|
||||
tags: |
|
||||
type=raw,value=test,enable=true
|
||||
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}}-test,pattern={{version}},enable=true
|
||||
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Generate Dockerfile from Dockerfile.tpl
|
||||
run: npm run prerun
|
||||
|
||||
# Build and deploy accounts.
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
file: ./Copilot/Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
GIT_SHA=${{ github.sha }}
|
||||
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
|
||||
|
||||
|
||||
test-helm-chart:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy]
|
||||
needs: [copilot-docker-image-deploy, isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
@@ -888,3 +950,111 @@ jobs:
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: cd HelmChart && cd Tests && bash index.sh
|
||||
|
||||
test-e2e-test-saas:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-helm-chart]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/change-release-to-test-tag.sh
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
test-e2e-test-self-hosted:
|
||||
runs-on: ubuntu-latest
|
||||
# After all the jobs runs
|
||||
needs: [test-helm-chart]
|
||||
env:
|
||||
CI_PIPELINE_ID: ${{github.run_number}}
|
||||
steps:
|
||||
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/change-release-to-test-tag.sh
|
||||
- name: Start Server with release tag
|
||||
run: npm run start
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
if: failure()
|
||||
with:
|
||||
# Name of the artifact to upload.
|
||||
# Optional. Default is 'artifact'
|
||||
name: test-results
|
||||
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
|
||||
|
||||
14
.github/workflows/test.e2e.yaml
vendored
14
.github/workflows/test.e2e.yaml
vendored
@@ -32,10 +32,12 @@ jobs:
|
||||
node-version: 18.3.0
|
||||
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
|
||||
- run: npm run dev
|
||||
- name: Sleep for 2 minutes to wait for server to start
|
||||
run: sleep 120
|
||||
- name: Wait for server to start
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
run: bash ./Tests/Scripts/status-check.sh http://localhost
|
||||
- name: Run E2E Tests. Run docker container e2e in docker compose file
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
|
||||
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs && exit 1)
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
# Run this on failure
|
||||
@@ -48,15 +50,11 @@ jobs:
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
# Required.
|
||||
path: |
|
||||
./E2E/playwright-report
|
||||
./E2E/test-results
|
||||
./E2E
|
||||
|
||||
|
||||
# Duration after which artifact will expire in days. 0 means using default retention.
|
||||
# Minimum 1 day.
|
||||
# Maximum 90 days unless changed from the repository settings page.
|
||||
# Optional. Defaults to repository settings.
|
||||
retention-days: 7
|
||||
|
||||
|
||||
|
||||
retention-days: 7
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -98,6 +98,8 @@ Llama/Models/llama*
|
||||
|
||||
Llama/__pycache__/*
|
||||
|
||||
Llama/Models/*
|
||||
|
||||
Examples/otel-dotnet/obj/*
|
||||
|
||||
InfrastructureAgent/sea-prep.blob
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"plugins": ["@trivago/prettier-plugin-sort-imports"],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true,
|
||||
"importOrderParserPlugins": ["typescript", "decorators", "dynamicImport", "jsx"]
|
||||
}
|
||||
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@@ -19,6 +19,20 @@
|
||||
}
|
||||
],
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug: Copilot Locally",
|
||||
"request": "launch",
|
||||
"localRoot": "${workspaceFolder}/Copilot",
|
||||
"runtimeArgs": [
|
||||
"run-script",
|
||||
"start"
|
||||
],
|
||||
"runtimeExecutable": "npm",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node"
|
||||
},
|
||||
{
|
||||
"name": "Debug Infrastructure Agent",
|
||||
"type": "go",
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import App from 'CommonServer/Utils/StartServer';
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import App from "CommonServer/Utils/StartServer";
|
||||
|
||||
export const APP_NAME: string = 'accounts';
|
||||
export const APP_NAME: string = "accounts";
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error('App Init Failed:');
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error("App Init Failed:");
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
init().catch((err: Error) => {
|
||||
logger.error(err);
|
||||
logger.error('Exiting node process');
|
||||
process.exit(1);
|
||||
logger.error(err);
|
||||
logger.error("Exiting node process");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
8
Accounts/index.d.ts
vendored
8
Accounts/index.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
declare module '*.png';
|
||||
declare module '*.svg';
|
||||
declare module '*.jpg';
|
||||
declare module '*.gif';
|
||||
declare module "*.png";
|
||||
declare module "*.svg";
|
||||
declare module "*.jpg";
|
||||
declare module "*.gif";
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
import ForgotPasswordPage from './Pages/ForgotPassword';
|
||||
import LoginPage from './Pages/Login';
|
||||
import NotFound from './Pages/NotFound';
|
||||
import RegisterPage from './Pages/Register';
|
||||
import ResetPasswordPage from './Pages/ResetPassword';
|
||||
import VerifyEmail from './Pages/VerifyEmail';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import React, { ReactElement } from 'react';
|
||||
import ForgotPasswordPage from "./Pages/ForgotPassword";
|
||||
import LoginPage from "./Pages/Login";
|
||||
import LoginWithSSO from "./Pages/LoginWithSSO";
|
||||
import NotFound from "./Pages/NotFound";
|
||||
import RegisterPage from "./Pages/Register";
|
||||
import ResetPasswordPage from "./Pages/ResetPassword";
|
||||
import VerifyEmail from "./Pages/VerifyEmail";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import React, { ReactElement } from "react";
|
||||
import {
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
|
||||
function App(): ReactElement {
|
||||
Navigation.setNavigateHook(useNavigate());
|
||||
Navigation.setLocation(useLocation());
|
||||
Navigation.setParams(useParams());
|
||||
Navigation.setNavigateHook(useNavigate());
|
||||
Navigation.setLocation(useLocation());
|
||||
Navigation.setParams(useParams());
|
||||
|
||||
return (
|
||||
<div className="m-auto h-screen">
|
||||
<Routes>
|
||||
<Route path="/accounts" element={<LoginPage />} />
|
||||
<Route path="/accounts/login" element={<LoginPage />} />
|
||||
<Route
|
||||
path="/accounts/forgot-password"
|
||||
element={<ForgotPasswordPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/accounts/reset-password/:token"
|
||||
element={<ResetPasswordPage />}
|
||||
/>
|
||||
<Route path="/accounts/register" element={<RegisterPage />} />
|
||||
<Route
|
||||
path="/accounts/verify-email/:token"
|
||||
element={<VerifyEmail />}
|
||||
/>
|
||||
{/* 👇️ only match this when no other routes match */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="m-auto h-screen">
|
||||
<Routes>
|
||||
<Route path="/accounts" element={<LoginPage />} />
|
||||
<Route path="/accounts/login" element={<LoginPage />} />
|
||||
|
||||
<Route path="/accounts/sso" element={<LoginWithSSO />} />
|
||||
<Route
|
||||
path="/accounts/forgot-password"
|
||||
element={<ForgotPasswordPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/accounts/reset-password/:token"
|
||||
element={<ResetPasswordPage />}
|
||||
/>
|
||||
<Route path="/accounts/register" element={<RegisterPage />} />
|
||||
<Route path="/accounts/verify-email/:token" element={<VerifyEmail />} />
|
||||
{/* 👇️ only match this when no other routes match */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const Footer: () => JSX.Element = () => {
|
||||
return (
|
||||
<div className="footer">
|
||||
<p>
|
||||
<Link to="/">© OneUptime</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Link to="/">Contact</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Link to="/">Privacy & terms</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="footer">
|
||||
<p>
|
||||
<Link to="/">© OneUptime</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Link to="/">Contact</Link>
|
||||
</p>
|
||||
<p>
|
||||
<Link to="/">Privacy & terms</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import App from './App';
|
||||
import Telemetry from 'CommonUI/src/Utils/Telemetry';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import App from "./App";
|
||||
import Telemetry from "CommonUI/src/Utils/Telemetry";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
Telemetry.init({
|
||||
serviceName: 'Accounts',
|
||||
serviceName: "Accounts",
|
||||
});
|
||||
|
||||
const root: any = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
document.getElementById("root") as HTMLElement,
|
||||
);
|
||||
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>,
|
||||
);
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
import { FORGOT_PASSWORD_API_URL } from '../Utils/ApiPaths';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import User from 'Model/Models/User';
|
||||
import React, { useState } from 'react';
|
||||
import { FORGOT_PASSWORD_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import User from "Model/Models/User";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const ForgotPassword: () => JSX.Element = () => {
|
||||
const apiUrl: URL = FORGOT_PASSWORD_API_URL;
|
||||
const apiUrl: URL = FORGOT_PASSWORD_API_URL;
|
||||
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Forgot your password
|
||||
</h2>
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Forgot your password
|
||||
</h2>
|
||||
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your email and the password reset link will
|
||||
be sent to you.
|
||||
</p>
|
||||
)}
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your email and the password reset link will be sent to
|
||||
you.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
We have emailed you the password reset link. Please do
|
||||
not forget to check spam.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
We have emailed you the password reset link. Please do not forget to
|
||||
check spam.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
name="Forgot Password"
|
||||
id="login-form"
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
onSuccess={() => {
|
||||
setIsSuccess(true);
|
||||
}}
|
||||
submitButtonText={'Send Password Reset Link'}
|
||||
formType={FormType.Create}
|
||||
maxPrimaryButtonWidth={true}
|
||||
footer={
|
||||
<div className="actions pointer text-center mt-4 hover:underline fw-semibold">
|
||||
<p>
|
||||
<Link
|
||||
to={new Route('/accounts/login')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
|
||||
>
|
||||
Return to Sign in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Remember your password?{' '}
|
||||
<Link
|
||||
to={new Route('/accounts/login')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
name="Forgot Password"
|
||||
id="login-form"
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: "Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
onSuccess={() => {
|
||||
setIsSuccess(true);
|
||||
}}
|
||||
submitButtonText={"Send Password Reset Link"}
|
||||
formType={FormType.Create}
|
||||
maxPrimaryButtonWidth={true}
|
||||
footer={
|
||||
<div className="actions pointer text-center mt-4 hover:underline fw-semibold">
|
||||
<p>
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
|
||||
>
|
||||
Return to Sign in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Remember your password?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPassword;
|
||||
|
||||
@@ -1,167 +1,132 @@
|
||||
import { LOGIN_API_URL } from '../Utils/ApiPaths';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert';
|
||||
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import { DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
|
||||
import LoginUtil from 'CommonUI/src/Utils/Login';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import UserUtil from 'CommonUI/src/Utils/User';
|
||||
import User from 'Model/Models/User';
|
||||
import React, { useState } from 'react';
|
||||
import useAsyncEffect from 'use-async-effect';
|
||||
import { LOGIN_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import { DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import UiAnalytics from "CommonUI/src/Utils/Analytics";
|
||||
import LoginUtil from "CommonUI/src/Utils/Login";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import UserUtil from "CommonUI/src/Utils/User";
|
||||
import User from "Model/Models/User";
|
||||
import React from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = LOGIN_API_URL;
|
||||
const apiUrl: URL = LOGIN_API_URL;
|
||||
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}
|
||||
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (Navigation.getQueryStringByName("email")) {
|
||||
setInitialValues({
|
||||
email: Navigation.getQueryStringByName("email"),
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const showSsoMessage: boolean = Boolean(
|
||||
Navigation.getQueryStringByName('sso')
|
||||
);
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Join thousands of business that use OneUptime to help them stay online
|
||||
all the time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
const [showSsoTip, setShowSSOTip] = useState<boolean>(false);
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="login-form"
|
||||
name="Login"
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues["email"]),
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: "Password",
|
||||
required: true,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
sideLink: {
|
||||
text: "Forgot password?",
|
||||
url: new Route("/accounts/forgot-password"),
|
||||
openLinkInNewTab: false,
|
||||
},
|
||||
dataTestId: "password",
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Login"}
|
||||
onSuccess={(value: User, miscData: JSONObject | undefined) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture("accounts/login");
|
||||
}
|
||||
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
if (Navigation.getQueryStringByName('email')) {
|
||||
setInitialValues({
|
||||
email: Navigation.getQueryStringByName('email'),
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Join thousands of business that use OneUptime to help them
|
||||
stay online all the time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{showSsoMessage && (
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md mt-8">
|
||||
{' '}
|
||||
<Alert
|
||||
type={AlertType.DANGER}
|
||||
title="You must be logged into OneUptime account to use single sign-on (SSO) for your project. Logging in to OneUptime account and single sign on (SSO) for your project are two separate steps. Please use the form below to log in to your OneUptime account before you use SSO."
|
||||
/>{' '}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="login-form"
|
||||
name="Login"
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: 'jeff@example.com',
|
||||
required: true,
|
||||
disabled: Boolean(
|
||||
initialValues && initialValues['email']
|
||||
),
|
||||
title: 'Email',
|
||||
dataTestId: 'email',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: 'Password',
|
||||
required: true,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
sideLink: {
|
||||
text: 'Forgot password?',
|
||||
url: new Route('/accounts/forgot-password'),
|
||||
openLinkInNewTab: false,
|
||||
},
|
||||
dataTestId: 'password',
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={'Login'}
|
||||
onSuccess={(
|
||||
value: User,
|
||||
miscData: JSONObject | undefined
|
||||
) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture('accounts/login');
|
||||
}
|
||||
|
||||
LoginUtil.login({
|
||||
user: value,
|
||||
token: miscData ? miscData['token'] : undefined,
|
||||
});
|
||||
}}
|
||||
maxPrimaryButtonWidth={true}
|
||||
footer={
|
||||
<div className="actions text-center mt-4 hover:underline fw-semibold">
|
||||
<div>
|
||||
{!showSsoTip && (
|
||||
<div
|
||||
onClick={() => {
|
||||
setShowSSOTip(true);
|
||||
}}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
|
||||
>
|
||||
Use single sign-on (SSO) instead
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSsoTip && (
|
||||
<div className="text-gray-500 text-sm">
|
||||
Please sign in with your SSO
|
||||
provider like Okta, Auth0, Entra ID
|
||||
or any other SAML 2.0 provider.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{' '}
|
||||
<Link
|
||||
to={new Route('/accounts/register')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Register.
|
||||
</Link>
|
||||
LoginUtil.login({
|
||||
user: value,
|
||||
token: miscData ? miscData["token"] : undefined,
|
||||
});
|
||||
}}
|
||||
maxPrimaryButtonWidth={true}
|
||||
footer={
|
||||
<div className="actions text-center mt-4 hover:underline fw-semibold">
|
||||
<div>
|
||||
<Link to={new Route("/accounts/sso")}>
|
||||
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
|
||||
Use single sign-on (SSO) instead
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
<div className="mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/register")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Register.
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
|
||||
235
Accounts/src/Pages/LoginWithSSO.tsx
Normal file
235
Accounts/src/Pages/LoginWithSSO.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
import { SERVICE_PROVIDER_LOGIN_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import { DASHBOARD_URL, IDENTITY_URL } from "CommonUI/src/Config";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import UserUtil from "CommonUI/src/Utils/User";
|
||||
import User from "Model/Models/User";
|
||||
import React, { ReactElement, useState } from "react";
|
||||
import ProjectSSO from "Model/Models/ProjectSso";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import API from "CommonUI/src/Utils/API/API";
|
||||
import BasicForm from "CommonUI/src/Components/Forms/BasicForm";
|
||||
import Email from "Common/Types/Email";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import StaticModelList from "CommonUI/src/Components/ModelList/StaticModelList";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = SERVICE_PROVIDER_LOGIN_URL;
|
||||
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}
|
||||
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [projectSsoConfigList, setProjectSsoConfigList] = useState<
|
||||
Array<ProjectSSO>
|
||||
>([]);
|
||||
|
||||
type FetchSSOConfigsFunction = (email: Email) => Promise<void>;
|
||||
|
||||
const fetchSsoConfigs: FetchSSOConfigsFunction = async (
|
||||
email: Email,
|
||||
): Promise<void> => {
|
||||
if (email) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// get sso config by email.
|
||||
const listResult: HTTPErrorResponse | HTTPResponse<JSONArray> =
|
||||
await API.get(
|
||||
URL.fromString(apiUrl.toString()).addQueryParam(
|
||||
"email",
|
||||
email.toString(),
|
||||
),
|
||||
);
|
||||
|
||||
if (listResult instanceof HTTPErrorResponse) {
|
||||
throw listResult;
|
||||
}
|
||||
|
||||
if (!listResult.data || (listResult.data as JSONArray).length === 0) {
|
||||
setError(
|
||||
"No SSO configuration found for the email: " + email.toString(),
|
||||
);
|
||||
} else {
|
||||
setProjectSsoConfigList(
|
||||
ProjectSSO.fromJSONArray(listResult["data"], ProjectSSO),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(API.getFriendlyErrorMessage(error as Error));
|
||||
}
|
||||
} else {
|
||||
setError("Email is required to perform this action");
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
type GetSsoConfigModelListFunction = (
|
||||
configs: Array<ProjectSSO>,
|
||||
) => ReactElement;
|
||||
|
||||
const getSsoConfigModelList: GetSsoConfigModelListFunction = (
|
||||
configs: Array<ProjectSSO>,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<StaticModelList<ProjectSSO>
|
||||
list={configs}
|
||||
titleField="name"
|
||||
selectedItems={[]}
|
||||
descriptionField="description"
|
||||
onClick={(item: ProjectSSO) => {
|
||||
setIsLoading(true);
|
||||
Navigation.navigate(
|
||||
URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route(
|
||||
`/sso/${item.projectId?.toString()}/${item.id?.toString()}`,
|
||||
),
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
type GetProjectNameFunction = (projectId: string) => string;
|
||||
|
||||
const getProjectName: GetProjectNameFunction = (
|
||||
projectId: string,
|
||||
): string => {
|
||||
const projectNames: Array<string | undefined> = projectSsoConfigList
|
||||
.filter((config: ProjectSSO) => {
|
||||
return config.projectId?.toString() === projectId.toString();
|
||||
})
|
||||
.map((config: ProjectSSO) => {
|
||||
return config.project?.name;
|
||||
});
|
||||
return projectNames[0] || "Project";
|
||||
};
|
||||
|
||||
if (projectSsoConfigList.length > 0 && !error && !isLoading) {
|
||||
const projectIds: Array<string> = projectSsoConfigList.map(
|
||||
(config: ProjectSSO) => {
|
||||
return config.projectId?.toString() as string;
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-10 text-center text-xl tracking-tight text-gray-900">
|
||||
Select Project
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Select the project you want to login to.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{projectIds.map((projectId: string) => {
|
||||
return (
|
||||
<div key={projectId}>
|
||||
<h3 className="mt-6 font-medium tracking-tight">
|
||||
{getProjectName(projectId)}
|
||||
</h3>
|
||||
{getSsoConfigModelList(
|
||||
projectSsoConfigList.filter((config: ProjectSSO) => {
|
||||
return (
|
||||
config.projectId?.toString() === projectId.toString()
|
||||
);
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Login with SSO
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Login with your SSO provider to access your account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<BasicForm
|
||||
modelType={User}
|
||||
id="login-form"
|
||||
error={error}
|
||||
name="Login"
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
},
|
||||
]}
|
||||
maxPrimaryButtonWidth={true}
|
||||
submitButtonText="Login with SSO"
|
||||
onSubmit={async (data: JSONObject) => {
|
||||
await fetchSsoConfigs(data["email"] as Email);
|
||||
}}
|
||||
footer={
|
||||
<div className="actions text-center mt-4 hover:underline fw-semibold">
|
||||
<div>
|
||||
<Link to={new Route("/accounts/login")}>
|
||||
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
|
||||
Use username and password insead.
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-10 text-center">
|
||||
<div className="text-muted mb-0 text-gray-500">
|
||||
Don't have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/register")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Register.
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
@@ -1,18 +1,18 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
const LoginPage: () => JSX.Element = () => {
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Page not found
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Page you are looking for does not exist.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Page not found
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Page you are looking for does not exist.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
|
||||
@@ -1,276 +1,267 @@
|
||||
import { SIGNUP_API_URL } from '../Utils/ApiPaths';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
|
||||
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import Fields from 'CommonUI/src/Components/Forms/Types/Fields';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import { BILLING_ENABLED, DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import BaseAPI from 'CommonUI/src/Utils/API/API';
|
||||
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
|
||||
import LocalStorage from 'CommonUI/src/Utils/LocalStorage';
|
||||
import LoginUtil from 'CommonUI/src/Utils/Login';
|
||||
import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import UserUtil from 'CommonUI/src/Utils/User';
|
||||
import Reseller from 'Model/Models/Reseller';
|
||||
import User from 'Model/Models/User';
|
||||
import React, { useState } from 'react';
|
||||
import useAsyncEffect from 'use-async-effect';
|
||||
import { SIGNUP_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
|
||||
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import Fields from "CommonUI/src/Components/Forms/Types/Fields";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import { BILLING_ENABLED, DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import BaseAPI from "CommonUI/src/Utils/API/API";
|
||||
import UiAnalytics from "CommonUI/src/Utils/Analytics";
|
||||
import LocalStorage from "CommonUI/src/Utils/LocalStorage";
|
||||
import LoginUtil from "CommonUI/src/Utils/Login";
|
||||
import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import UserUtil from "CommonUI/src/Utils/User";
|
||||
import Reseller from "Model/Models/Reseller";
|
||||
import User from "Model/Models/User";
|
||||
import React, { useState } from "react";
|
||||
import useAsyncEffect from "use-async-effect";
|
||||
|
||||
const RegisterPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = SIGNUP_API_URL;
|
||||
const apiUrl: URL = SIGNUP_API_URL;
|
||||
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
|
||||
|
||||
const [error, setError] = useState<string>('');
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||
|
||||
const [reseller, setResller] = React.useState<Reseller | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [reseller, setResller] = React.useState<Reseller | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
if (UserUtil.isLoggedIn()) {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}
|
||||
|
||||
type FetchResellerFunction = (resellerId: string) => Promise<void>;
|
||||
|
||||
const fetchReseller: FetchResellerFunction = async (
|
||||
resellerId: string,
|
||||
): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const reseller: ListResult<Reseller> = await ModelAPI.getList<Reseller>({
|
||||
modelType: Reseller,
|
||||
query: {
|
||||
resellerId: resellerId,
|
||||
},
|
||||
limit: 1,
|
||||
skip: 0,
|
||||
select: {
|
||||
hidePhoneNumberOnSignup: true,
|
||||
},
|
||||
sort: {},
|
||||
requestOptions: {},
|
||||
});
|
||||
|
||||
if (reseller.data.length > 0) {
|
||||
setResller(reseller.data[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(BaseAPI.getFriendlyMessage(err));
|
||||
}
|
||||
|
||||
type FetchResellerFunction = (resellerId: string) => Promise<void>;
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const fetchReseller: FetchResellerFunction = async (
|
||||
resellerId: string
|
||||
): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const reseller: ListResult<Reseller> =
|
||||
await ModelAPI.getList<Reseller>({
|
||||
modelType: Reseller,
|
||||
query: {
|
||||
resellerId: resellerId,
|
||||
},
|
||||
limit: 1,
|
||||
skip: 0,
|
||||
select: {
|
||||
hidePhoneNumberOnSignup: true,
|
||||
},
|
||||
sort: {},
|
||||
requestOptions: {},
|
||||
});
|
||||
|
||||
if (reseller.data.length > 0) {
|
||||
setResller(reseller.data[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(BaseAPI.getFriendlyMessage(err));
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
// if promo code is found, please save it in localstorage.
|
||||
if (Navigation.getQueryStringByName('promoCode')) {
|
||||
LocalStorage.setItem(
|
||||
'promoCode',
|
||||
Navigation.getQueryStringByName('promoCode')
|
||||
);
|
||||
}
|
||||
|
||||
if (Navigation.getQueryStringByName('email')) {
|
||||
setInitialValues({
|
||||
email: Navigation.getQueryStringByName('email'),
|
||||
});
|
||||
}
|
||||
|
||||
// if promo code is found, please save it in localstorage.
|
||||
if (Navigation.getQueryStringByName('partnerId')) {
|
||||
await fetchReseller(Navigation.getQueryStringByName('partnerId')!);
|
||||
}
|
||||
}, []);
|
||||
|
||||
let formFields: Fields<User> = [
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: 'jeff@example.com',
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues['email']),
|
||||
title: 'Email',
|
||||
dataTestId: 'email',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'Jeff Smith',
|
||||
required: true,
|
||||
title: 'Full Name',
|
||||
dataTestId: 'name',
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
field: {
|
||||
companyName: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'Acme, Inc.',
|
||||
required: true,
|
||||
title: 'Company Name',
|
||||
dataTestId: 'companyName',
|
||||
},
|
||||
]);
|
||||
|
||||
// If reseller wants to hide phone number on sign up, we hide it.
|
||||
if (!reseller || !reseller.hidePhoneNumberOnSignup) {
|
||||
formFields.push({
|
||||
field: {
|
||||
companyPhoneNumber: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: true,
|
||||
placeholder: '+11234567890',
|
||||
title: 'Phone Number',
|
||||
dataTestId: 'companyPhoneNumber',
|
||||
});
|
||||
}
|
||||
useAsyncEffect(async () => {
|
||||
// if promo code is found, please save it in localstorage.
|
||||
if (Navigation.getQueryStringByName("promoCode")) {
|
||||
LocalStorage.setItem(
|
||||
"promoCode",
|
||||
Navigation.getQueryStringByName("promoCode"),
|
||||
);
|
||||
}
|
||||
|
||||
if (Navigation.getQueryStringByName("email")) {
|
||||
setInitialValues({
|
||||
email: Navigation.getQueryStringByName("email"),
|
||||
});
|
||||
}
|
||||
|
||||
// if promo code is found, please save it in localstorage.
|
||||
if (Navigation.getQueryStringByName("partnerId")) {
|
||||
await fetchReseller(Navigation.getQueryStringByName("partnerId")!);
|
||||
}
|
||||
}, []);
|
||||
|
||||
let formFields: Fields<User> = [
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
placeholder: "jeff@example.com",
|
||||
required: true,
|
||||
disabled: Boolean(initialValues && initialValues["email"]),
|
||||
title: "Email",
|
||||
dataTestId: "email",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Jeff Smith",
|
||||
required: true,
|
||||
title: "Full Name",
|
||||
dataTestId: "name",
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
placeholder: 'Password',
|
||||
title: 'Password',
|
||||
required: true,
|
||||
dataTestId: 'password',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
confirmPassword: true,
|
||||
} as any,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
toMatchField: 'password',
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
placeholder: 'Confirm Password',
|
||||
title: 'Confirm Password',
|
||||
overrideFieldKey: 'confirmPassword',
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
dataTestId: 'confirmPassword',
|
||||
{
|
||||
field: {
|
||||
companyName: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Acme, Inc.",
|
||||
required: true,
|
||||
title: "Company Name",
|
||||
dataTestId: "companyName",
|
||||
},
|
||||
]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage error={error} />;
|
||||
// If reseller wants to hide phone number on sign up, we hide it.
|
||||
if (!reseller || !reseller.hidePhoneNumberOnSignup) {
|
||||
formFields.push({
|
||||
field: {
|
||||
companyPhoneNumber: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: true,
|
||||
placeholder: "+11234567890",
|
||||
title: "Phone Number",
|
||||
dataTestId: "companyPhoneNumber",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
formFields = formFields.concat([
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
placeholder: "Password",
|
||||
title: "Password",
|
||||
required: true,
|
||||
dataTestId: "password",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
confirmPassword: true,
|
||||
} as any,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
toMatchField: "password",
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
placeholder: "Confirm Password",
|
||||
title: "Confirm Password",
|
||||
overrideFieldKey: "confirmPassword",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
dataTestId: "confirmPassword",
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Create your OneUptime account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Join thousands of business that use OneUptime to help them
|
||||
stay online all the time.
|
||||
</p>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
No credit card required.
|
||||
</p>
|
||||
</div>
|
||||
if (error) {
|
||||
return <ErrorMessage error={error} />;
|
||||
}
|
||||
|
||||
<div className="mt-8 lg:mx-auto lg:w-full lg:max-w-2xl">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
showAsColumns={reseller ? 1 : 2}
|
||||
name="Register"
|
||||
initialValues={initialValues}
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={formFields}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
const utmParams: Dictionary<string> =
|
||||
UserUtil.getUtmParams();
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
if (
|
||||
utmParams &&
|
||||
Object.keys(utmParams).length > 0
|
||||
) {
|
||||
item.utmSource = utmParams['utmSource'] || '';
|
||||
item.utmMedium = utmParams['utmMedium'] || '';
|
||||
item.utmCampaign =
|
||||
utmParams['utmCampaign'] || '';
|
||||
item.utmTerm = utmParams['utmTerm'] || '';
|
||||
item.utmContent = utmParams['utmContent'] || '';
|
||||
item.utmUrl = utmParams['utmUrl'] || '';
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="OneUptime"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Create your OneUptime account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Join thousands of business that use OneUptime to help them stay online
|
||||
all the time.
|
||||
</p>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
No credit card required.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
UiAnalytics.capture('utm_event', utmParams);
|
||||
}
|
||||
<div className="mt-8 lg:mx-auto lg:w-full lg:max-w-2xl">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
showAsColumns={reseller ? 1 : 2}
|
||||
name="Register"
|
||||
initialValues={initialValues}
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={formFields}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
const utmParams: Dictionary<string> = UserUtil.getUtmParams();
|
||||
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={'Sign Up'}
|
||||
onSuccess={(
|
||||
value: User,
|
||||
miscData: JSONObject | undefined
|
||||
) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture('accounts/register');
|
||||
}
|
||||
if (utmParams && Object.keys(utmParams).length > 0) {
|
||||
item.utmSource = utmParams["utmSource"] || "";
|
||||
item.utmMedium = utmParams["utmMedium"] || "";
|
||||
item.utmCampaign = utmParams["utmCampaign"] || "";
|
||||
item.utmTerm = utmParams["utmTerm"] || "";
|
||||
item.utmContent = utmParams["utmContent"] || "";
|
||||
item.utmUrl = utmParams["utmUrl"] || "";
|
||||
|
||||
LoginUtil.login({
|
||||
user: value,
|
||||
token: miscData ? miscData['token'] : undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5 text-center text-gray-500">
|
||||
<p className="text-muted mb-0">
|
||||
Already have an account?{' '}
|
||||
<Link
|
||||
to={new Route('/accounts/login')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Log in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
UiAnalytics.capture("utm_event", utmParams);
|
||||
}
|
||||
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Sign Up"}
|
||||
onSuccess={(value: User, miscData: JSONObject | undefined) => {
|
||||
if (value && value.email) {
|
||||
UiAnalytics.userAuth(value.email);
|
||||
UiAnalytics.capture("accounts/register");
|
||||
}
|
||||
|
||||
LoginUtil.login({
|
||||
user: value,
|
||||
token: miscData ? miscData["token"] : undefined,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
<div className="mt-5 text-center text-gray-500">
|
||||
<p className="text-muted mb-0">
|
||||
Already have an account?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Log in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterPage;
|
||||
|
||||
@@ -1,113 +1,114 @@
|
||||
import { RESET_PASSWORD_API_URL } from '../Utils/ApiPaths';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import User from 'Model/Models/User';
|
||||
import React, { useState } from 'react';
|
||||
import { RESET_PASSWORD_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import User from "Model/Models/User";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const RegisterPage: () => JSX.Element = () => {
|
||||
const apiUrl: URL = RESET_PASSWORD_API_URL;
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
const apiUrl: URL = RESET_PASSWORD_API_URL;
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Reset your password
|
||||
</h2>
|
||||
return (
|
||||
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
className="mx-auto h-12 w-auto"
|
||||
src={OneUptimeLogo}
|
||||
alt="Your Company"
|
||||
/>
|
||||
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
|
||||
Reset your password
|
||||
</h2>
|
||||
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your new password and we will have it
|
||||
updated.{' '}
|
||||
</p>
|
||||
)}
|
||||
{!isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Please enter your new password and we will have it updated.{" "}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Your password has been updated. Please log in.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{isSuccess && (
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Your password has been updated. Please log in.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
name="Reset Password"
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
item.resetPasswordToken =
|
||||
Navigation.getLastParam()
|
||||
?.toString()
|
||||
.replace('/', '')
|
||||
.toString() || '';
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
showAsColumns={1}
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
placeholder: 'New Password',
|
||||
title: 'New Password',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 6,
|
||||
toMatchField: 'password',
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
placeholder: 'Confirm Password',
|
||||
title: 'Confirm Password',
|
||||
overrideFieldKey: 'confirmPassword',
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={'Reset Password'}
|
||||
onSuccess={() => {
|
||||
setIsSuccess(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
{!isSuccess && (
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<ModelForm<User>
|
||||
modelType={User}
|
||||
id="register-form"
|
||||
name="Reset Password"
|
||||
onBeforeCreate={(item: User): Promise<User> => {
|
||||
item.resetPasswordToken =
|
||||
Navigation.getLastParam()
|
||||
?.toString()
|
||||
.replace("/", "")
|
||||
.toString() || "";
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
showAsColumns={1}
|
||||
maxPrimaryButtonWidth={true}
|
||||
fields={[
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
placeholder: "New Password",
|
||||
title: "New Password",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
confirmPassword: true,
|
||||
} as any,
|
||||
validation: {
|
||||
minLength: 6,
|
||||
toMatchField: "password",
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
placeholder: "Confirm Password",
|
||||
title: "Confirm Password",
|
||||
overrideFieldKey: "confirmPassword",
|
||||
required: true,
|
||||
showEvenIfPermissionDoesNotExist: true,
|
||||
},
|
||||
]}
|
||||
createOrUpdateApiUrl={apiUrl}
|
||||
formType={FormType.Create}
|
||||
submitButtonText={"Reset Password"}
|
||||
onSuccess={() => {
|
||||
setIsSuccess(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Know your password?{' '}
|
||||
<Link
|
||||
to={new Route('/accounts/login')}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Log in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0 text-gray-500">
|
||||
Know your password?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
|
||||
>
|
||||
Log in.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterPage;
|
||||
|
||||
@@ -1,132 +1,121 @@
|
||||
import { VERIFY_EMAIL_API_URL } from '../Utils/ApiPaths';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import Link from 'CommonUI/src/Components/Link/Link';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import API from 'CommonUI/src/Utils/API/API';
|
||||
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import EmailVerificationToken from 'Model/Models/EmailVerificationToken';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { VERIFY_EMAIL_API_URL } from "../Utils/ApiPaths";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import Link from "CommonUI/src/Components/Link/Link";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import API from "CommonUI/src/Utils/API/API";
|
||||
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import EmailVerificationToken from "Model/Models/EmailVerificationToken";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const VerifyEmail: () => JSX.Element = () => {
|
||||
const apiUrl: URL = VERIFY_EMAIL_API_URL;
|
||||
const [error, setError] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const apiUrl: URL = VERIFY_EMAIL_API_URL;
|
||||
const [error, setError] = useState<string>("");
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
// Ping an API here.
|
||||
setError('');
|
||||
setIsLoading(true);
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
// Ping an API here.
|
||||
setError("");
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// strip data.
|
||||
const emailverificationToken: EmailVerificationToken =
|
||||
new EmailVerificationToken();
|
||||
emailverificationToken.token = new ObjectID(
|
||||
Navigation.getLastParam()?.toString().replace('/', '') || ''
|
||||
);
|
||||
try {
|
||||
// strip data.
|
||||
const emailverificationToken: EmailVerificationToken =
|
||||
new EmailVerificationToken();
|
||||
emailverificationToken.token = new ObjectID(
|
||||
Navigation.getLastParam()?.toString().replace("/", "") || "",
|
||||
);
|
||||
|
||||
await ModelAPI.createOrUpdate<EmailVerificationToken>({
|
||||
model: emailverificationToken,
|
||||
modelType: EmailVerificationToken,
|
||||
formType: FormType.Create,
|
||||
miscDataProps: {},
|
||||
requestOptions: {
|
||||
overrideRequestUrl: apiUrl,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyMessage(err));
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
init().catch((err: Error) => {
|
||||
setError(err.toString());
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
await ModelAPI.createOrUpdate<EmailVerificationToken>({
|
||||
model: emailverificationToken,
|
||||
modelType: EmailVerificationToken,
|
||||
formType: FormType.Create,
|
||||
miscDataProps: {},
|
||||
requestOptions: {
|
||||
overrideRequestUrl: apiUrl,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyMessage(err));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="container-fluid p-0">
|
||||
<div className="row g-0">
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-6">
|
||||
<div className="auth-full-page-content d-flex p-sm-5 p-4">
|
||||
<div className="w-100">
|
||||
<div className="d-flex flex-column h-100">
|
||||
<div className="auth-content my-auto">
|
||||
<div
|
||||
className="mt-4 text-center flex justify-center"
|
||||
style={{ marginBottom: '40px' }}
|
||||
>
|
||||
<img
|
||||
style={{ height: '50px' }}
|
||||
src={`${OneUptimeLogo}`}
|
||||
/>
|
||||
</div>
|
||||
{!error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">
|
||||
Your email is verified.
|
||||
</h5>
|
||||
<p className="text-muted mt-2 mb-0">
|
||||
Thank you for verifying your
|
||||
email. You can now log in to
|
||||
OneUptime.{' '}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
useEffect(() => {
|
||||
init().catch((err: Error) => {
|
||||
setError(err.toString());
|
||||
});
|
||||
}, []);
|
||||
|
||||
{error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">
|
||||
Sorry, something went wrong!
|
||||
</h5>
|
||||
<p className="text-muted mt-2 mb-0">
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0">
|
||||
Return to sign in?{' '}
|
||||
<Link
|
||||
to={
|
||||
new Route(
|
||||
'/accounts/login'
|
||||
)
|
||||
}
|
||||
className="hover:underline text-primary fw-semibold"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className="auth-page">
|
||||
<div className="container-fluid p-0">
|
||||
<div className="row g-0">
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-6">
|
||||
<div className="auth-full-page-content d-flex p-sm-5 p-4">
|
||||
<div className="w-100">
|
||||
<div className="d-flex flex-column h-100">
|
||||
<div className="auth-content my-auto">
|
||||
<div
|
||||
className="mt-4 text-center flex justify-center"
|
||||
style={{ marginBottom: "40px" }}
|
||||
>
|
||||
<img
|
||||
style={{ height: "50px" }}
|
||||
src={`${OneUptimeLogo}`}
|
||||
/>
|
||||
</div>
|
||||
{!error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">Your email is verified.</h5>
|
||||
<p className="text-muted mt-2 mb-0">
|
||||
Thank you for verifying your email. You can now log in
|
||||
to OneUptime.{" "}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
{error && (
|
||||
<div className="text-center">
|
||||
<h5 className="mb-0">Sorry, something went wrong!</h5>
|
||||
<p className="text-muted mt-2 mb-0">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 text-center">
|
||||
<p className="text-muted mb-0">
|
||||
Return to sign in?{" "}
|
||||
<Link
|
||||
to={new Route("/accounts/login")}
|
||||
className="hover:underline text-primary fw-semibold"
|
||||
>
|
||||
Login.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyEmail;
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { IDENTITY_URL } from 'CommonUI/src/Config';
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { IDENTITY_URL } from "CommonUI/src/Config";
|
||||
|
||||
export const SIGNUP_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/signup')
|
||||
new Route("/signup"),
|
||||
);
|
||||
export const LOGIN_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/login')
|
||||
new Route("/login"),
|
||||
);
|
||||
|
||||
export const SERVICE_PROVIDER_LOGIN_URL: URL = URL.fromURL(
|
||||
IDENTITY_URL,
|
||||
).addRoute(new Route("/service-provider-login"));
|
||||
|
||||
export const FORGOT_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/forgot-password')
|
||||
new Route("/forgot-password"),
|
||||
);
|
||||
|
||||
export const VERIFY_EMAIL_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/verify-email')
|
||||
new Route("/verify-email"),
|
||||
);
|
||||
|
||||
export const RESET_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
|
||||
new Route('/reset-password')
|
||||
new Route("/reset-password"),
|
||||
);
|
||||
|
||||
@@ -1,83 +1,84 @@
|
||||
require('ts-loader');
|
||||
require('file-loader');
|
||||
require('style-loader');
|
||||
require('css-loader');
|
||||
require('sass-loader');
|
||||
require("ts-loader");
|
||||
require("file-loader");
|
||||
require("style-loader");
|
||||
require("css-loader");
|
||||
require("sass-loader");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const dotenv = require('dotenv');
|
||||
const express = require('express');
|
||||
const dotenv = require("dotenv");
|
||||
const express = require("express");
|
||||
|
||||
const readEnvFile = (pathToFile) => {
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
const env = {};
|
||||
|
||||
const env = {
|
||||
};
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
return env;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/accounts/dist/",
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/accounts/dist/",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
|
||||
alias: {
|
||||
react: path.resolve("./node_modules/react"),
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'],
|
||||
alias: {
|
||||
react: path.resolve('./node_modules/react'),
|
||||
}
|
||||
},
|
||||
externals: {
|
||||
'react-native-sqlite-storage': 'react-native-sqlite-storage'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process': {
|
||||
'env': {
|
||||
...readEnvFile('/usr/src/app/dev-env/.env')
|
||||
}
|
||||
}
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: 'ts-loader'
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ['style-loader', 'css-loader', "sass-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: 'file-loader'
|
||||
}
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
externals: {
|
||||
"react-native-sqlite-storage": "react-native-sqlite-storage",
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
process: {
|
||||
env: {
|
||||
...readEnvFile("/usr/src/app/dev-env/.env"),
|
||||
},
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use('/accounts/assets', express.static(path.resolve(__dirname, 'public', 'assets')));
|
||||
return middlewares;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: "ts-loader",
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ["style-loader", "css-loader", "sass-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: "file-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
devtool: 'eval-source-map',
|
||||
}
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use(
|
||||
"/accounts/assets",
|
||||
express.static(path.resolve(__dirname, "public", "assets")),
|
||||
);
|
||||
return middlewares;
|
||||
},
|
||||
},
|
||||
devtool: "eval-source-map",
|
||||
};
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import App from 'CommonServer/Utils/StartServer';
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import App from "CommonServer/Utils/StartServer";
|
||||
|
||||
export const APP_NAME: string = 'admin';
|
||||
export const APP_NAME: string = "admin";
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
try {
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
try {
|
||||
// init the app
|
||||
await App.init({
|
||||
appName: APP_NAME,
|
||||
port: undefined,
|
||||
isFrontendApp: true,
|
||||
statusOptions: {
|
||||
liveCheck: async () => {},
|
||||
readyCheck: async () => {},
|
||||
},
|
||||
});
|
||||
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error('App Init Failed:');
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
// add default routes
|
||||
await App.addDefaultRoutes();
|
||||
} catch (err) {
|
||||
logger.error("App Init Failed:");
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
init().catch((err: Error) => {
|
||||
logger.error(err);
|
||||
logger.error('Exiting node process');
|
||||
process.exit(1);
|
||||
logger.error(err);
|
||||
logger.error("Exiting node process");
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
8
AdminDashboard/index.d.ts
vendored
8
AdminDashboard/index.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
declare module '*.png';
|
||||
declare module '*.svg';
|
||||
declare module '*.jpg';
|
||||
declare module '*.gif';
|
||||
declare module "*.png";
|
||||
declare module "*.svg";
|
||||
declare module "*.jpg";
|
||||
declare module "*.gif";
|
||||
|
||||
@@ -1,112 +1,103 @@
|
||||
import MasterPage from './Components/MasterPage/MasterPage';
|
||||
import Init from './Pages/Init/Init';
|
||||
import Logout from './Pages/Logout/Logout';
|
||||
import Projects from './Pages/Projects/Index';
|
||||
import SettingsAPIKey from './Pages/Settings/APIKey/Index';
|
||||
import SettingsAuthentication from './Pages/Settings/Authentication/Index';
|
||||
import SettingsCallSMS from './Pages/Settings/CallSMS/Index';
|
||||
import MasterPage from "./Components/MasterPage/MasterPage";
|
||||
import Init from "./Pages/Init/Init";
|
||||
import Logout from "./Pages/Logout/Logout";
|
||||
import Projects from "./Pages/Projects/Index";
|
||||
import SettingsAPIKey from "./Pages/Settings/APIKey/Index";
|
||||
import SettingsAuthentication from "./Pages/Settings/Authentication/Index";
|
||||
import SettingsCallSMS from "./Pages/Settings/CallSMS/Index";
|
||||
// Settings Pages.
|
||||
import SettingsEmail from './Pages/Settings/Email/Index';
|
||||
import SettingsProbes from './Pages/Settings/Probes/Index';
|
||||
import Users from './Pages/Users/Index';
|
||||
import PageMap from './Utils/PageMap';
|
||||
import RouteMap from './Utils/RouteMap';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import { ACCOUNTS_URL, DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import User from 'CommonUI/src/Utils/User';
|
||||
import React from 'react';
|
||||
import SettingsEmail from "./Pages/Settings/Email/Index";
|
||||
import SettingsProbes from "./Pages/Settings/Probes/Index";
|
||||
import Users from "./Pages/Users/Index";
|
||||
import PageMap from "./Utils/PageMap";
|
||||
import RouteMap from "./Utils/RouteMap";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { ACCOUNTS_URL, DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import User from "CommonUI/src/Utils/User";
|
||||
import React from "react";
|
||||
import {
|
||||
Route as PageRoute,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
Route as PageRoute,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
|
||||
const App: () => JSX.Element = () => {
|
||||
Navigation.setNavigateHook(useNavigate());
|
||||
Navigation.setLocation(useLocation());
|
||||
Navigation.setParams(useParams());
|
||||
Navigation.setNavigateHook(useNavigate());
|
||||
Navigation.setLocation(useLocation());
|
||||
Navigation.setParams(useParams());
|
||||
|
||||
if (!User.isLoggedIn()) {
|
||||
if (Navigation.getQueryStringByName('sso_token')) {
|
||||
Navigation.navigate(
|
||||
URL.fromString(ACCOUNTS_URL.toString()).addQueryParam(
|
||||
'sso',
|
||||
'true'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
Navigation.navigate(URL.fromString(ACCOUNTS_URL.toString()));
|
||||
}
|
||||
if (!User.isLoggedIn()) {
|
||||
if (Navigation.getQueryStringByName("sso_token")) {
|
||||
Navigation.navigate(
|
||||
URL.fromString(ACCOUNTS_URL.toString()).addQueryParam("sso", "true"),
|
||||
);
|
||||
} else {
|
||||
Navigation.navigate(URL.fromString(ACCOUNTS_URL.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!User.isMasterAdmin()) {
|
||||
Navigation.navigate(URL.fromString(DASHBOARD_URL.toString()));
|
||||
}
|
||||
if (!User.isMasterAdmin()) {
|
||||
Navigation.navigate(URL.fromString(DASHBOARD_URL.toString()));
|
||||
}
|
||||
|
||||
return (
|
||||
<MasterPage>
|
||||
<Routes>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.INIT]?.toString() || ''}
|
||||
element={<Init />}
|
||||
/>
|
||||
return (
|
||||
<MasterPage>
|
||||
<Routes>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.INIT]?.toString() || ""}
|
||||
element={<Init />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.PROJECTS]?.toString() || ''}
|
||||
element={<Projects />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.PROJECTS]?.toString() || ""}
|
||||
element={<Projects />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.USERS]?.toString() || ''}
|
||||
element={<Users />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.USERS]?.toString() || ""}
|
||||
element={<Users />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.LOGOUT]?.toString() || ''}
|
||||
element={<Logout />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.LOGOUT]?.toString() || ""}
|
||||
element={<Logout />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS]?.toString() || ''}
|
||||
element={<SettingsAuthentication />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS]?.toString() || ""}
|
||||
element={<SettingsAuthentication />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_SMTP]?.toString() || ''}
|
||||
element={<SettingsEmail />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_SMTP]?.toString() || ""}
|
||||
element={<SettingsEmail />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS]?.toString() ||
|
||||
''
|
||||
}
|
||||
element={<SettingsCallSMS />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_CALL_AND_SMS]?.toString() || ""}
|
||||
element={<SettingsCallSMS />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_PROBES]?.toString() || ''}
|
||||
element={<SettingsProbes />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_PROBES]?.toString() || ""}
|
||||
element={<SettingsProbes />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION]?.toString() ||
|
||||
''
|
||||
}
|
||||
element={<SettingsAuthentication />}
|
||||
/>
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_AUTHENTICATION]?.toString() || ""}
|
||||
element={<SettingsAuthentication />}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_API_KEY]?.toString() || ''}
|
||||
element={<SettingsAPIKey />}
|
||||
/>
|
||||
</Routes>
|
||||
</MasterPage>
|
||||
);
|
||||
<PageRoute
|
||||
path={RouteMap[PageMap.SETTINGS_API_KEY]?.toString() || ""}
|
||||
element={<SettingsAPIKey />}
|
||||
/>
|
||||
</Routes>
|
||||
</MasterPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,122 +1,114 @@
|
||||
import HTTPResponse from 'Common/Types/API/HTTPResponse';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import API from 'Common/Utils/API';
|
||||
import Footer from 'CommonUI/src/Components/Footer/Footer';
|
||||
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
|
||||
import { HOST, HTTP_PROTOCOL } from 'CommonUI/src/Config';
|
||||
import React from 'react';
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import API from "Common/Utils/API";
|
||||
import Footer from "CommonUI/src/Components/Footer/Footer";
|
||||
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
|
||||
import { HOST, HTTP_PROTOCOL } from "CommonUI/src/Config";
|
||||
import React from "react";
|
||||
|
||||
const DashboardFooter: () => JSX.Element = () => {
|
||||
const [showAboutModal, setShowAboutModal] = React.useState<boolean>(false);
|
||||
const [isAboutModalLoading, setIsAboutModalLoading] =
|
||||
React.useState<boolean>(false);
|
||||
const [versionText, setVersionText] = React.useState<Dictionary<string>>(
|
||||
{}
|
||||
const [showAboutModal, setShowAboutModal] = React.useState<boolean>(false);
|
||||
const [isAboutModalLoading, setIsAboutModalLoading] =
|
||||
React.useState<boolean>(false);
|
||||
const [versionText, setVersionText] = React.useState<Dictionary<string>>({});
|
||||
|
||||
const fetchVersions: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsAboutModalLoading(true);
|
||||
|
||||
try {
|
||||
const verText: Dictionary<string> = {};
|
||||
const apps: Array<{
|
||||
name: string;
|
||||
path: string;
|
||||
}> = [
|
||||
{
|
||||
name: "API",
|
||||
path: "/api",
|
||||
},
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: "/dashboard",
|
||||
},
|
||||
];
|
||||
|
||||
for (const app of apps) {
|
||||
const version: JSONObject = await fetchAppVersion(app.path);
|
||||
verText[app.name] =
|
||||
`${app.name}: ${version["version"]} (${version["commit"]})`;
|
||||
}
|
||||
|
||||
setVersionText(verText);
|
||||
} catch (err) {
|
||||
setVersionText({
|
||||
error: "Version data is not available: " + (err as Error).message,
|
||||
});
|
||||
}
|
||||
|
||||
setIsAboutModalLoading(false);
|
||||
};
|
||||
|
||||
const fetchAppVersion: (appName: string) => Promise<JSONObject> = async (
|
||||
appName: string,
|
||||
): Promise<JSONObject> => {
|
||||
const response: HTTPResponse<JSONObject> = await API.get<JSONObject>(
|
||||
URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`),
|
||||
);
|
||||
|
||||
const fetchVersions: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsAboutModalLoading(true);
|
||||
if (response.data) {
|
||||
return response.data as JSONObject;
|
||||
}
|
||||
throw new BadDataException("Version data is not available");
|
||||
};
|
||||
|
||||
try {
|
||||
const verText: Dictionary<string> = {};
|
||||
const apps: Array<{
|
||||
name: string;
|
||||
path: string;
|
||||
}> = [
|
||||
{
|
||||
name: 'API',
|
||||
path: '/api',
|
||||
},
|
||||
{
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<Footer
|
||||
className="bg-white h-16 inset-x-0 bottom-0 px-8"
|
||||
copyright="HackerBay, Inc."
|
||||
links={[
|
||||
{
|
||||
title: "Help and Support",
|
||||
to: URL.fromString("https://oneuptime.com/support"),
|
||||
},
|
||||
{
|
||||
title: "Legal",
|
||||
to: URL.fromString("https://oneuptime.com/legal"),
|
||||
},
|
||||
{
|
||||
title: "Version",
|
||||
onClick: async () => {
|
||||
setShowAboutModal(true);
|
||||
await fetchVersions();
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
for (const app of apps) {
|
||||
const version: JSONObject = await fetchAppVersion(app.path);
|
||||
verText[
|
||||
app.name
|
||||
] = `${app.name}: ${version['version']} (${version['commit']})`;
|
||||
}
|
||||
|
||||
setVersionText(verText);
|
||||
} catch (err) {
|
||||
setVersionText({
|
||||
error:
|
||||
'Version data is not available: ' + (err as Error).message,
|
||||
});
|
||||
}
|
||||
|
||||
setIsAboutModalLoading(false);
|
||||
};
|
||||
|
||||
const fetchAppVersion: (appName: string) => Promise<JSONObject> = async (
|
||||
appName: string
|
||||
): Promise<JSONObject> => {
|
||||
const response: HTTPResponse<JSONObject> = await API.get<JSONObject>(
|
||||
URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`)
|
||||
);
|
||||
|
||||
if (response.data) {
|
||||
return response.data as JSONObject;
|
||||
}
|
||||
throw new BadDataException('Version data is not available');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Footer
|
||||
className="bg-white h-16 inset-x-0 bottom-0 px-8"
|
||||
copyright="HackerBay, Inc."
|
||||
links={[
|
||||
{
|
||||
title: 'Help and Support',
|
||||
to: URL.fromString('https://oneuptime.com/support'),
|
||||
},
|
||||
{
|
||||
title: 'Legal',
|
||||
to: URL.fromString('https://oneuptime.com/legal'),
|
||||
},
|
||||
{
|
||||
title: 'Version',
|
||||
onClick: async () => {
|
||||
setShowAboutModal(true);
|
||||
await fetchVersions();
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showAboutModal ? (
|
||||
<ConfirmModal
|
||||
title={`OneUptime Version`}
|
||||
description={
|
||||
<div>
|
||||
{Object.keys(versionText).map(
|
||||
(key: string, i: number) => {
|
||||
return (
|
||||
<div key={i}>{versionText[key]}</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
isLoading={isAboutModalLoading}
|
||||
submitButtonText={'Close'}
|
||||
onSubmit={() => {
|
||||
return setShowAboutModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
{showAboutModal ? (
|
||||
<ConfirmModal
|
||||
title={`OneUptime Version`}
|
||||
description={
|
||||
<div>
|
||||
{Object.keys(versionText).map((key: string, i: number) => {
|
||||
return <div key={i}>{versionText[key]}</div>;
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
isLoading={isAboutModalLoading}
|
||||
submitButtonText={"Close"}
|
||||
onSubmit={() => {
|
||||
return setShowAboutModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardFooter;
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import Help from './Help';
|
||||
import Logo from './Logo';
|
||||
import UserProfile from './UserProfile';
|
||||
import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
|
||||
import Header from 'CommonUI/src/Components/Header/Header';
|
||||
import { DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import Help from "./Help";
|
||||
import Logo from "./Logo";
|
||||
import UserProfile from "./UserProfile";
|
||||
import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
|
||||
import Header from "CommonUI/src/Components/Header/Header";
|
||||
import { DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const DashboardHeader: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
leftComponents={
|
||||
<>
|
||||
<Logo onClick={() => {}} />
|
||||
</>
|
||||
}
|
||||
centerComponents={
|
||||
<>
|
||||
{/* <SearchBox
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
leftComponents={
|
||||
<>
|
||||
<Logo onClick={() => {}} />
|
||||
</>
|
||||
}
|
||||
centerComponents={
|
||||
<>
|
||||
{/* <SearchBox
|
||||
key={2}
|
||||
selectedProject={props.selectedProject}
|
||||
onChange={(_value: string) => { }}
|
||||
/>{' '} */}
|
||||
</>
|
||||
}
|
||||
rightComponents={
|
||||
<>
|
||||
<Button
|
||||
title="Exit Admin"
|
||||
buttonStyle={ButtonStyleType.NORMAL}
|
||||
onClick={() => {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}}
|
||||
/>
|
||||
<Help />
|
||||
<UserProfile />
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
rightComponents={
|
||||
<>
|
||||
<Button
|
||||
title="Exit Admin"
|
||||
buttonStyle={ButtonStyleType.NORMAL}
|
||||
onClick={() => {
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
<Help />
|
||||
<UserProfile />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardHeader;
|
||||
|
||||
@@ -1,60 +1,58 @@
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton';
|
||||
import IconDropdownItem from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownItem';
|
||||
import IconDropdownMenu from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu';
|
||||
import IconDropdownRow from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownRow';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import URL from "Common/Types/API/URL";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
|
||||
import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem";
|
||||
import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu";
|
||||
import IconDropdownRow from "CommonUI/src/Components/Header/IconDropdown/IconDropdownRow";
|
||||
import React, { ReactElement, useState } from "react";
|
||||
|
||||
const Help: () => JSX.Element = (): ReactElement => {
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<HeaderIconDropdownButton
|
||||
icon={IconProp.Help}
|
||||
name="Help"
|
||||
showDropdown={isDropdownVisible}
|
||||
return (
|
||||
<HeaderIconDropdownButton
|
||||
icon={IconProp.Help}
|
||||
name="Help"
|
||||
showDropdown={isDropdownVisible}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
>
|
||||
<IconDropdownMenu>
|
||||
<IconDropdownRow>
|
||||
<IconDropdownItem
|
||||
title="Support Email"
|
||||
icon={IconProp.Email}
|
||||
openInNewTab={true}
|
||||
url={URL.fromString("mailto:support@oneuptime.com")}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
>
|
||||
<IconDropdownMenu>
|
||||
<IconDropdownRow>
|
||||
<IconDropdownItem
|
||||
title="Support Email"
|
||||
icon={IconProp.Email}
|
||||
openInNewTab={true}
|
||||
url={URL.fromString('mailto:support@oneuptime.com')}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
/>
|
||||
<IconDropdownItem
|
||||
title="Chat on Slack"
|
||||
icon={IconProp.Slack}
|
||||
openInNewTab={true}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
url={URL.fromString(
|
||||
'https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ'
|
||||
)}
|
||||
/>
|
||||
<IconDropdownItem
|
||||
title="Request Demo"
|
||||
icon={IconProp.Window}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
openInNewTab={true}
|
||||
url={URL.fromString(
|
||||
'https://oneuptime.com/enterprise/demo'
|
||||
)}
|
||||
/>
|
||||
</IconDropdownRow>
|
||||
</IconDropdownMenu>
|
||||
</HeaderIconDropdownButton>
|
||||
);
|
||||
/>
|
||||
<IconDropdownItem
|
||||
title="Chat on Slack"
|
||||
icon={IconProp.Slack}
|
||||
openInNewTab={true}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
url={URL.fromString(
|
||||
"https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ",
|
||||
)}
|
||||
/>
|
||||
<IconDropdownItem
|
||||
title="Request Demo"
|
||||
icon={IconProp.Window}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
openInNewTab={true}
|
||||
url={URL.fromString("https://oneuptime.com/enterprise/demo")}
|
||||
/>
|
||||
</IconDropdownRow>
|
||||
</IconDropdownMenu>
|
||||
</HeaderIconDropdownButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default Help;
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
// Tailwind
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import Image from 'CommonUI/src/Components/Image/Image';
|
||||
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import Route from "Common/Types/API/Route";
|
||||
import Image from "CommonUI/src/Components/Image/Image";
|
||||
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
onClick: () => void;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const Logo: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<div className="relative z-10 flex px-2 lg:px-0">
|
||||
<div className="flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
className="block h-8 w-auto"
|
||||
onClick={() => {
|
||||
props.onClick && props.onClick();
|
||||
}}
|
||||
imageUrl={Route.fromString(`${OneUptimeLogo}`)}
|
||||
alt={'OneUptime'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="relative z-10 flex px-2 lg:px-0">
|
||||
<div className="flex flex-shrink-0 items-center">
|
||||
<Image
|
||||
className="block h-8 w-auto"
|
||||
onClick={() => {
|
||||
props.onClick && props.onClick();
|
||||
}}
|
||||
imageUrl={Route.fromString(`${OneUptimeLogo}`)}
|
||||
alt={"OneUptime"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton';
|
||||
import NotificationItem from 'CommonUI/src/Components/Header/Notifications/NotificationItem';
|
||||
import Notifications from 'CommonUI/src/Components/Header/Notifications/Notifications';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
|
||||
import NotificationItem from "CommonUI/src/Components/Header/Notifications/NotificationItem";
|
||||
import Notifications from "CommonUI/src/Components/Header/Notifications/Notifications";
|
||||
import React, { ReactElement, useState } from "react";
|
||||
|
||||
const DashboardHeader: () => JSX.Element = (): ReactElement => {
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<HeaderIconDropdownButton
|
||||
name="Notifications"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
showDropdown={isDropdownVisible}
|
||||
icon={IconProp.Notification}
|
||||
badge={4}
|
||||
>
|
||||
<Notifications>
|
||||
<NotificationItem
|
||||
title="Sample Title"
|
||||
description="Sample Description"
|
||||
createdAt={new Date()}
|
||||
icon={IconProp.Home}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
/>
|
||||
</Notifications>
|
||||
</HeaderIconDropdownButton>
|
||||
);
|
||||
return (
|
||||
<HeaderIconDropdownButton
|
||||
name="Notifications"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
showDropdown={isDropdownVisible}
|
||||
icon={IconProp.Notification}
|
||||
badge={4}
|
||||
>
|
||||
<Notifications>
|
||||
<NotificationItem
|
||||
title="Sample Title"
|
||||
description="Sample Description"
|
||||
createdAt={new Date()}
|
||||
icon={IconProp.Home}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
/>
|
||||
</Notifications>
|
||||
</HeaderIconDropdownButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardHeader;
|
||||
|
||||
@@ -1,286 +1,273 @@
|
||||
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
|
||||
import Field from 'CommonUI/src/Components/Forms/Types/Field';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import ProjectPicker from 'CommonUI/src/Components/Header/ProjectPicker/ProjectPicker';
|
||||
import ModelFormModal from 'CommonUI/src/Components/ModelFormModal/ModelFormModal';
|
||||
import { RadioButton } from 'CommonUI/src/Components/RadioButtons/GroupRadioButtons';
|
||||
import Toggle from 'CommonUI/src/Components/Toggle/Toggle';
|
||||
import { BILLING_ENABLED, getAllEnvVars } from 'CommonUI/src/Config';
|
||||
import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes';
|
||||
import ProjectUtil from 'CommonUI/src/Utils/Project';
|
||||
import Project from 'Model/Models/Project';
|
||||
import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import { FormType } from "CommonUI/src/Components/Forms/ModelForm";
|
||||
import Field from "CommonUI/src/Components/Forms/Types/Field";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ProjectPicker from "CommonUI/src/Components/Header/ProjectPicker/ProjectPicker";
|
||||
import ModelFormModal from "CommonUI/src/Components/ModelFormModal/ModelFormModal";
|
||||
import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons";
|
||||
import Toggle from "CommonUI/src/Components/Toggle/Toggle";
|
||||
import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config";
|
||||
import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes";
|
||||
import ProjectUtil from "CommonUI/src/Utils/Project";
|
||||
import Project from "Model/Models/Project";
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
projects: Array<Project>;
|
||||
onProjectSelected: (project: Project) => void;
|
||||
showProjectModal: boolean;
|
||||
onProjectModalClose: () => void;
|
||||
projects: Array<Project>;
|
||||
onProjectSelected: (project: Project) => void;
|
||||
showProjectModal: boolean;
|
||||
onProjectModalClose: () => void;
|
||||
}
|
||||
|
||||
const DashboardProjectPicker: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(
|
||||
null
|
||||
);
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
||||
|
||||
const getFooter: GetReactElementFunction = (): ReactElement => {
|
||||
if (!BILLING_ENABLED) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Toggle
|
||||
title="Yearly Plan"
|
||||
value={isSubscriptionPlanYearly}
|
||||
description="(Save 20%)"
|
||||
onChange={(value: boolean) => {
|
||||
setIsSubscriptionPlanYearly(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
|
||||
useState<boolean>(true);
|
||||
|
||||
const [fields, setFields] = useState<Array<Field<Project>>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.showProjectModal) {
|
||||
setShowModal(true);
|
||||
}
|
||||
}, [props.showProjectModal]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentProject: Project | null = ProjectUtil.getCurrentProject();
|
||||
setSelectedProject(currentProject);
|
||||
if (currentProject && props.onProjectSelected) {
|
||||
props.onProjectSelected(currentProject);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProject) {
|
||||
ProjectUtil.setCurrentProject(selectedProject);
|
||||
if (props.onProjectSelected) {
|
||||
props.onProjectSelected(selectedProject);
|
||||
}
|
||||
}
|
||||
}, [selectedProject]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.projects &&
|
||||
props.projects.length > 0 &&
|
||||
!selectedProject &&
|
||||
props.projects[0]
|
||||
) {
|
||||
const currentProject: Project | null =
|
||||
ProjectUtil.getCurrentProject();
|
||||
|
||||
if (!currentProject) {
|
||||
setSelectedProject(props.projects[0]);
|
||||
} else if (
|
||||
props.projects.filter((project: Project) => {
|
||||
return project._id === currentProject._id;
|
||||
}).length > 0
|
||||
) {
|
||||
setSelectedProject(
|
||||
props.projects.filter((project: Project) => {
|
||||
return project._id === currentProject._id;
|
||||
})[0] as Project
|
||||
);
|
||||
} else {
|
||||
setSelectedProject(props.projects[0]);
|
||||
}
|
||||
}
|
||||
}, [props.projects]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshFields();
|
||||
}, [isSubscriptionPlanYearly]);
|
||||
|
||||
const refreshFields: VoidFunction = (): void => {
|
||||
let formFields: Array<Field<Project>> = [
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 4,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'My Project',
|
||||
description: 'Pick a friendly name.',
|
||||
title: 'Project Name',
|
||||
required: true,
|
||||
stepId: BILLING_ENABLED ? 'basic' : undefined,
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = [
|
||||
...formFields,
|
||||
{
|
||||
field: {
|
||||
paymentProviderPlanId: true,
|
||||
},
|
||||
stepId: 'plan',
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
footerElement: getFooter(),
|
||||
fieldType: FormFieldSchemaType.RadioButton,
|
||||
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
|
||||
getAllEnvVars()
|
||||
).map((plan: SubscriptionPlan): RadioButton => {
|
||||
let description: string = plan.isCustomPricing()
|
||||
? `Our sales team will contact you soon.`
|
||||
: `Billed ${
|
||||
isSubscriptionPlanYearly
|
||||
? 'yearly'
|
||||
: 'monthly'
|
||||
}. ${
|
||||
plan.getTrialPeriod() > 0
|
||||
? `Free ${plan.getTrialPeriod()} days trial.`
|
||||
: ''
|
||||
}`;
|
||||
|
||||
if (
|
||||
isSubscriptionPlanYearly &&
|
||||
plan.getYearlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = 'This plan is free, forever. ';
|
||||
}
|
||||
|
||||
if (
|
||||
!isSubscriptionPlanYearly &&
|
||||
plan.getMonthlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = 'This plan is free, forever. ';
|
||||
}
|
||||
|
||||
return {
|
||||
value: isSubscriptionPlanYearly
|
||||
? plan.getYearlyPlanId()
|
||||
: plan.getMonthlyPlanId(),
|
||||
title: plan.getName(),
|
||||
description: description,
|
||||
sideTitle: plan.isCustomPricing()
|
||||
? 'Custom Price'
|
||||
: isSubscriptionPlanYearly
|
||||
? '$' +
|
||||
plan
|
||||
.getYearlySubscriptionAmountInUSD()
|
||||
.toString() +
|
||||
'/mo billed yearly'
|
||||
: '$' +
|
||||
plan
|
||||
.getMonthlySubscriptionAmountInUSD()
|
||||
.toString(),
|
||||
sideDescription: plan.isCustomPricing()
|
||||
? ''
|
||||
: isSubscriptionPlanYearly
|
||||
? `~ $${
|
||||
plan.getYearlySubscriptionAmountInUSD() *
|
||||
12
|
||||
} per user / year`
|
||||
: `/month per user`,
|
||||
};
|
||||
}),
|
||||
title: 'Please select a plan.',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
paymentProviderPromoCode: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'Promo Code (Optional)',
|
||||
description: 'If you have a coupon code, enter it here.',
|
||||
title: 'Promo Code',
|
||||
required: false,
|
||||
stepId: 'plan',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setFields(formFields);
|
||||
};
|
||||
const getFooter: GetReactElementFunction = (): ReactElement => {
|
||||
if (!BILLING_ENABLED) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.projects.length !== 0 && (
|
||||
<ProjectPicker
|
||||
selectedProjectName={selectedProject?.name || ''}
|
||||
selectedProjectIcon={IconProp.Folder}
|
||||
projects={props.projects}
|
||||
onCreateProjectButtonClicked={() => {
|
||||
setShowModal(true);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
onProjectSelected={(project: Project) => {
|
||||
setSelectedProject(project);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showModal ? (
|
||||
<ModelFormModal<Project>
|
||||
modelType={Project}
|
||||
name="Create New Project"
|
||||
title="Create New Project"
|
||||
description="Please create a new OneUptime project to get started."
|
||||
onClose={() => {
|
||||
setShowModal(false);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
submitButtonText="Create Project"
|
||||
onSuccess={(project: Project | null) => {
|
||||
setSelectedProject(project);
|
||||
if (project && props.onProjectSelected) {
|
||||
props.onProjectSelected(project);
|
||||
}
|
||||
setShowModal(false);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
formProps={{
|
||||
name: 'Create New Project',
|
||||
steps: BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
title: 'Basic',
|
||||
id: 'basic',
|
||||
},
|
||||
{
|
||||
title: 'Select Plan',
|
||||
id: 'plan',
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
saveRequestOptions: {
|
||||
isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request
|
||||
},
|
||||
modelType: Project,
|
||||
id: 'create-project-from',
|
||||
fields: [...fields],
|
||||
formType: FormType.Create,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
<Toggle
|
||||
title="Yearly Plan"
|
||||
value={isSubscriptionPlanYearly}
|
||||
description="(Save 20%)"
|
||||
onChange={(value: boolean) => {
|
||||
setIsSubscriptionPlanYearly(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
|
||||
useState<boolean>(true);
|
||||
|
||||
const [fields, setFields] = useState<Array<Field<Project>>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.showProjectModal) {
|
||||
setShowModal(true);
|
||||
}
|
||||
}, [props.showProjectModal]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentProject: Project | null = ProjectUtil.getCurrentProject();
|
||||
setSelectedProject(currentProject);
|
||||
if (currentProject && props.onProjectSelected) {
|
||||
props.onProjectSelected(currentProject);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProject) {
|
||||
ProjectUtil.setCurrentProject(selectedProject);
|
||||
if (props.onProjectSelected) {
|
||||
props.onProjectSelected(selectedProject);
|
||||
}
|
||||
}
|
||||
}, [selectedProject]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.projects &&
|
||||
props.projects.length > 0 &&
|
||||
!selectedProject &&
|
||||
props.projects[0]
|
||||
) {
|
||||
const currentProject: Project | null = ProjectUtil.getCurrentProject();
|
||||
|
||||
if (!currentProject) {
|
||||
setSelectedProject(props.projects[0]);
|
||||
} else if (
|
||||
props.projects.filter((project: Project) => {
|
||||
return project._id === currentProject._id;
|
||||
}).length > 0
|
||||
) {
|
||||
setSelectedProject(
|
||||
props.projects.filter((project: Project) => {
|
||||
return project._id === currentProject._id;
|
||||
})[0] as Project,
|
||||
);
|
||||
} else {
|
||||
setSelectedProject(props.projects[0]);
|
||||
}
|
||||
}
|
||||
}, [props.projects]);
|
||||
|
||||
useEffect(() => {
|
||||
refreshFields();
|
||||
}, [isSubscriptionPlanYearly]);
|
||||
|
||||
const refreshFields: VoidFunction = (): void => {
|
||||
let formFields: Array<Field<Project>> = [
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 4,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "My Project",
|
||||
description: "Pick a friendly name.",
|
||||
title: "Project Name",
|
||||
required: true,
|
||||
stepId: BILLING_ENABLED ? "basic" : undefined,
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = [
|
||||
...formFields,
|
||||
{
|
||||
field: {
|
||||
paymentProviderPlanId: true,
|
||||
},
|
||||
stepId: "plan",
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
footerElement: getFooter(),
|
||||
fieldType: FormFieldSchemaType.RadioButton,
|
||||
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
|
||||
getAllEnvVars(),
|
||||
).map((plan: SubscriptionPlan): RadioButton => {
|
||||
let description: string = plan.isCustomPricing()
|
||||
? `Our sales team will contact you soon.`
|
||||
: `Billed ${isSubscriptionPlanYearly ? "yearly" : "monthly"}. ${
|
||||
plan.getTrialPeriod() > 0
|
||||
? `Free ${plan.getTrialPeriod()} days trial.`
|
||||
: ""
|
||||
}`;
|
||||
|
||||
if (
|
||||
isSubscriptionPlanYearly &&
|
||||
plan.getYearlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = "This plan is free, forever. ";
|
||||
}
|
||||
|
||||
if (
|
||||
!isSubscriptionPlanYearly &&
|
||||
plan.getMonthlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = "This plan is free, forever. ";
|
||||
}
|
||||
|
||||
return {
|
||||
value: isSubscriptionPlanYearly
|
||||
? plan.getYearlyPlanId()
|
||||
: plan.getMonthlyPlanId(),
|
||||
title: plan.getName(),
|
||||
description: description,
|
||||
sideTitle: plan.isCustomPricing()
|
||||
? "Custom Price"
|
||||
: isSubscriptionPlanYearly
|
||||
? "$" +
|
||||
plan.getYearlySubscriptionAmountInUSD().toString() +
|
||||
"/mo billed yearly"
|
||||
: "$" + plan.getMonthlySubscriptionAmountInUSD().toString(),
|
||||
sideDescription: plan.isCustomPricing()
|
||||
? ""
|
||||
: isSubscriptionPlanYearly
|
||||
? `~ $${
|
||||
plan.getYearlySubscriptionAmountInUSD() * 12
|
||||
} per user / year`
|
||||
: `/month per user`,
|
||||
};
|
||||
}),
|
||||
title: "Please select a plan.",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
paymentProviderPromoCode: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Promo Code (Optional)",
|
||||
description: "If you have a coupon code, enter it here.",
|
||||
title: "Promo Code",
|
||||
required: false,
|
||||
stepId: "plan",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setFields(formFields);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.projects.length !== 0 && (
|
||||
<ProjectPicker
|
||||
selectedProjectName={selectedProject?.name || ""}
|
||||
selectedProjectIcon={IconProp.Folder}
|
||||
projects={props.projects}
|
||||
onCreateProjectButtonClicked={() => {
|
||||
setShowModal(true);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
onProjectSelected={(project: Project) => {
|
||||
setSelectedProject(project);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showModal ? (
|
||||
<ModelFormModal<Project>
|
||||
modelType={Project}
|
||||
name="Create New Project"
|
||||
title="Create New Project"
|
||||
description="Please create a new OneUptime project to get started."
|
||||
onClose={() => {
|
||||
setShowModal(false);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
submitButtonText="Create Project"
|
||||
onSuccess={(project: Project | null) => {
|
||||
setSelectedProject(project);
|
||||
if (project && props.onProjectSelected) {
|
||||
props.onProjectSelected(project);
|
||||
}
|
||||
setShowModal(false);
|
||||
props.onProjectModalClose();
|
||||
}}
|
||||
formProps={{
|
||||
name: "Create New Project",
|
||||
steps: BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
title: "Basic",
|
||||
id: "basic",
|
||||
},
|
||||
{
|
||||
title: "Select Plan",
|
||||
id: "plan",
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
saveRequestOptions: {
|
||||
isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request
|
||||
},
|
||||
modelType: Project,
|
||||
id: "create-project-from",
|
||||
fields: [...fields],
|
||||
formType: FormType.Create,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardProjectPicker;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import SearchBox from 'CommonUI/src/Components/Header/SearchBox';
|
||||
import Project from 'Model/Models/Project';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import SearchBox from "CommonUI/src/Components/Header/SearchBox";
|
||||
import Project from "Model/Models/Project";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
onChange: (search: string) => void;
|
||||
selectedProject: Project | null;
|
||||
onChange: (search: string) => void;
|
||||
selectedProject: Project | null;
|
||||
}
|
||||
|
||||
const Search: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
if (!props.selectedProject) {
|
||||
return <></>;
|
||||
}
|
||||
if (!props.selectedProject) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return <SearchBox key={2} onChange={props.onChange} />;
|
||||
return <SearchBox key={2} onChange={props.onChange} />;
|
||||
};
|
||||
|
||||
export default Search;
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton';
|
||||
import IconDropdownItem from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownItem';
|
||||
import IconDropdownMenu from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu';
|
||||
import { DASHBOARD_URL } from 'CommonUI/src/Config';
|
||||
import BlankProfilePic from 'CommonUI/src/Images/users/blank-profile.svg';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import User from 'CommonUI/src/Utils/User';
|
||||
import React, { FunctionComponent, ReactElement, useState } from 'react';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
|
||||
import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem";
|
||||
import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu";
|
||||
import { DASHBOARD_URL } from "CommonUI/src/Config";
|
||||
import BlankProfilePic from "CommonUI/src/Images/users/blank-profile.svg";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import User from "CommonUI/src/Utils/User";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
|
||||
const DashboardUserProfile: FunctionComponent = (): ReactElement => {
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderIconDropdownButton
|
||||
iconImageUrl={BlankProfilePic}
|
||||
name="User Profile"
|
||||
showDropdown={isDropdownVisible}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
>
|
||||
<IconDropdownMenu>
|
||||
{User.isMasterAdmin() ? (
|
||||
<IconDropdownItem
|
||||
title="Exit Admin"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}}
|
||||
icon={IconProp.ExternalLink}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
return (
|
||||
<>
|
||||
<HeaderIconDropdownButton
|
||||
iconImageUrl={BlankProfilePic}
|
||||
name="User Profile"
|
||||
showDropdown={isDropdownVisible}
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(true);
|
||||
}}
|
||||
>
|
||||
<IconDropdownMenu>
|
||||
{User.isMasterAdmin() ? (
|
||||
<IconDropdownItem
|
||||
title="Exit Admin"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
Navigation.navigate(DASHBOARD_URL);
|
||||
}}
|
||||
icon={IconProp.ExternalLink}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<IconDropdownItem
|
||||
title="Log out"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
url={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.LOGOUT] as Route
|
||||
)}
|
||||
icon={IconProp.Logout}
|
||||
/>
|
||||
</IconDropdownMenu>
|
||||
</HeaderIconDropdownButton>
|
||||
</>
|
||||
);
|
||||
<IconDropdownItem
|
||||
title="Log out"
|
||||
onClick={() => {
|
||||
setIsDropdownVisible(false);
|
||||
}}
|
||||
url={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.LOGOUT] as Route,
|
||||
)}
|
||||
icon={IconProp.Logout}
|
||||
/>
|
||||
</IconDropdownMenu>
|
||||
</HeaderIconDropdownButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardUserProfile;
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import Footer from '../Footer/Footer';
|
||||
import Header from '../Header/Header';
|
||||
import NavBar from '../NavBar/NavBar';
|
||||
import MasterPage from 'CommonUI/src/Components/MasterPage/MasterPage';
|
||||
import TopAlert from 'CommonUI/src/Components/TopAlert/TopAlert';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import Footer from "../Footer/Footer";
|
||||
import Header from "../Header/Header";
|
||||
import NavBar from "../NavBar/NavBar";
|
||||
import MasterPage from "CommonUI/src/Components/MasterPage/MasterPage";
|
||||
import TopAlert from "CommonUI/src/Components/TopAlert/TopAlert";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
export interface ComponentProps {
|
||||
children: ReactElement | Array<ReactElement>;
|
||||
children: ReactElement | Array<ReactElement>;
|
||||
}
|
||||
|
||||
const DashboardMasterPage: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
return (
|
||||
<div>
|
||||
<TopAlert
|
||||
title="OneUptime Admin Dashboard"
|
||||
description="You can perform your OneUptime server related tasks on this dashboard."
|
||||
/>
|
||||
<MasterPage
|
||||
footer={<Footer />}
|
||||
header={<Header />}
|
||||
navBar={<NavBar />}
|
||||
isLoading={false}
|
||||
error={''}
|
||||
className="flex flex-col h-screen justify-between"
|
||||
>
|
||||
{props.children}
|
||||
</MasterPage>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<TopAlert
|
||||
title="OneUptime Admin Dashboard"
|
||||
description="You can perform your OneUptime server related tasks on this dashboard."
|
||||
/>
|
||||
<MasterPage
|
||||
footer={<Footer />}
|
||||
header={<Header />}
|
||||
navBar={<NavBar />}
|
||||
isLoading={false}
|
||||
error={""}
|
||||
className="flex flex-col h-screen justify-between"
|
||||
>
|
||||
{props.children}
|
||||
</MasterPage>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardMasterPage;
|
||||
|
||||
@@ -1,39 +1,37 @@
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import NavBar from 'CommonUI/src/Components/Navbar/NavBar';
|
||||
import NavBarItem from 'CommonUI/src/Components/Navbar/NavBarItem';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import NavBar from "CommonUI/src/Components/Navbar/NavBar";
|
||||
import NavBarItem from "CommonUI/src/Components/Navbar/NavBarItem";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const DashboardNavbar: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<NavBar>
|
||||
<NavBarItem
|
||||
title="Users"
|
||||
icon={IconProp.User}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USERS] as Route
|
||||
)}
|
||||
></NavBarItem>
|
||||
return (
|
||||
<NavBar>
|
||||
<NavBarItem
|
||||
title="Users"
|
||||
icon={IconProp.User}
|
||||
route={RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route)}
|
||||
></NavBarItem>
|
||||
|
||||
<NavBarItem
|
||||
title="Projects"
|
||||
icon={IconProp.Folder}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.PROJECTS] as Route
|
||||
)}
|
||||
></NavBarItem>
|
||||
<NavBarItem
|
||||
title="Projects"
|
||||
icon={IconProp.Folder}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.PROJECTS] as Route,
|
||||
)}
|
||||
></NavBarItem>
|
||||
|
||||
<NavBarItem
|
||||
title="Settings"
|
||||
icon={IconProp.Settings}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
)}
|
||||
></NavBarItem>
|
||||
</NavBar>
|
||||
);
|
||||
<NavBarItem
|
||||
title="Settings"
|
||||
icon={IconProp.Settings}
|
||||
route={RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
)}
|
||||
></NavBarItem>
|
||||
</NavBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardNavbar;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import App from './App';
|
||||
import Telemetry from 'CommonUI/src/Utils/Telemetry';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import App from "./App";
|
||||
import Telemetry from "CommonUI/src/Utils/Telemetry";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
Telemetry.init({
|
||||
serviceName: 'AdminDashboard',
|
||||
serviceName: "AdminDashboard",
|
||||
});
|
||||
|
||||
const root: any = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
document.getElementById("root") as HTMLElement,
|
||||
);
|
||||
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>,
|
||||
);
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap from '../../Utils/RouteMap';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap from "../../Utils/RouteMap";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
||||
|
||||
const Init: FunctionComponent = (): ReactElement => {
|
||||
useEffect(() => {
|
||||
Navigation.navigate(RouteMap[PageMap.USERS]!, {
|
||||
forceNavigate: true,
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
Navigation.navigate(RouteMap[PageMap.USERS]!, {
|
||||
forceNavigate: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Page title={''} breadcrumbLinks={[]}>
|
||||
<PageLoader isVisible={true} />
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page title={""} breadcrumbLinks={[]}>
|
||||
<PageLoader isVisible={true} />
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Init;
|
||||
|
||||
@@ -1,53 +1,49 @@
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import { ACCOUNTS_URL } from 'CommonUI/src/Config';
|
||||
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import UserUtil from 'CommonUI/src/Utils/User';
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import { ACCOUNTS_URL } from "CommonUI/src/Config";
|
||||
import UiAnalytics from "CommonUI/src/Utils/Analytics";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import UserUtil from "CommonUI/src/Utils/User";
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
||||
|
||||
const Logout: FunctionComponent = (): ReactElement => {
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
const logout: PromiseVoidFunction = async (): Promise<void> => {
|
||||
UiAnalytics.logout();
|
||||
await UserUtil.logout();
|
||||
Navigation.navigate(ACCOUNTS_URL);
|
||||
};
|
||||
const logout: PromiseVoidFunction = async (): Promise<void> => {
|
||||
UiAnalytics.logout();
|
||||
await UserUtil.logout();
|
||||
Navigation.navigate(ACCOUNTS_URL);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
logout().catch((error: Error) => {
|
||||
setError(error.message || error.toString());
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
logout().catch((error: Error) => {
|
||||
setError(error.message || error.toString());
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Logout'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.INIT] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Logout',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.LOGOUT] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{!error ? <PageLoader isVisible={true} /> : <></>}
|
||||
{error ? <ErrorMessage error={error} /> : <></>}
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page
|
||||
title={"Logout"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.INIT] as Route),
|
||||
},
|
||||
{
|
||||
title: "Logout",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.LOGOUT] as Route),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{!error ? <PageLoader isVisible={true} /> : <></>}
|
||||
{error ? <ErrorMessage error={error} /> : <></>}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logout;
|
||||
|
||||
@@ -1,263 +1,251 @@
|
||||
import AdminModelAPI from '../../Utils/ModelAPI';
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
|
||||
import Field from 'CommonUI/src/Components/Forms/Types/Field';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import { RadioButton } from 'CommonUI/src/Components/RadioButtons/GroupRadioButtons';
|
||||
import Toggle from 'CommonUI/src/Components/Toggle/Toggle';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import { BILLING_ENABLED, getAllEnvVars } from 'CommonUI/src/Config';
|
||||
import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import Project from 'Model/Models/Project';
|
||||
import User from 'Model/Models/User';
|
||||
import AdminModelAPI from "../../Utils/ModelAPI";
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan";
|
||||
import Field from "CommonUI/src/Components/Forms/Types/Field";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons";
|
||||
import Toggle from "CommonUI/src/Components/Toggle/Toggle";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config";
|
||||
import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import Project from "Model/Models/Project";
|
||||
import User from "Model/Models/User";
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
const Projects: FunctionComponent = (): ReactElement => {
|
||||
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
|
||||
useState<boolean>(true);
|
||||
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
|
||||
useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
refreshFields();
|
||||
}, [isSubscriptionPlanYearly]);
|
||||
useEffect(() => {
|
||||
refreshFields();
|
||||
}, [isSubscriptionPlanYearly]);
|
||||
|
||||
const refreshFields: VoidFunction = (): void => {
|
||||
let formFields: Array<Field<Project>> = [
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 4,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'My Project',
|
||||
description: 'Pick a friendly name.',
|
||||
title: 'Project Name',
|
||||
required: true,
|
||||
stepId: BILLING_ENABLED ? 'basic' : undefined,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdByUser: true,
|
||||
},
|
||||
title: 'Owner',
|
||||
description:
|
||||
'Who would you like the owner of this project to be? If you leave this blank - you will be the owner of the project',
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
stepId: BILLING_ENABLED ? 'basic' : undefined,
|
||||
dropdownModal: {
|
||||
type: User,
|
||||
labelField: 'email',
|
||||
valueField: '_id',
|
||||
},
|
||||
},
|
||||
];
|
||||
const refreshFields: VoidFunction = (): void => {
|
||||
let formFields: Array<Field<Project>> = [
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
validation: {
|
||||
minLength: 4,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "My Project",
|
||||
description: "Pick a friendly name.",
|
||||
title: "Project Name",
|
||||
required: true,
|
||||
stepId: BILLING_ENABLED ? "basic" : undefined,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdByUser: true,
|
||||
},
|
||||
title: "Owner",
|
||||
description:
|
||||
"Who would you like the owner of this project to be? If you leave this blank - you will be the owner of the project",
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
stepId: BILLING_ENABLED ? "basic" : undefined,
|
||||
dropdownModal: {
|
||||
type: User,
|
||||
labelField: "email",
|
||||
valueField: "_id",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = [
|
||||
...formFields,
|
||||
{
|
||||
field: {
|
||||
paymentProviderPlanId: true,
|
||||
},
|
||||
stepId: 'plan',
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
footerElement: getFooter(),
|
||||
fieldType: FormFieldSchemaType.RadioButton,
|
||||
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
|
||||
getAllEnvVars()
|
||||
).map((plan: SubscriptionPlan): RadioButton => {
|
||||
let description: string = plan.isCustomPricing()
|
||||
? `Our sales team will contact you soon.`
|
||||
: `Billed ${
|
||||
isSubscriptionPlanYearly
|
||||
? 'yearly'
|
||||
: 'monthly'
|
||||
}. ${
|
||||
plan.getTrialPeriod() > 0
|
||||
? `Free ${plan.getTrialPeriod()} days trial.`
|
||||
: ''
|
||||
}`;
|
||||
if (BILLING_ENABLED) {
|
||||
formFields = [
|
||||
...formFields,
|
||||
{
|
||||
field: {
|
||||
paymentProviderPlanId: true,
|
||||
},
|
||||
stepId: "plan",
|
||||
validation: {
|
||||
minLength: 6,
|
||||
},
|
||||
footerElement: getFooter(),
|
||||
fieldType: FormFieldSchemaType.RadioButton,
|
||||
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
|
||||
getAllEnvVars(),
|
||||
).map((plan: SubscriptionPlan): RadioButton => {
|
||||
let description: string = plan.isCustomPricing()
|
||||
? `Our sales team will contact you soon.`
|
||||
: `Billed ${isSubscriptionPlanYearly ? "yearly" : "monthly"}. ${
|
||||
plan.getTrialPeriod() > 0
|
||||
? `Free ${plan.getTrialPeriod()} days trial.`
|
||||
: ""
|
||||
}`;
|
||||
|
||||
if (
|
||||
isSubscriptionPlanYearly &&
|
||||
plan.getYearlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = 'This plan is free, forever. ';
|
||||
}
|
||||
if (
|
||||
isSubscriptionPlanYearly &&
|
||||
plan.getYearlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = "This plan is free, forever. ";
|
||||
}
|
||||
|
||||
if (
|
||||
!isSubscriptionPlanYearly &&
|
||||
plan.getMonthlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = 'This plan is free, forever. ';
|
||||
}
|
||||
if (
|
||||
!isSubscriptionPlanYearly &&
|
||||
plan.getMonthlySubscriptionAmountInUSD() === 0
|
||||
) {
|
||||
description = "This plan is free, forever. ";
|
||||
}
|
||||
|
||||
return {
|
||||
value: isSubscriptionPlanYearly
|
||||
? plan.getYearlyPlanId()
|
||||
: plan.getMonthlyPlanId(),
|
||||
title: plan.getName(),
|
||||
description: description,
|
||||
sideTitle: plan.isCustomPricing()
|
||||
? 'Custom Price'
|
||||
: isSubscriptionPlanYearly
|
||||
? '$' +
|
||||
plan
|
||||
.getYearlySubscriptionAmountInUSD()
|
||||
.toString() +
|
||||
'/mo billed yearly'
|
||||
: '$' +
|
||||
plan
|
||||
.getMonthlySubscriptionAmountInUSD()
|
||||
.toString(),
|
||||
sideDescription: plan.isCustomPricing()
|
||||
? ''
|
||||
: isSubscriptionPlanYearly
|
||||
? `~ $${
|
||||
plan.getYearlySubscriptionAmountInUSD() *
|
||||
12
|
||||
} per user / year`
|
||||
: `/month per user`,
|
||||
};
|
||||
}),
|
||||
title: 'Please select a plan.',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
paymentProviderPromoCode: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: 'Promo Code (Optional)',
|
||||
description: 'If you have a coupon code, enter it here.',
|
||||
title: 'Promo Code',
|
||||
required: false,
|
||||
stepId: 'plan',
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
return {
|
||||
value: isSubscriptionPlanYearly
|
||||
? plan.getYearlyPlanId()
|
||||
: plan.getMonthlyPlanId(),
|
||||
title: plan.getName(),
|
||||
description: description,
|
||||
sideTitle: plan.isCustomPricing()
|
||||
? "Custom Price"
|
||||
: isSubscriptionPlanYearly
|
||||
? "$" +
|
||||
plan.getYearlySubscriptionAmountInUSD().toString() +
|
||||
"/mo billed yearly"
|
||||
: "$" + plan.getMonthlySubscriptionAmountInUSD().toString(),
|
||||
sideDescription: plan.isCustomPricing()
|
||||
? ""
|
||||
: isSubscriptionPlanYearly
|
||||
? `~ $${
|
||||
plan.getYearlySubscriptionAmountInUSD() * 12
|
||||
} per user / year`
|
||||
: `/month per user`,
|
||||
};
|
||||
}),
|
||||
title: "Please select a plan.",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
paymentProviderPromoCode: true,
|
||||
},
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
placeholder: "Promo Code (Optional)",
|
||||
description: "If you have a coupon code, enter it here.",
|
||||
title: "Promo Code",
|
||||
required: false,
|
||||
stepId: "plan",
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setFields(formFields);
|
||||
};
|
||||
setFields(formFields);
|
||||
};
|
||||
|
||||
const [fields, setFields] = useState<Array<Field<Project>>>([]);
|
||||
const [fields, setFields] = useState<Array<Field<Project>>>([]);
|
||||
|
||||
const getFooter: GetReactElementFunction = (): ReactElement => {
|
||||
if (!BILLING_ENABLED) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Toggle
|
||||
title="Yearly Plan"
|
||||
value={isSubscriptionPlanYearly}
|
||||
description="(Save 20%)"
|
||||
onChange={(value: boolean) => {
|
||||
setIsSubscriptionPlanYearly(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const getFooter: GetReactElementFunction = (): ReactElement => {
|
||||
if (!BILLING_ENABLED) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Projects'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Projects',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.PROJECTS] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ModelTable<Project>
|
||||
modelType={Project}
|
||||
modelAPI={AdminModelAPI}
|
||||
id="projects-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={true}
|
||||
name="Projects"
|
||||
isViewable={false}
|
||||
cardProps={{
|
||||
title: 'Projects',
|
||||
description: 'Here is a list of proejcts in OneUptime.',
|
||||
}}
|
||||
showViewIdButton={true}
|
||||
formSteps={
|
||||
BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
title: 'Basic',
|
||||
id: 'basic',
|
||||
},
|
||||
{
|
||||
title: 'Select Plan',
|
||||
id: 'plan',
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
noItemsMessage={'No projects found.'}
|
||||
formFields={fields}
|
||||
showRefreshButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
<Toggle
|
||||
title="Yearly Plan"
|
||||
value={isSubscriptionPlanYearly}
|
||||
description="(Save 20%)"
|
||||
onChange={(value: boolean) => {
|
||||
setIsSubscriptionPlanYearly(value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={"Projects"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Projects",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.PROJECTS] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ModelTable<Project>
|
||||
modelType={Project}
|
||||
modelAPI={AdminModelAPI}
|
||||
id="projects-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={true}
|
||||
name="Projects"
|
||||
isViewable={false}
|
||||
cardProps={{
|
||||
title: "Projects",
|
||||
description: "Here is a list of proejcts in OneUptime.",
|
||||
}}
|
||||
showViewIdButton={true}
|
||||
formSteps={
|
||||
BILLING_ENABLED
|
||||
? [
|
||||
{
|
||||
title: "Basic",
|
||||
id: "basic",
|
||||
},
|
||||
{
|
||||
title: "Select Plan",
|
||||
id: "plan",
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
noItemsMessage={"No projects found."}
|
||||
formFields={fields}
|
||||
showRefreshButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Projects;
|
||||
|
||||
@@ -1,102 +1,100 @@
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import GlobalConfig from 'Model/Models/GlobalConfig';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import GlobalConfig from "Model/Models/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'API Key',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_HOST] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="API Key Settings"
|
||||
cardProps={{
|
||||
title: 'Master API Key Settings',
|
||||
description:
|
||||
'This API key has root access to all the resources in all the projects on OneUptime.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit API Key Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
masterApiKey: true,
|
||||
},
|
||||
title: 'Master API Key',
|
||||
fieldType: FormFieldSchemaType.ObjectID,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isMasterApiKeyEnabled: true,
|
||||
},
|
||||
title: 'Enabled',
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
masterApiKey: true,
|
||||
},
|
||||
title: 'Master API Key',
|
||||
description:
|
||||
'This API key has root access to all the resources in all the projects on OneUptime.',
|
||||
fieldType: FieldType.HiddenText,
|
||||
opts: {
|
||||
isCopyable: true,
|
||||
},
|
||||
placeholder: 'API Key not generated yet.',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isMasterApiKeyEnabled: true,
|
||||
},
|
||||
title: 'Enabled',
|
||||
description:
|
||||
'Enable or disable the master API key. If disabled, all requests using this key will fail.',
|
||||
fieldType: FieldType.Boolean,
|
||||
placeholder: 'Not Enabled',
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "API Key",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_HOST] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="API Key Settings"
|
||||
cardProps={{
|
||||
title: "Master API Key Settings",
|
||||
description:
|
||||
"This API key has root access to all the resources in all the projects on OneUptime.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit API Key Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
masterApiKey: true,
|
||||
},
|
||||
title: "Master API Key",
|
||||
fieldType: FormFieldSchemaType.ObjectID,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isMasterApiKeyEnabled: true,
|
||||
},
|
||||
title: "Enabled",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
masterApiKey: true,
|
||||
},
|
||||
title: "Master API Key",
|
||||
description:
|
||||
"This API key has root access to all the resources in all the projects on OneUptime.",
|
||||
fieldType: FieldType.HiddenText,
|
||||
opts: {
|
||||
isCopyable: true,
|
||||
},
|
||||
placeholder: "API Key not generated yet.",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isMasterApiKeyEnabled: true,
|
||||
},
|
||||
title: "Enabled",
|
||||
description:
|
||||
"Enable or disable the master API key. If disabled, all requests using this key will fail.",
|
||||
fieldType: FieldType.Boolean,
|
||||
placeholder: "Not Enabled",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,83 +1,80 @@
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import GlobalConfig from 'Model/Models/GlobalConfig';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import GlobalConfig from "Model/Models/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Authentication',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="Authentication Settings"
|
||||
cardProps={{
|
||||
title: 'Authentication Settings',
|
||||
description:
|
||||
'Authentication Settings for this OneUptime Server instance.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
disableSignup: true,
|
||||
},
|
||||
title: 'Disable Sign Up',
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
description:
|
||||
'Should we disable sign up of new users to OneUptime?',
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
disableSignup: true,
|
||||
},
|
||||
fieldType: FieldType.Boolean,
|
||||
title: 'Disable Sign Up',
|
||||
placeholder: 'No',
|
||||
description:
|
||||
'Should we disable sign up of new users to OneUptime?',
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Authentication",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="Authentication Settings"
|
||||
cardProps={{
|
||||
title: "Authentication Settings",
|
||||
description:
|
||||
"Authentication Settings for this OneUptime Server instance.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Settings"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
disableSignup: true,
|
||||
},
|
||||
title: "Disable Sign Up",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
required: false,
|
||||
description: "Should we disable sign up of new users to OneUptime?",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
disableSignup: true,
|
||||
},
|
||||
fieldType: FieldType.Boolean,
|
||||
title: "Disable Sign Up",
|
||||
placeholder: "No",
|
||||
description:
|
||||
"Should we disable sign up of new users to OneUptime?",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,119 +1,114 @@
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import GlobalConfig from 'Model/Models/GlobalConfig';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import GlobalConfig from "Model/Models/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Calls and SMS',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="Call and SMS Settings"
|
||||
cardProps={{
|
||||
title: 'Twilio Config',
|
||||
description: 'This will be used to make Call and send SMS.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Twilio Config"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
twilioAccountSID: true,
|
||||
},
|
||||
title: 'Twilio Account SID',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
'You can find this in your Twilio console.',
|
||||
placeholder: '',
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioAuthToken: true,
|
||||
},
|
||||
title: 'Twilio Auth Token',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
'You can find this in your Twilio console.',
|
||||
placeholder: '',
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: 'Twilio Phone Number',
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: true,
|
||||
description:
|
||||
'You can find this in your Twilio console.',
|
||||
placeholder: '',
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
twilioAccountSID: true,
|
||||
},
|
||||
title: 'Twilio Account SID',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: 'Twilio Phone Number',
|
||||
fieldType: FieldType.Phone,
|
||||
placeholder: 'None',
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Calls and SMS",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
<CardModelDetail
|
||||
name="Call and SMS Settings"
|
||||
cardProps={{
|
||||
title: "Twilio Config",
|
||||
description: "This will be used to make Call and send SMS.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Twilio Config"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
twilioAccountSID: true,
|
||||
},
|
||||
title: "Twilio Account SID",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description: "You can find this in your Twilio console.",
|
||||
placeholder: "",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioAuthToken: true,
|
||||
},
|
||||
title: "Twilio Auth Token",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description: "You can find this in your Twilio console.",
|
||||
placeholder: "",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: "Twilio Phone Number",
|
||||
fieldType: FormFieldSchemaType.Phone,
|
||||
required: true,
|
||||
description: "You can find this in your Twilio console.",
|
||||
placeholder: "",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
twilioAccountSID: true,
|
||||
},
|
||||
title: "Twilio Account SID",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
title: "Twilio Phone Number",
|
||||
fieldType: FieldType.Phone,
|
||||
placeholder: "None",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,427 +1,419 @@
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import { Green, Red } from 'Common/Types/BrandColors';
|
||||
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
|
||||
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import Pill from 'CommonUI/src/Components/Pill/Pill';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
|
||||
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import GlobalConfig, { EmailServerType } from 'Model/Models/GlobalConfig';
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import { Green, Red } from "Common/Types/BrandColors";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
|
||||
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import Pill from "CommonUI/src/Components/Pill/Pill";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import DropdownUtil from "CommonUI/src/Utils/Dropdown";
|
||||
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
import GlobalConfig, { EmailServerType } from "Model/Models/GlobalConfig";
|
||||
import React, { FunctionComponent, ReactElement, useEffect } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
const [emailServerType, setemailServerType] =
|
||||
React.useState<EmailServerType>(EmailServerType.Internal);
|
||||
const [emailServerType, setemailServerType] = React.useState<EmailServerType>(
|
||||
EmailServerType.Internal,
|
||||
);
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(true);
|
||||
const [isLoading, setIsLoading] = React.useState<boolean>(true);
|
||||
|
||||
const [error, setError] = React.useState<string>('');
|
||||
const [error, setError] = React.useState<string>("");
|
||||
|
||||
const fetchItem: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
const fetchItem: PromiseVoidFunction = async (): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await ModelAPI.getItem<GlobalConfig>({
|
||||
modelType: GlobalConfig,
|
||||
id: ObjectID.getZeroObjectID(),
|
||||
select: {
|
||||
_id: true,
|
||||
emailServerType: true,
|
||||
},
|
||||
});
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await ModelAPI.getItem<GlobalConfig>({
|
||||
modelType: GlobalConfig,
|
||||
id: ObjectID.getZeroObjectID(),
|
||||
select: {
|
||||
_id: true,
|
||||
emailServerType: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (globalConfig) {
|
||||
setemailServerType(
|
||||
globalConfig.emailServerType || EmailServerType.Internal
|
||||
);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchItem().catch((err: Error) => {
|
||||
setError(err.message);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
if (globalConfig) {
|
||||
setemailServerType(
|
||||
globalConfig.emailServerType || EmailServerType.Internal,
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage error={error} />;
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
useEffect(() => {
|
||||
fetchItem().catch((err: Error) => {
|
||||
setError(err.message);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Email Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_SMTP] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
|
||||
<CardModelDetail
|
||||
name="Admin Notification Email"
|
||||
cardProps={{
|
||||
title: "Admin Notification Email",
|
||||
description:
|
||||
"Enter the email address where you would like to receive admin-level notifications.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Email"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
adminNotificationEmail: true,
|
||||
},
|
||||
title: "Admin Notification Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: false,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
adminNotificationEmail: true,
|
||||
},
|
||||
title: "Admin Notification Email",
|
||||
fieldType: FieldType.Email,
|
||||
placeholder: "None",
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
|
||||
<CardModelDetail
|
||||
name="Internal SMTP Settings"
|
||||
cardProps={{
|
||||
title: "Email Server Settings",
|
||||
description:
|
||||
"Pick which email server you would like to use to send emails.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Server"
|
||||
onSaveSuccess={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
emailServerType: true,
|
||||
},
|
||||
title: "Email Server Type",
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
dropdownOptions:
|
||||
DropdownUtil.getDropdownOptionsFromEnum(EmailServerType),
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
emailServerType: true,
|
||||
},
|
||||
title: "Email Server Type",
|
||||
fieldType: FieldType.Text,
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
|
||||
{emailServerType === EmailServerType.CustomSMTP ? (
|
||||
<CardModelDetail
|
||||
name="Host Settings"
|
||||
cardProps={{
|
||||
title: "Custom Email and SMTP Settings",
|
||||
description:
|
||||
"If you have not enabled Internal SMTP server to send emails. Please configure your SMTP server here.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit SMTP Config"
|
||||
formSteps={[
|
||||
{
|
||||
title: "SMTP Server",
|
||||
id: "server-info",
|
||||
},
|
||||
{
|
||||
title: "Authentication",
|
||||
id: "authentication",
|
||||
},
|
||||
{
|
||||
title: "Email",
|
||||
id: "email-info",
|
||||
},
|
||||
]}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
smtpHost: true,
|
||||
},
|
||||
title: "Hostname",
|
||||
stepId: "server-info",
|
||||
fieldType: FormFieldSchemaType.Hostname,
|
||||
required: true,
|
||||
placeholder: "smtp.server.com",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPort: true,
|
||||
},
|
||||
title: "Port",
|
||||
stepId: "server-info",
|
||||
fieldType: FormFieldSchemaType.Port,
|
||||
required: true,
|
||||
placeholder: "587",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isSMTPSecure: true,
|
||||
},
|
||||
title: "Use SSL / TLS",
|
||||
stepId: "server-info",
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
description:
|
||||
"If you use port 465, please enable this. Do not enable this if you use port 587.",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpUsername: true,
|
||||
},
|
||||
title: "Username",
|
||||
stepId: "authentication",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
placeholder: "emailuser",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPassword: true,
|
||||
},
|
||||
title: "Password",
|
||||
stepId: "authentication",
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: false,
|
||||
placeholder: "Password",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromEmail: true,
|
||||
},
|
||||
title: "Email From",
|
||||
stepId: "email-info",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
description:
|
||||
"This is the display email your team and customers see, when they receive emails from OneUptime.",
|
||||
placeholder: "email@company.com",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromName: true,
|
||||
},
|
||||
title: "From Name",
|
||||
stepId: "email-info",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
"This is the display name your team and customers see, when they receive emails from OneUptime.",
|
||||
placeholder: "Company, Inc.",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
smtpHost: true,
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
title: "SMTP Host",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPort: true,
|
||||
},
|
||||
{
|
||||
title: 'Email Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_SMTP] as Route
|
||||
),
|
||||
title: "SMTP Port",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpUsername: true,
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
title: "SMTP Username",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromEmail: true,
|
||||
},
|
||||
title: "SMTP Email",
|
||||
placeholder: "None",
|
||||
fieldType: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromName: true,
|
||||
},
|
||||
title: "SMTP From Name",
|
||||
placeholder: "None",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isSMTPSecure: true,
|
||||
},
|
||||
title: "Use SSL/TLS",
|
||||
placeholder: "No",
|
||||
fieldType: FieldType.Boolean,
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<CardModelDetail
|
||||
name="Admin Notification Email"
|
||||
cardProps={{
|
||||
title: 'Admin Notification Email',
|
||||
description:
|
||||
'Enter the email address where you would like to receive admin-level notifications.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Email"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
adminNotificationEmail: true,
|
||||
},
|
||||
title: 'Admin Notification Email',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: false,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
adminNotificationEmail: true,
|
||||
},
|
||||
title: 'Admin Notification Email',
|
||||
fieldType: FieldType.Email,
|
||||
placeholder: 'None',
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
{emailServerType === EmailServerType.Sendgrid ? (
|
||||
<CardModelDetail<GlobalConfig>
|
||||
name="Sendgrid Settings"
|
||||
cardProps={{
|
||||
title: "Sendgrid Settings",
|
||||
description:
|
||||
"Enter your Sendgrid API key to send emails through Sendgrid.",
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit API Key"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
sendgridApiKey: true,
|
||||
},
|
||||
title: "Sendgrid API Key",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "Sendgrid API Key",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
sendgridFromEmail: true,
|
||||
},
|
||||
title: "From Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
placeholder: "email@yourcompany.com",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
sendgridFromName: true,
|
||||
},
|
||||
title: "From Name",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "Acme, Inc.",
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: "model-detail-global-config",
|
||||
selectMoreFields: {
|
||||
sendgridFromEmail: true,
|
||||
sendgridFromName: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
sendgridApiKey: true,
|
||||
},
|
||||
title: "",
|
||||
placeholder: "None",
|
||||
getElement: (item: GlobalConfig) => {
|
||||
if (
|
||||
item["sendgridApiKey"] &&
|
||||
item["sendgridFromEmail"] &&
|
||||
item["sendgridFromName"]
|
||||
) {
|
||||
return <Pill text="Enabled" color={Green} />;
|
||||
} else if (!item["sendgridApiKey"]) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the API key."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
} else if (!item["sendgridFromEmail"]) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the From Email."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
} else if (!item["sendgridFromName"]) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the From Name."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
<CardModelDetail
|
||||
name="Internal SMTP Settings"
|
||||
cardProps={{
|
||||
title: 'Email Server Settings',
|
||||
description:
|
||||
'Pick which email server you would like to use to send emails.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit Server"
|
||||
onSaveSuccess={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
emailServerType: true,
|
||||
},
|
||||
title: 'Email Server Type',
|
||||
fieldType: FormFieldSchemaType.Dropdown,
|
||||
dropdownOptions:
|
||||
DropdownUtil.getDropdownOptionsFromEnum(
|
||||
EmailServerType
|
||||
),
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
emailServerType: true,
|
||||
},
|
||||
title: 'Email Server Type',
|
||||
fieldType: FieldType.Text,
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
|
||||
{emailServerType === EmailServerType.CustomSMTP ? (
|
||||
<CardModelDetail
|
||||
name="Host Settings"
|
||||
cardProps={{
|
||||
title: 'Custom Email and SMTP Settings',
|
||||
description:
|
||||
'If you have not enabled Internal SMTP server to send emails. Please configure your SMTP server here.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit SMTP Config"
|
||||
formSteps={[
|
||||
{
|
||||
title: 'SMTP Server',
|
||||
id: 'server-info',
|
||||
},
|
||||
{
|
||||
title: 'Authentication',
|
||||
id: 'authentication',
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
id: 'email-info',
|
||||
},
|
||||
]}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
smtpHost: true,
|
||||
},
|
||||
title: 'Hostname',
|
||||
stepId: 'server-info',
|
||||
fieldType: FormFieldSchemaType.Hostname,
|
||||
required: true,
|
||||
placeholder: 'smtp.server.com',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPort: true,
|
||||
},
|
||||
title: 'Port',
|
||||
stepId: 'server-info',
|
||||
fieldType: FormFieldSchemaType.Port,
|
||||
required: true,
|
||||
placeholder: '587',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isSMTPSecure: true,
|
||||
},
|
||||
title: 'Use SSL / TLS',
|
||||
stepId: 'server-info',
|
||||
fieldType: FormFieldSchemaType.Toggle,
|
||||
description:
|
||||
'If you use port 465, please enable this. Do not enable this if you use port 587.',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpUsername: true,
|
||||
},
|
||||
title: 'Username',
|
||||
stepId: 'authentication',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: false,
|
||||
placeholder: 'emailuser',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPassword: true,
|
||||
},
|
||||
title: 'Password',
|
||||
stepId: 'authentication',
|
||||
fieldType: FormFieldSchemaType.EncryptedText,
|
||||
required: false,
|
||||
placeholder: 'Password',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromEmail: true,
|
||||
},
|
||||
title: 'Email From',
|
||||
stepId: 'email-info',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
description:
|
||||
'This is the display email your team and customers see, when they receive emails from OneUptime.',
|
||||
placeholder: 'email@company.com',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromName: true,
|
||||
},
|
||||
title: 'From Name',
|
||||
stepId: 'email-info',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
description:
|
||||
'This is the display name your team and customers see, when they receive emails from OneUptime.',
|
||||
placeholder: 'Company, Inc.',
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
smtpHost: true,
|
||||
},
|
||||
title: 'SMTP Host',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpPort: true,
|
||||
},
|
||||
title: 'SMTP Port',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpUsername: true,
|
||||
},
|
||||
title: 'SMTP Username',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromEmail: true,
|
||||
},
|
||||
title: 'SMTP Email',
|
||||
placeholder: 'None',
|
||||
fieldType: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
smtpFromName: true,
|
||||
},
|
||||
title: 'SMTP From Name',
|
||||
placeholder: 'None',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isSMTPSecure: true,
|
||||
},
|
||||
title: 'Use SSL/TLS',
|
||||
placeholder: 'No',
|
||||
fieldType: FieldType.Boolean,
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{emailServerType === EmailServerType.Sendgrid ? (
|
||||
<CardModelDetail<GlobalConfig>
|
||||
name="Sendgrid Settings"
|
||||
cardProps={{
|
||||
title: 'Sendgrid Settings',
|
||||
description:
|
||||
'Enter your Sendgrid API key to send emails through Sendgrid.',
|
||||
}}
|
||||
isEditable={true}
|
||||
editButtonText="Edit API Key"
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
sendgridApiKey: true,
|
||||
},
|
||||
title: 'Sendgrid API Key',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: 'Sendgrid API Key',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
sendgridFromEmail: true,
|
||||
},
|
||||
title: 'From Email',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
placeholder: 'email@yourcompany.com',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
sendgridFromName: true,
|
||||
},
|
||||
title: 'From Name',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: 'Acme, Inc.',
|
||||
},
|
||||
]}
|
||||
modelDetailProps={{
|
||||
modelType: GlobalConfig,
|
||||
id: 'model-detail-global-config',
|
||||
selectMoreFields: {
|
||||
sendgridFromEmail: true,
|
||||
sendgridFromName: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: {
|
||||
sendgridApiKey: true,
|
||||
},
|
||||
title: '',
|
||||
placeholder: 'None',
|
||||
getElement: (item: GlobalConfig) => {
|
||||
if (
|
||||
item['sendgridApiKey'] &&
|
||||
item['sendgridFromEmail'] &&
|
||||
item['sendgridFromName']
|
||||
) {
|
||||
return (
|
||||
<Pill
|
||||
text="Enabled"
|
||||
color={Green}
|
||||
/>
|
||||
);
|
||||
} else if (!item['sendgridApiKey']) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the API key."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
} else if (!item['sendgridFromEmail']) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the From Email."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
} else if (!item['sendgridFromName']) {
|
||||
return (
|
||||
<Pill
|
||||
text="Not Enabled. Please add the From Name."
|
||||
color={Red}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
],
|
||||
modelId: ObjectID.getZeroObjectID(),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,251 +1,243 @@
|
||||
import AdminModelAPI from '../../../Utils/ModelAPI';
|
||||
import PageMap from '../../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
|
||||
import DashboardSideMenu from '../SideMenu';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import IsNull from 'Common/Types/BaseDatabase/IsNull';
|
||||
import { Green, Red } from 'Common/Types/BrandColors';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes';
|
||||
import Banner from 'CommonUI/src/Components/Banner/Banner';
|
||||
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
|
||||
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import ProbeElement from 'CommonUI/src/Components/Probe/Probe';
|
||||
import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import Probe from 'Model/Models/Probe';
|
||||
import React, { FunctionComponent, ReactElement, useState } from 'react';
|
||||
import AdminModelAPI from "../../../Utils/ModelAPI";
|
||||
import PageMap from "../../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
|
||||
import DashboardSideMenu from "../SideMenu";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IsNull from "Common/Types/BaseDatabase/IsNull";
|
||||
import { Green, Red } from "Common/Types/BrandColors";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes";
|
||||
import Banner from "CommonUI/src/Components/Banner/Banner";
|
||||
import { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
|
||||
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import ProbeElement from "CommonUI/src/Components/Probe/Probe";
|
||||
import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import Probe from "Model/Models/Probe";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
|
||||
const Settings: FunctionComponent = (): ReactElement => {
|
||||
const [showKeyModal, setShowKeyModal] = useState<boolean>(false);
|
||||
const [showKeyModal, setShowKeyModal] = useState<boolean>(false);
|
||||
|
||||
const [currentProbe, setCurrentProbe] = useState<Probe | null>(null);
|
||||
const [currentProbe, setCurrentProbe] = useState<Probe | null>(null);
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Admin Settings'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Global Probes',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_PROBES] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
return (
|
||||
<Page
|
||||
title={"Admin Settings"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS] as Route,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Global Probes",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_PROBES] as Route,
|
||||
),
|
||||
},
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
{/* Project Settings View */}
|
||||
|
||||
<Banner
|
||||
openInNewTab={true}
|
||||
title="Need help with setting up Global Probes?"
|
||||
description="Here is a guide which will help you get set up"
|
||||
link={Route.fromString('/docs/probe/custom-probe')}
|
||||
/>
|
||||
<Banner
|
||||
openInNewTab={true}
|
||||
title="Need help with setting up Global Probes?"
|
||||
description="Here is a guide which will help you get set up"
|
||||
link={Route.fromString("/docs/probe/custom-probe")}
|
||||
/>
|
||||
|
||||
<ModelTable<Probe>
|
||||
modelType={Probe}
|
||||
id="probes-table"
|
||||
name="Settings > Global Probes"
|
||||
isDeleteable={true}
|
||||
isEditable={true}
|
||||
isCreateable={true}
|
||||
cardProps={{
|
||||
title: 'Global Probes',
|
||||
description:
|
||||
'Global Probes help you monitor external resources from different locations around the world.',
|
||||
}}
|
||||
query={{
|
||||
projectId: new IsNull(),
|
||||
isGlobalProbe: true,
|
||||
}}
|
||||
modelAPI={AdminModelAPI}
|
||||
noItemsMessage={'No probes found.'}
|
||||
showRefreshButton={true}
|
||||
onBeforeCreate={(item: Probe) => {
|
||||
item.isGlobalProbe = true;
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: 'internal-probe',
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
<ModelTable<Probe>
|
||||
modelType={Probe}
|
||||
id="probes-table"
|
||||
name="Settings > Global Probes"
|
||||
isDeleteable={true}
|
||||
isEditable={true}
|
||||
isCreateable={true}
|
||||
cardProps={{
|
||||
title: "Global Probes",
|
||||
description:
|
||||
"Global Probes help you monitor external resources from different locations around the world.",
|
||||
}}
|
||||
query={{
|
||||
projectId: new IsNull(),
|
||||
isGlobalProbe: true,
|
||||
}}
|
||||
modelAPI={AdminModelAPI}
|
||||
noItemsMessage={"No probes found."}
|
||||
showRefreshButton={true}
|
||||
onBeforeCreate={(item: Probe) => {
|
||||
item.isGlobalProbe = true;
|
||||
return Promise.resolve(item);
|
||||
}}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "internal-probe",
|
||||
validation: {
|
||||
minLength: 2,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: 'Description',
|
||||
fieldType: FormFieldSchemaType.LongText,
|
||||
required: true,
|
||||
placeholder:
|
||||
'This probe is to monitor all the internal services.',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
fieldType: FormFieldSchemaType.LongText,
|
||||
required: true,
|
||||
placeholder: "This probe is to monitor all the internal services.",
|
||||
},
|
||||
|
||||
{
|
||||
field: {
|
||||
iconFile: true,
|
||||
},
|
||||
title: 'Probe Logo',
|
||||
fieldType: FormFieldSchemaType.ImageFile,
|
||||
required: false,
|
||||
placeholder: 'Upload logo',
|
||||
},
|
||||
]}
|
||||
selectMoreFields={{
|
||||
key: true,
|
||||
iconFileId: true,
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
title: 'Show ID and Key',
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
onClick: async (
|
||||
item: Probe,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction
|
||||
) => {
|
||||
try {
|
||||
setCurrentProbe(item);
|
||||
setShowKeyModal(true);
|
||||
{
|
||||
field: {
|
||||
iconFile: true,
|
||||
},
|
||||
title: "Probe Logo",
|
||||
fieldType: FormFieldSchemaType.ImageFile,
|
||||
required: false,
|
||||
placeholder: "Upload logo",
|
||||
},
|
||||
]}
|
||||
selectMoreFields={{
|
||||
key: true,
|
||||
iconFileId: true,
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
title: "Show ID and Key",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
onClick: async (
|
||||
item: Probe,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction,
|
||||
) => {
|
||||
try {
|
||||
setCurrentProbe(item);
|
||||
setShowKeyModal(true);
|
||||
|
||||
onCompleteAction();
|
||||
} catch (err) {
|
||||
onCompleteAction();
|
||||
onError(err as Error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: 'Description',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Name',
|
||||
type: FieldType.Text,
|
||||
onCompleteAction();
|
||||
} catch (err) {
|
||||
onCompleteAction();
|
||||
onError(err as Error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
return <ProbeElement probe={item} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: 'Description',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
lastAlive: true,
|
||||
},
|
||||
title: 'Status',
|
||||
type: FieldType.Text,
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
if (
|
||||
item &&
|
||||
item['lastAlive'] &&
|
||||
OneUptimeDate.getNumberOfMinutesBetweenDates(
|
||||
OneUptimeDate.fromString(item['lastAlive']),
|
||||
OneUptimeDate.getCurrentDate()
|
||||
) < 5
|
||||
) {
|
||||
return (
|
||||
<Statusbubble
|
||||
text={'Connected'}
|
||||
color={Green}
|
||||
shouldAnimate={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
return <ProbeElement probe={item} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: {
|
||||
description: true,
|
||||
},
|
||||
title: "Description",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
lastAlive: true,
|
||||
},
|
||||
title: "Status",
|
||||
type: FieldType.Text,
|
||||
getElement: (item: Probe): ReactElement => {
|
||||
if (
|
||||
item &&
|
||||
item["lastAlive"] &&
|
||||
OneUptimeDate.getNumberOfMinutesBetweenDates(
|
||||
OneUptimeDate.fromString(item["lastAlive"]),
|
||||
OneUptimeDate.getCurrentDate(),
|
||||
) < 5
|
||||
) {
|
||||
return (
|
||||
<Statusbubble
|
||||
text={"Connected"}
|
||||
color={Green}
|
||||
shouldAnimate={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Statusbubble
|
||||
text={'Disconnected'}
|
||||
color={Red}
|
||||
shouldAnimate={false}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showKeyModal && currentProbe ? (
|
||||
<ConfirmModal
|
||||
title={`Probe Key`}
|
||||
description={
|
||||
<div>
|
||||
<span>
|
||||
Here is your probe key. Please keep this a
|
||||
secret.
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>Probe ID: </b>{' '}
|
||||
{currentProbe['_id']?.toString()}
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>Probe Key: </b>{' '}
|
||||
{currentProbe['key']?.toString()}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
submitButtonText={'Close'}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
onSubmit={async () => {
|
||||
setShowKeyModal(false);
|
||||
}}
|
||||
return (
|
||||
<Statusbubble
|
||||
text={"Disconnected"}
|
||||
color={Red}
|
||||
shouldAnimate={false}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{showKeyModal && currentProbe ? (
|
||||
<ConfirmModal
|
||||
title={`Probe Key`}
|
||||
description={
|
||||
<div>
|
||||
<span>Here is your probe key. Please keep this a secret.</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>Probe ID: </b> {currentProbe["_id"]?.toString()}
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
<span>
|
||||
<b>Probe Key: </b> {currentProbe["key"]?.toString()}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
submitButtonText={"Close"}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
onSubmit={async () => {
|
||||
setShowKeyModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import IconProp from 'Common/Types/Icon/IconProp';
|
||||
import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu';
|
||||
import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem';
|
||||
import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection';
|
||||
import React, { ReactElement } from 'react';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import IconProp from "Common/Types/Icon/IconProp";
|
||||
import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu";
|
||||
import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem";
|
||||
import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection";
|
||||
import React, { ReactElement } from "react";
|
||||
|
||||
const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
|
||||
return (
|
||||
<SideMenu>
|
||||
<SideMenuSection title="Basic">
|
||||
{/* <SideMenuItem
|
||||
return (
|
||||
<SideMenu>
|
||||
<SideMenuSection title="Basic">
|
||||
{/* <SideMenuItem
|
||||
link={{
|
||||
title: 'Host',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
@@ -20,62 +20,62 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
|
||||
}}
|
||||
icon={IconProp.Globe}
|
||||
/> */}
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'Authentication',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Lock}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Authentication",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Lock}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Notifications">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'Emails',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_SMTP] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Email}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'Call and SMS',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuSection title="Notifications">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Emails",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_SMTP] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Email}
|
||||
/>
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Call and SMS",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Call}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Monitoring">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'Global Probes',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_PROBES] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Signal}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuSection title="API and Integrations">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'API Key',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_API_KEY] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Code}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
</SideMenu>
|
||||
);
|
||||
<SideMenuSection title="Monitoring">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Global Probes",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_PROBES] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Signal}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
<SideMenuSection title="API and Integrations">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "API Key",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_API_KEY] as Route,
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Code}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
</SideMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardSideMenu;
|
||||
|
||||
@@ -1,222 +1,218 @@
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import { ErrorFunction } from 'Common/Types/FunctionTypes';
|
||||
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
|
||||
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
|
||||
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
|
||||
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import API from 'CommonUI/src/Utils/API/API';
|
||||
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import User from 'Model/Models/User';
|
||||
import React, { FunctionComponent, ReactElement, useState } from 'react';
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import { ErrorFunction } from "Common/Types/FunctionTypes";
|
||||
import { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
|
||||
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
|
||||
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
|
||||
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
|
||||
import Page from "CommonUI/src/Components/Page/Page";
|
||||
import FieldType from "CommonUI/src/Components/Types/FieldType";
|
||||
import API from "CommonUI/src/Utils/API/API";
|
||||
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
import Navigation from "CommonUI/src/Utils/Navigation";
|
||||
import User from "Model/Models/User";
|
||||
import React, { FunctionComponent, ReactElement, useState } from "react";
|
||||
|
||||
const Users: FunctionComponent = (): ReactElement => {
|
||||
const [showConfirmVerifyEmailModal, setShowConfirmVerifyEmailModal] =
|
||||
useState<boolean>(false);
|
||||
const [selectedUser, setSelectedUser] = useState<User | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showConfirmVerifyEmailModal, setShowConfirmVerifyEmailModal] =
|
||||
useState<boolean>(false);
|
||||
const [selectedUser, setSelectedUser] = useState<User | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [isConfimModalLoading, setIsConfirmModalLoading] =
|
||||
useState<boolean>(false);
|
||||
const [isConfimModalLoading, setIsConfirmModalLoading] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [refreshItemsTrigger, setRefreshItemsTrigger] =
|
||||
useState<boolean>(false);
|
||||
const [refreshItemsTrigger, setRefreshItemsTrigger] =
|
||||
useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Users'}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: 'Admin Dashboard',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.HOME] as Route
|
||||
),
|
||||
return (
|
||||
<Page
|
||||
title={"Users"}
|
||||
breadcrumbLinks={[
|
||||
{
|
||||
title: "Admin Dashboard",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
|
||||
},
|
||||
{
|
||||
title: "Users",
|
||||
to: RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ModelTable<User>
|
||||
modelType={User}
|
||||
id="users-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
showViewIdButton={true}
|
||||
refreshToggle={refreshItemsTrigger}
|
||||
isCreateable={true}
|
||||
name="Users"
|
||||
isViewable={false}
|
||||
cardProps={{
|
||||
title: "Users",
|
||||
description: "Here is a list of users in OneUptime.",
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
title: "Verify Email",
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
isVisible: (item: User) => {
|
||||
return !item.isEmailVerified;
|
||||
},
|
||||
onClick: async (
|
||||
item: User,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction,
|
||||
) => {
|
||||
try {
|
||||
setSelectedUser(item);
|
||||
setShowConfirmVerifyEmailModal(true);
|
||||
|
||||
onCompleteAction();
|
||||
} catch (err) {
|
||||
onCompleteAction();
|
||||
onError(err as Error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
noItemsMessage={"No users found."}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: "Email",
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
placeholder: "email@company.com",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: "Password",
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
required: true,
|
||||
placeholder: "Password",
|
||||
},
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Full Name",
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: "John Smith",
|
||||
},
|
||||
]}
|
||||
showRefreshButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Full Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: "Email",
|
||||
type: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: "Full Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: "Email",
|
||||
type: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isEmailVerified: true,
|
||||
},
|
||||
title: "Email Verified",
|
||||
type: FieldType.Boolean,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: "Created At",
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{error ? (
|
||||
<ConfirmModal
|
||||
title={`Error`}
|
||||
description={error}
|
||||
submitButtonText={"Close"}
|
||||
onSubmit={async () => {
|
||||
setError(null);
|
||||
}}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{showConfirmVerifyEmailModal && selectedUser ? (
|
||||
<ConfirmModal
|
||||
title={`Verify Email`}
|
||||
description={`Are you sure you want to verify the email - ${selectedUser.email}?`}
|
||||
isLoading={isConfimModalLoading}
|
||||
submitButtonText={"Verify Email"}
|
||||
onClose={async () => {
|
||||
setShowConfirmVerifyEmailModal(false);
|
||||
setSelectedUser(null);
|
||||
}}
|
||||
onSubmit={async () => {
|
||||
try {
|
||||
setIsConfirmModalLoading(true);
|
||||
await ModelAPI.updateById<User>({
|
||||
modelType: User,
|
||||
id: selectedUser.id!,
|
||||
data: {
|
||||
isEmailVerified: true,
|
||||
},
|
||||
{
|
||||
title: 'Users',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.USERS] as Route
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ModelTable<User>
|
||||
modelType={User}
|
||||
id="users-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
showViewIdButton={true}
|
||||
refreshToggle={refreshItemsTrigger}
|
||||
isCreateable={true}
|
||||
name="Users"
|
||||
isViewable={false}
|
||||
cardProps={{
|
||||
title: 'Users',
|
||||
description: 'Here is a list of users in OneUptime.',
|
||||
}}
|
||||
actionButtons={[
|
||||
{
|
||||
title: 'Verify Email',
|
||||
buttonStyleType: ButtonStyleType.NORMAL,
|
||||
isVisible: (item: User) => {
|
||||
return !item.isEmailVerified;
|
||||
},
|
||||
onClick: async (
|
||||
item: User,
|
||||
onCompleteAction: VoidFunction,
|
||||
onError: ErrorFunction
|
||||
) => {
|
||||
try {
|
||||
setSelectedUser(item);
|
||||
setShowConfirmVerifyEmailModal(true);
|
||||
});
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyMessage(err as Error));
|
||||
}
|
||||
|
||||
onCompleteAction();
|
||||
} catch (err) {
|
||||
onCompleteAction();
|
||||
onError(err as Error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
noItemsMessage={'No users found.'}
|
||||
formFields={[
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
fieldType: FormFieldSchemaType.Email,
|
||||
required: true,
|
||||
placeholder: 'email@company.com',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
password: true,
|
||||
},
|
||||
title: 'Password',
|
||||
fieldType: FormFieldSchemaType.Password,
|
||||
required: true,
|
||||
placeholder: 'Password',
|
||||
},
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Full Name',
|
||||
fieldType: FormFieldSchemaType.Text,
|
||||
required: true,
|
||||
placeholder: 'John Smith',
|
||||
},
|
||||
]}
|
||||
showRefreshButton={true}
|
||||
viewPageRoute={Navigation.getCurrentRoute()}
|
||||
filters={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Full Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
type: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
name: true,
|
||||
},
|
||||
title: 'Full Name',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
email: true,
|
||||
},
|
||||
title: 'Email',
|
||||
type: FieldType.Email,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
isEmailVerified: true,
|
||||
},
|
||||
title: 'Email Verified',
|
||||
type: FieldType.Boolean,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
createdAt: true,
|
||||
},
|
||||
title: 'Created At',
|
||||
type: FieldType.DateTime,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{error ? (
|
||||
<ConfirmModal
|
||||
title={`Error`}
|
||||
description={error}
|
||||
submitButtonText={'Close'}
|
||||
onSubmit={async () => {
|
||||
setError(null);
|
||||
}}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{showConfirmVerifyEmailModal && selectedUser ? (
|
||||
<ConfirmModal
|
||||
title={`Verify Email`}
|
||||
description={`Are you sure you want to verify the email - ${selectedUser.email}?`}
|
||||
isLoading={isConfimModalLoading}
|
||||
submitButtonText={'Verify Email'}
|
||||
onClose={async () => {
|
||||
setShowConfirmVerifyEmailModal(false);
|
||||
setSelectedUser(null);
|
||||
}}
|
||||
onSubmit={async () => {
|
||||
try {
|
||||
setIsConfirmModalLoading(true);
|
||||
await ModelAPI.updateById<User>({
|
||||
modelType: User,
|
||||
id: selectedUser.id!,
|
||||
data: {
|
||||
isEmailVerified: true,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
setError(API.getFriendlyMessage(err as Error));
|
||||
}
|
||||
|
||||
setRefreshItemsTrigger(!refreshItemsTrigger);
|
||||
setIsConfirmModalLoading(false);
|
||||
setShowConfirmVerifyEmailModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
setRefreshItemsTrigger(!refreshItemsTrigger);
|
||||
setIsConfirmModalLoading(false);
|
||||
setShowConfirmVerifyEmailModal(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default Users;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
|
||||
|
||||
export default class AdminModelAPI extends ModelAPI {
|
||||
public static override getCommonHeaders(): Dictionary<string> {
|
||||
return {};
|
||||
}
|
||||
public static override getCommonHeaders(): Dictionary<string> {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
enum PageMap {
|
||||
INIT = 'INIT',
|
||||
HOME = 'HOME',
|
||||
LOGOUT = 'LOGOUT',
|
||||
SETTINGS = 'SETTINGS',
|
||||
USERS = 'USERS',
|
||||
PROJECTS = 'PROJECTS',
|
||||
INIT = "INIT",
|
||||
HOME = "HOME",
|
||||
LOGOUT = "LOGOUT",
|
||||
SETTINGS = "SETTINGS",
|
||||
USERS = "USERS",
|
||||
PROJECTS = "PROJECTS",
|
||||
|
||||
SETTINGS_HOST = 'SETTINGS_HOST',
|
||||
SETTINGS_SMTP = 'SETTINGS_SMTP',
|
||||
SETTINGS_CALL_AND_SMS = 'SETTINGS_CALL_AND_SMS',
|
||||
SETTINGS_PROBES = 'SETTINGS_PROBES',
|
||||
SETTINGS_AUTHENTICATION = 'SETTINGS_AUTHENTICATION',
|
||||
SETTINGS_API_KEY = 'SETTINGS_API_KEY',
|
||||
SETTINGS_HOST = "SETTINGS_HOST",
|
||||
SETTINGS_SMTP = "SETTINGS_SMTP",
|
||||
SETTINGS_CALL_AND_SMS = "SETTINGS_CALL_AND_SMS",
|
||||
SETTINGS_PROBES = "SETTINGS_PROBES",
|
||||
SETTINGS_AUTHENTICATION = "SETTINGS_AUTHENTICATION",
|
||||
SETTINGS_API_KEY = "SETTINGS_API_KEY",
|
||||
}
|
||||
|
||||
export default PageMap;
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
import PageMap from './PageMap';
|
||||
import RouteParams from './RouteParams';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import PageMap from "./PageMap";
|
||||
import RouteParams from "./RouteParams";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
|
||||
const RouteMap: Dictionary<Route> = {
|
||||
[PageMap.INIT]: new Route(`/admin`),
|
||||
[PageMap.HOME]: new Route(`/admin`),
|
||||
[PageMap.LOGOUT]: new Route(`/admin/logout`),
|
||||
[PageMap.SETTINGS]: new Route(`/admin/settings/host`),
|
||||
[PageMap.PROJECTS]: new Route(`/admin/projects`),
|
||||
[PageMap.USERS]: new Route(`/admin/users`),
|
||||
[PageMap.SETTINGS_HOST]: new Route(`/admin/settings/host`),
|
||||
[PageMap.SETTINGS_SMTP]: new Route(`/admin/settings/smtp`),
|
||||
[PageMap.SETTINGS_CALL_AND_SMS]: new Route(`/admin/settings/call-and-sms`),
|
||||
[PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`),
|
||||
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
|
||||
`/admin/settings/authentication`
|
||||
),
|
||||
[PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`),
|
||||
[PageMap.INIT]: new Route(`/admin`),
|
||||
[PageMap.HOME]: new Route(`/admin`),
|
||||
[PageMap.LOGOUT]: new Route(`/admin/logout`),
|
||||
[PageMap.SETTINGS]: new Route(`/admin/settings/host`),
|
||||
[PageMap.PROJECTS]: new Route(`/admin/projects`),
|
||||
[PageMap.USERS]: new Route(`/admin/users`),
|
||||
[PageMap.SETTINGS_HOST]: new Route(`/admin/settings/host`),
|
||||
[PageMap.SETTINGS_SMTP]: new Route(`/admin/settings/smtp`),
|
||||
[PageMap.SETTINGS_CALL_AND_SMS]: new Route(`/admin/settings/call-and-sms`),
|
||||
[PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`),
|
||||
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
|
||||
`/admin/settings/authentication`,
|
||||
),
|
||||
[PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`),
|
||||
};
|
||||
|
||||
export class RouteUtil {
|
||||
public static populateRouteParams(
|
||||
route: Route,
|
||||
props?: {
|
||||
modelId?: ObjectID;
|
||||
subModelId?: ObjectID;
|
||||
}
|
||||
): Route {
|
||||
// populate projectid
|
||||
public static populateRouteParams(
|
||||
route: Route,
|
||||
props?: {
|
||||
modelId?: ObjectID;
|
||||
subModelId?: ObjectID;
|
||||
},
|
||||
): Route {
|
||||
// populate projectid
|
||||
|
||||
const tempRoute: Route = new Route(route.toString());
|
||||
const tempRoute: Route = new Route(route.toString());
|
||||
|
||||
if (props && props.modelId) {
|
||||
route = tempRoute.addRouteParam(
|
||||
RouteParams.ModelID,
|
||||
props.modelId.toString()
|
||||
);
|
||||
}
|
||||
|
||||
if (props && props.subModelId) {
|
||||
route = tempRoute.addRouteParam(
|
||||
RouteParams.SubModelID,
|
||||
props.subModelId.toString()
|
||||
);
|
||||
}
|
||||
|
||||
return tempRoute;
|
||||
if (props && props.modelId) {
|
||||
route = tempRoute.addRouteParam(
|
||||
RouteParams.ModelID,
|
||||
props.modelId.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
if (props && props.subModelId) {
|
||||
route = tempRoute.addRouteParam(
|
||||
RouteParams.SubModelID,
|
||||
props.subModelId.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return tempRoute;
|
||||
}
|
||||
}
|
||||
|
||||
export default RouteMap;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
enum RouteParams {
|
||||
ModelID = ':id',
|
||||
SubModelID = ':subModelId',
|
||||
ModelID = ":id",
|
||||
SubModelID = ":subModelId",
|
||||
}
|
||||
|
||||
export default RouteParams;
|
||||
|
||||
@@ -1,82 +1,84 @@
|
||||
require('ts-loader');
|
||||
require('file-loader');
|
||||
require('style-loader');
|
||||
require('css-loader');
|
||||
require('sass-loader');
|
||||
require("ts-loader");
|
||||
require("file-loader");
|
||||
require("style-loader");
|
||||
require("css-loader");
|
||||
require("sass-loader");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const dotenv = require('dotenv');
|
||||
const express = require('express');
|
||||
const dotenv = require("dotenv");
|
||||
const express = require("express");
|
||||
|
||||
const readEnvFile = (pathToFile) => {
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
|
||||
const parsed = dotenv.config({ path: pathToFile }).parsed;
|
||||
const env = {};
|
||||
|
||||
const env = {};
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
for (const key in parsed) {
|
||||
env[key] = JSON.stringify(parsed[key]);
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
return env;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/admin/dist/",
|
||||
entry: "./src/Index.tsx",
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "public", "dist"),
|
||||
publicPath: "/admin/dist/",
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
|
||||
alias: {
|
||||
react: path.resolve("./node_modules/react"),
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'],
|
||||
alias: {
|
||||
react: path.resolve('./node_modules/react'),
|
||||
}
|
||||
},
|
||||
externals: {
|
||||
'react-native-sqlite-storage': 'react-native-sqlite-storage'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process': {
|
||||
'env': {
|
||||
...readEnvFile('/usr/src/app/dev-env/.env')
|
||||
}
|
||||
}
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: 'ts-loader'
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ['style-loader', 'css-loader', "sass-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: 'file-loader'
|
||||
}
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
externals: {
|
||||
"react-native-sqlite-storage": "react-native-sqlite-storage",
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
process: {
|
||||
env: {
|
||||
...readEnvFile("/usr/src/app/dev-env/.env"),
|
||||
},
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use('/admin/assets', express.static(path.resolve(__dirname, 'public', 'assets')));
|
||||
return middlewares;
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
use: "ts-loader",
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ["style-loader", "css-loader", "sass-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loader: "file-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
devtool: 'eval-source-map',
|
||||
}
|
||||
allowedHosts: "all",
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
devServer.app.use(
|
||||
"/admin/assets",
|
||||
express.static(path.resolve(__dirname, "public", "assets")),
|
||||
);
|
||||
return middlewares;
|
||||
},
|
||||
},
|
||||
devtool: "eval-source-map",
|
||||
};
|
||||
|
||||
@@ -1,89 +1,83 @@
|
||||
import AuthenticationServiceHandler from './Service/Authentication';
|
||||
import DataTypeServiceHandler from './Service/DataType';
|
||||
import ErrorServiceHandler from './Service/Errors';
|
||||
import IntroductionServiceHandler from './Service/Introduction';
|
||||
import ModelServiceHandler from './Service/Model';
|
||||
import PageNotFoundServiceHandler from './Service/PageNotFound';
|
||||
import PaginationServiceHandler from './Service/Pagination';
|
||||
import PermissionServiceHandler from './Service/Permissions';
|
||||
import StatusServiceHandler from './Service/Status';
|
||||
import { StaticPath } from './Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from './Utils/Resources';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
import AuthenticationServiceHandler from "./Service/Authentication";
|
||||
import DataTypeServiceHandler from "./Service/DataType";
|
||||
import ErrorServiceHandler from "./Service/Errors";
|
||||
import IntroductionServiceHandler from "./Service/Introduction";
|
||||
import ModelServiceHandler from "./Service/Model";
|
||||
import PageNotFoundServiceHandler from "./Service/PageNotFound";
|
||||
import PaginationServiceHandler from "./Service/Pagination";
|
||||
import PermissionServiceHandler from "./Service/Permissions";
|
||||
import StatusServiceHandler from "./Service/Status";
|
||||
import { StaticPath } from "./Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "./Utils/Resources";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import FeatureSet from "CommonServer/Types/FeatureSet";
|
||||
import Express, {
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressStatic,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressStatic,
|
||||
} from "CommonServer/Utils/Express";
|
||||
|
||||
const APIReferenceFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
init: async (): Promise<void> => {
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.use('/reference', ExpressStatic(StaticPath, { maxAge: 2592000 }));
|
||||
app.use("/reference", ExpressStatic(StaticPath, { maxAge: 2592000 }));
|
||||
|
||||
// Index page
|
||||
app.get(
|
||||
['/reference'],
|
||||
(_req: ExpressRequest, res: ExpressResponse) => {
|
||||
return res.redirect('/reference/introduction');
|
||||
}
|
||||
);
|
||||
// Index page
|
||||
app.get(["/reference"], (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
return res.redirect("/reference/introduction");
|
||||
});
|
||||
|
||||
app.get(
|
||||
['/reference/page-not-found'],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
);
|
||||
app.get(
|
||||
["/reference/page-not-found"],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
// All Pages
|
||||
app.get(
|
||||
['/reference/:page'],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
const page: string | undefined = req.params['page'];
|
||||
// All Pages
|
||||
app.get(
|
||||
["/reference/:page"],
|
||||
(req: ExpressRequest, res: ExpressResponse) => {
|
||||
const page: string | undefined = req.params["page"];
|
||||
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
|
||||
if (req.params['page'] === 'permissions') {
|
||||
return PermissionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'authentication') {
|
||||
return AuthenticationServiceHandler.executeResponse(
|
||||
req,
|
||||
res
|
||||
);
|
||||
} else if (req.params['page'] === 'pagination') {
|
||||
return PaginationServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'errors') {
|
||||
return ErrorServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'introduction') {
|
||||
return IntroductionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'status') {
|
||||
return StatusServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params['page'] === 'data-types') {
|
||||
return DataTypeServiceHandler.executeResponse(req, res);
|
||||
} else if (currentResource) {
|
||||
return ModelServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
// page not found
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
);
|
||||
if (req.params["page"] === "permissions") {
|
||||
return PermissionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "authentication") {
|
||||
return AuthenticationServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "pagination") {
|
||||
return PaginationServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "errors") {
|
||||
return ErrorServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "introduction") {
|
||||
return IntroductionServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "status") {
|
||||
return StatusServiceHandler.executeResponse(req, res);
|
||||
} else if (req.params["page"] === "data-types") {
|
||||
return DataTypeServiceHandler.executeResponse(req, res);
|
||||
} else if (currentResource) {
|
||||
return ModelServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
// page not found
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
app.get('/reference/*', (req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
});
|
||||
},
|
||||
app.get("/reference/*", (req: ExpressRequest, res: ExpressResponse) => {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default APIReferenceFeatureSet;
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageTitle = 'Authentication';
|
||||
pageDescription =
|
||||
'Learn how to authenticate requests with OneUptime API';
|
||||
pageTitle = "Authentication";
|
||||
pageDescription = "Learn how to authenticate requests with OneUptime API";
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +1,126 @@
|
||||
import { CodeExamplesPath, ViewsPath } from '../Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import LocalFile from 'CommonServer/Utils/LocalFile';
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import LocalCache from "CommonServer/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
import LocalFile from "CommonServer/Utils/LocalFile";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
const pageData: any = {};
|
||||
|
||||
pageData.selectCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'select',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/Select.md`
|
||||
);
|
||||
}
|
||||
pageData.selectCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"select",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/Select.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.sortCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"sort",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/Sort.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/EqualTo.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToOrNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"equal-to-or-null",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/EqualToOrNull.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.sortCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'sort',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/Sort.md`
|
||||
);
|
||||
}
|
||||
pageData.greaterThanCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/GreaterThan.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'equal-to',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/EqualTo.md`
|
||||
);
|
||||
}
|
||||
pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"greater-than-or-equal",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/GreaterThanOrEqual.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.equalToOrNullCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'equal-to-or-null',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/EqualToOrNull.md`
|
||||
);
|
||||
}
|
||||
pageData.lessThanCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThan.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.greaterThanCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'greater-than',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/GreaterThan.md`
|
||||
);
|
||||
}
|
||||
pageData.lessThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"less-than-or-equal",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThanOrEqual.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'greater-than-or-equal',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/GreaterThanOrEqual.md`
|
||||
);
|
||||
}
|
||||
pageData.isNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"is-null",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/IsNull.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.notNullCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-null",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/NotNull.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.notEqualToCode = await LocalCache.getOrSetString(
|
||||
"data-type",
|
||||
"not-equals",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/NotEqualTo.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.lessThanCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'less-than',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThan.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.lessThanOrEqualCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'less-than-or-equal',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/LessThanOrEqual.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.isNullCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'is-null',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/IsNull.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.notNullCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'not-null',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/NotNull.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.notEqualToCode = await LocalCache.getOrSetString(
|
||||
'data-type',
|
||||
'not-equals',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/DataTypes/NotEqualTo.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
res.status(200);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: 'data-types',
|
||||
pageTitle: 'Data Types',
|
||||
pageDescription:
|
||||
'Data Types that can be used to interact with OneUptime API',
|
||||
resources: Resources,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
res.status(200);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: "data-types",
|
||||
pageTitle: "Data Types",
|
||||
pageDescription:
|
||||
"Data Types that can be used to interact with OneUptime API",
|
||||
resources: Resources,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageTitle = 'Errors';
|
||||
pageDescription = 'Learn more about how we return errors from API';
|
||||
pageTitle = "Errors";
|
||||
pageDescription = "Learn more about how we return errors from API";
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const FeaturedResources: Array<ModelDocumentation> =
|
||||
ResourceUtil.getFeaturedResources();
|
||||
ResourceUtil.getFeaturedResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageData.featuredResources = FeaturedResources;
|
||||
pageTitle = 'Introduction';
|
||||
pageDescription = 'API Reference for OneUptime';
|
||||
pageData.featuredResources = FeaturedResources;
|
||||
pageTitle = "Introduction";
|
||||
pageDescription = "API Reference for OneUptime";
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,244 +1,238 @@
|
||||
import { CodeExamplesPath, ViewsPath } from '../Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import PageNotFoundServiceHandler from './PageNotFound';
|
||||
import { AppApiRoute } from 'Common/ServiceRoute';
|
||||
import { ColumnAccessControl } from 'Common/Types/BaseDatabase/AccessControl';
|
||||
import { getTableColumns } from 'Common/Types/Database/TableColumn';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import PageNotFoundServiceHandler from "./PageNotFound";
|
||||
import { AppApiRoute } from "Common/ServiceRoute";
|
||||
import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl";
|
||||
import { getTableColumns } from "Common/Types/Database/TableColumn";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Permission, {
|
||||
PermissionHelper,
|
||||
PermissionProps,
|
||||
} from 'Common/Types/Permission';
|
||||
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import LocalFile from 'CommonServer/Utils/LocalFile';
|
||||
PermissionHelper,
|
||||
PermissionProps,
|
||||
} from "Common/Types/Permission";
|
||||
import LocalCache from "CommonServer/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
import LocalFile from "CommonServer/Utils/LocalFile";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
const ResourceDictionary: Dictionary<ModelDocumentation> =
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
ResourceUtil.getResourceDictionaryByPath();
|
||||
|
||||
const PermissionDictionary: Dictionary<PermissionProps> =
|
||||
PermissionHelper.getAllPermissionPropsAsDictionary();
|
||||
PermissionHelper.getAllPermissionPropsAsDictionary();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
let page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
let page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
|
||||
if (!currentResource) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
// Resource Page.
|
||||
pageTitle = currentResource.name;
|
||||
pageDescription = currentResource.description;
|
||||
|
||||
page = 'model';
|
||||
|
||||
const tableColumns: any = getTableColumns(currentResource.model);
|
||||
|
||||
for (const key in tableColumns) {
|
||||
const accessControl: ColumnAccessControl | null =
|
||||
currentResource.model.getColumnAccessControlFor(key);
|
||||
|
||||
if (!accessControl) {
|
||||
// remove columns with no access
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
accessControl?.create.length === 0 &&
|
||||
accessControl?.read.length === 0 &&
|
||||
accessControl?.update.length === 0
|
||||
) {
|
||||
// remove columns with no access
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
tableColumns[key].permissions = accessControl;
|
||||
}
|
||||
|
||||
delete tableColumns['deletedAt'];
|
||||
delete tableColumns['deletedByUserId'];
|
||||
delete tableColumns['deletedByUser'];
|
||||
delete tableColumns['version'];
|
||||
|
||||
pageData.title = currentResource.model.singularName;
|
||||
pageData.description = currentResource.model.tableDescription;
|
||||
pageData.columns = tableColumns;
|
||||
pageData.tablePermissions = {
|
||||
read: currentResource.model.readRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
}
|
||||
),
|
||||
update: currentResource.model.updateRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
}
|
||||
),
|
||||
delete: currentResource.model.deleteRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
}
|
||||
),
|
||||
create: currentResource.model.createRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
pageData.listRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'list-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ListRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.itemRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'item-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ItemRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.itemResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'item-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ItemResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.countRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'count-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CountRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.countResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'count-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CountResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.updateRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'update-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/UpdateRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.updateResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'update-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/UpdateResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.createRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'create-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CreateRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.createResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'create-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CreateResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.deleteRequest = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'delete-request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/DeleteRequest.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.deleteResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'delete-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/DeleteResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.listResponse = await LocalCache.getOrSetString(
|
||||
'model',
|
||||
'list-response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ListResponse.md`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
pageData.exampleObjectID = ObjectID.generate();
|
||||
|
||||
pageData.apiPath =
|
||||
AppApiRoute.toString() +
|
||||
currentResource.model.crudApiPath?.toString();
|
||||
|
||||
pageData.isMasterAdminApiDocs =
|
||||
currentResource.model.isMasterAdminApiDocs;
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
if (!page) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
const currentResource: ModelDocumentation | undefined =
|
||||
ResourceDictionary[page];
|
||||
|
||||
if (!currentResource) {
|
||||
return PageNotFoundServiceHandler.executeResponse(req, res);
|
||||
}
|
||||
|
||||
// Resource Page.
|
||||
pageTitle = currentResource.name;
|
||||
pageDescription = currentResource.description;
|
||||
|
||||
page = "model";
|
||||
|
||||
const tableColumns: any = getTableColumns(currentResource.model);
|
||||
|
||||
for (const key in tableColumns) {
|
||||
const accessControl: ColumnAccessControl | null =
|
||||
currentResource.model.getColumnAccessControlFor(key);
|
||||
|
||||
if (!accessControl) {
|
||||
// remove columns with no access
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
accessControl?.create.length === 0 &&
|
||||
accessControl?.read.length === 0 &&
|
||||
accessControl?.update.length === 0
|
||||
) {
|
||||
// remove columns with no access
|
||||
delete tableColumns[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
tableColumns[key].permissions = accessControl;
|
||||
}
|
||||
|
||||
delete tableColumns["deletedAt"];
|
||||
delete tableColumns["deletedByUserId"];
|
||||
delete tableColumns["deletedByUser"];
|
||||
delete tableColumns["version"];
|
||||
|
||||
pageData.title = currentResource.model.singularName;
|
||||
pageData.description = currentResource.model.tableDescription;
|
||||
pageData.columns = tableColumns;
|
||||
pageData.tablePermissions = {
|
||||
read: currentResource.model.readRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
},
|
||||
),
|
||||
update: currentResource.model.updateRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
},
|
||||
),
|
||||
delete: currentResource.model.deleteRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
},
|
||||
),
|
||||
create: currentResource.model.createRecordPermissions.map(
|
||||
(permission: Permission) => {
|
||||
return PermissionDictionary[permission];
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
pageData.listRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-request",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/Model/ListRequest.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.itemRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-request",
|
||||
async () => {
|
||||
return await LocalFile.read(`${CodeExamplesPath}/Model/ItemRequest.md`);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.itemResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"item-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ItemResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.countRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CountRequest.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.countResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"count-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CountResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.updateRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/UpdateRequest.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.updateResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"update-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/UpdateResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.createRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CreateRequest.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.createResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"create-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/CreateResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.deleteRequest = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/DeleteRequest.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.deleteResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"delete-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/DeleteResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.listResponse = await LocalCache.getOrSetString(
|
||||
"model",
|
||||
"list-response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Model/ListResponse.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.exampleObjectID = ObjectID.generate();
|
||||
|
||||
pageData.apiPath =
|
||||
AppApiRoute.toString() + currentResource.model.crudApiPath?.toString();
|
||||
|
||||
pageData.isMasterAdminApiDocs = currentResource.model.isMasterAdminApiDocs;
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: '404',
|
||||
pageTitle: 'Page Not Found',
|
||||
pageDescription: "Page you're looking for is not found.",
|
||||
resources: Resources,
|
||||
pageData: {},
|
||||
});
|
||||
}
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: "404",
|
||||
pageTitle: "Page Not Found",
|
||||
pageDescription: "Page you're looking for is not found.",
|
||||
resources: Resources,
|
||||
pageData: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import { CodeExamplesPath, ViewsPath } from '../Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import LocalFile from 'CommonServer/Utils/LocalFile';
|
||||
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import LocalCache from "CommonServer/Infrastructure/LocalCache";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
import LocalFile from "CommonServer/Utils/LocalFile";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageTitle = 'Pagination';
|
||||
pageDescription = 'Learn how to paginate requests with OneUptime API';
|
||||
pageTitle = "Pagination";
|
||||
pageDescription = "Learn how to paginate requests with OneUptime API";
|
||||
|
||||
pageData.responseCode = await LocalCache.getOrSetString(
|
||||
'pagination',
|
||||
'response',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Pagination/Response.md`
|
||||
);
|
||||
}
|
||||
pageData.responseCode = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"response",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Pagination/Response.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
pageData.requestCode = await LocalCache.getOrSetString(
|
||||
'pagination',
|
||||
'request',
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Pagination/Request.md`
|
||||
);
|
||||
}
|
||||
pageData.requestCode = await LocalCache.getOrSetString(
|
||||
"pagination",
|
||||
"request",
|
||||
async () => {
|
||||
return await LocalFile.read(
|
||||
`${CodeExamplesPath}/Pagination/Request.md`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { PermissionHelper, PermissionProps } from 'Common/Types/Permission';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { PermissionHelper, PermissionProps } from "Common/Types/Permission";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
let pageTitle: string = '';
|
||||
let pageDescription: string = '';
|
||||
const page: string | undefined = req.params['page'];
|
||||
const pageData: any = {};
|
||||
public static async executeResponse(
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
let pageTitle: string = "";
|
||||
let pageDescription: string = "";
|
||||
const page: string | undefined = req.params["page"];
|
||||
const pageData: any = {};
|
||||
|
||||
pageTitle = 'Permissions';
|
||||
pageDescription = 'Learn how permissions work with OneUptime';
|
||||
pageTitle = "Permissions";
|
||||
pageDescription = "Learn how permissions work with OneUptime";
|
||||
|
||||
pageData.permissions = PermissionHelper.getAllPermissionProps().filter(
|
||||
(i: PermissionProps) => {
|
||||
return i.isAssignableToTenant;
|
||||
}
|
||||
);
|
||||
pageData.permissions = PermissionHelper.getAllPermissionProps().filter(
|
||||
(i: PermissionProps) => {
|
||||
return i.isAssignableToTenant;
|
||||
},
|
||||
);
|
||||
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: page,
|
||||
resources: Resources,
|
||||
pageTitle: pageTitle,
|
||||
pageDescription: pageDescription,
|
||||
pageData: pageData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
|
||||
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
|
||||
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
export default class ServiceHandler {
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
): Promise<void> {
|
||||
res.status(200);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: 'status',
|
||||
pageTitle: 'Status',
|
||||
pageDescription: '200 - Success',
|
||||
resources: Resources,
|
||||
pageData: {},
|
||||
});
|
||||
}
|
||||
public static async executeResponse(
|
||||
_req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> {
|
||||
res.status(200);
|
||||
return res.render(`${ViewsPath}/pages/index`, {
|
||||
page: "status",
|
||||
pageTitle: "Status",
|
||||
pageDescription: "200 - Success",
|
||||
resources: Resources,
|
||||
pageData: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const ViewsPath: string = '/usr/src/app/FeatureSet/ApiReference/views';
|
||||
export const StaticPath: string = '/usr/src/app/FeatureSet/ApiReference/Static';
|
||||
export const ViewsPath: string = "/usr/src/app/FeatureSet/ApiReference/views";
|
||||
export const StaticPath: string = "/usr/src/app/FeatureSet/ApiReference/Static";
|
||||
export const CodeExamplesPath: string =
|
||||
'/usr/src/app/FeatureSet/ApiReference/CodeExamples';
|
||||
"/usr/src/app/FeatureSet/ApiReference/CodeExamples";
|
||||
|
||||
@@ -1,74 +1,73 @@
|
||||
import BaseModel from 'Common/Models/BaseModel';
|
||||
import ArrayUtil from 'Common/Types/ArrayUtil';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig';
|
||||
import Models from 'Model/Models/Index';
|
||||
import BaseModel from "Common/Models/BaseModel";
|
||||
import ArrayUtil from "Common/Types/ArrayUtil";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { IsBillingEnabled } from "CommonServer/EnvironmentConfig";
|
||||
import Models from "Model/Models/Index";
|
||||
|
||||
export interface ModelDocumentation {
|
||||
name: string;
|
||||
path: string;
|
||||
model: BaseModel;
|
||||
description: string;
|
||||
name: string;
|
||||
path: string;
|
||||
model: BaseModel;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default class ResourceUtil {
|
||||
public static getResources(): Array<ModelDocumentation> {
|
||||
const resources: Array<ModelDocumentation> = Models.filter(
|
||||
(model: typeof BaseModel) => {
|
||||
const modelInstance: BaseModel = new model();
|
||||
let showDocs: boolean = modelInstance.enableDocumentation;
|
||||
public static getResources(): Array<ModelDocumentation> {
|
||||
const resources: Array<ModelDocumentation> = Models.filter(
|
||||
(model: typeof BaseModel) => {
|
||||
const modelInstance: BaseModel = new model();
|
||||
let showDocs: boolean = modelInstance.enableDocumentation;
|
||||
|
||||
if (modelInstance.isMasterAdminApiDocs && IsBillingEnabled) {
|
||||
showDocs = false;
|
||||
}
|
||||
|
||||
return showDocs;
|
||||
}
|
||||
)
|
||||
.map((model: typeof BaseModel) => {
|
||||
const modelInstance: BaseModel = new model();
|
||||
|
||||
return {
|
||||
name: modelInstance.singularName!,
|
||||
path: modelInstance.getAPIDocumentationPath(),
|
||||
model: modelInstance,
|
||||
description: modelInstance.tableDescription!,
|
||||
};
|
||||
})
|
||||
.sort(ArrayUtil.sortByFieldName('name'));
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
public static getFeaturedResources(): Array<ModelDocumentation> {
|
||||
const featuredResources: Array<string> = [
|
||||
'Monitor',
|
||||
'Scheduled Maintenance Event',
|
||||
'Status Page',
|
||||
'Incident',
|
||||
'Team',
|
||||
'On-Call Duty',
|
||||
'Label',
|
||||
'Team Member',
|
||||
];
|
||||
|
||||
return ResourceUtil.getResources().filter(
|
||||
(resource: ModelDocumentation) => {
|
||||
return featuredResources.includes(resource.name);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static getResourceDictionaryByPath(): Dictionary<ModelDocumentation> {
|
||||
const dict: Dictionary<ModelDocumentation> = {};
|
||||
|
||||
const resources: Array<ModelDocumentation> =
|
||||
ResourceUtil.getResources();
|
||||
|
||||
for (const resource of resources) {
|
||||
dict[resource.path] = resource;
|
||||
if (modelInstance.isMasterAdminApiDocs && IsBillingEnabled) {
|
||||
showDocs = false;
|
||||
}
|
||||
|
||||
return dict;
|
||||
return showDocs;
|
||||
},
|
||||
)
|
||||
.map((model: typeof BaseModel) => {
|
||||
const modelInstance: BaseModel = new model();
|
||||
|
||||
return {
|
||||
name: modelInstance.singularName!,
|
||||
path: modelInstance.getAPIDocumentationPath(),
|
||||
model: modelInstance,
|
||||
description: modelInstance.tableDescription!,
|
||||
};
|
||||
})
|
||||
.sort(ArrayUtil.sortByFieldName("name"));
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
public static getFeaturedResources(): Array<ModelDocumentation> {
|
||||
const featuredResources: Array<string> = [
|
||||
"Monitor",
|
||||
"Scheduled Maintenance Event",
|
||||
"Status Page",
|
||||
"Incident",
|
||||
"Team",
|
||||
"On-Call Duty",
|
||||
"Label",
|
||||
"Team Member",
|
||||
];
|
||||
|
||||
return ResourceUtil.getResources().filter(
|
||||
(resource: ModelDocumentation) => {
|
||||
return featuredResources.includes(resource.name);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public static getResourceDictionaryByPath(): Dictionary<ModelDocumentation> {
|
||||
const dict: Dictionary<ModelDocumentation> = {};
|
||||
|
||||
const resources: Array<ModelDocumentation> = ResourceUtil.getResources();
|
||||
|
||||
for (const resource of resources) {
|
||||
dict[resource.path] = resource;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,6 @@ We use OpenTelemetry to collect application logs. OneUptime currently supports l
|
||||
- [JavaScript / Typescript / NodeJS / Browser](https://opentelemetry.io/docs/instrumentation/js/)
|
||||
- [Python](https://opentelemetry.io/docs/instrumentation/python/)
|
||||
- [Ruby](https://opentelemetry.io/docs/instrumentation/ruby/)
|
||||
- [Swift](https://opentelemetry.io/docs/instrumentation/swift/)
|
||||
- [PHP](https://opentelemetry.io/docs/instrumentation/php/)
|
||||
- [Erlang](https://opentelemetry.io/docs/instrumentation/erlang/)
|
||||
- [Rust](https://opentelemetry.io/docs/instrumentation/rust/)
|
||||
|
||||
@@ -1,93 +1,85 @@
|
||||
import { ContentPath, StaticPath, ViewsPath } from './Utils/Config';
|
||||
import DocsNav, { NavGroup, NavLink } from './Utils/Nav';
|
||||
import DocsRender from './Utils/Render';
|
||||
import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
import { ContentPath, StaticPath, ViewsPath } from "./Utils/Config";
|
||||
import DocsNav, { NavGroup, NavLink } from "./Utils/Nav";
|
||||
import DocsRender from "./Utils/Render";
|
||||
import FeatureSet from "CommonServer/Types/FeatureSet";
|
||||
import Express, {
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressStatic,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import LocalFile from 'CommonServer/Utils/LocalFile';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import 'ejs';
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressStatic,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import LocalFile from "CommonServer/Utils/LocalFile";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import "ejs";
|
||||
|
||||
const DocsFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
init: async (): Promise<void> => {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.get('/docs', (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.redirect('/docs/introduction/getting-started');
|
||||
});
|
||||
app.get("/docs", (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
res.redirect("/docs/introduction/getting-started");
|
||||
});
|
||||
|
||||
app.get(
|
||||
'/docs/:categorypath/:pagepath',
|
||||
async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fullPath: string =
|
||||
`${_req.params['categorypath']}/${_req.params['pagepath']}`.toLowerCase();
|
||||
app.get(
|
||||
"/docs/:categorypath/:pagepath",
|
||||
async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fullPath: string =
|
||||
`${_req.params["categorypath"]}/${_req.params["pagepath"]}`.toLowerCase();
|
||||
|
||||
// read file from Content folder.
|
||||
let contentInMarkdown: string = await LocalFile.read(
|
||||
`${ContentPath}/${fullPath}.md`
|
||||
);
|
||||
// read file from Content folder.
|
||||
let contentInMarkdown: string = await LocalFile.read(
|
||||
`${ContentPath}/${fullPath}.md`,
|
||||
);
|
||||
|
||||
// remove first line from content because we dont want to show title in content. Title is already in nav.
|
||||
// remove first line from content because we dont want to show title in content. Title is already in nav.
|
||||
|
||||
contentInMarkdown = contentInMarkdown
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
.join('\n');
|
||||
contentInMarkdown = contentInMarkdown.split("\n").slice(1).join("\n");
|
||||
|
||||
const renderedContent: string = await DocsRender.render(
|
||||
contentInMarkdown
|
||||
);
|
||||
const renderedContent: string =
|
||||
await DocsRender.render(contentInMarkdown);
|
||||
|
||||
const currentCategory: NavGroup | undefined = DocsNav.find(
|
||||
(category: NavGroup) => {
|
||||
return category.links.find((link: NavLink) => {
|
||||
return link.url
|
||||
.toLocaleLowerCase()
|
||||
.includes(fullPath);
|
||||
});
|
||||
}
|
||||
);
|
||||
const currentCategory: NavGroup | undefined = DocsNav.find(
|
||||
(category: NavGroup) => {
|
||||
return category.links.find((link: NavLink) => {
|
||||
return link.url.toLocaleLowerCase().includes(fullPath);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const currrentNavLink: NavLink | undefined =
|
||||
currentCategory?.links.find((link: NavLink) => {
|
||||
return link.url
|
||||
.toLocaleLowerCase()
|
||||
.includes(fullPath);
|
||||
});
|
||||
const currrentNavLink: NavLink | undefined =
|
||||
currentCategory?.links.find((link: NavLink) => {
|
||||
return link.url.toLocaleLowerCase().includes(fullPath);
|
||||
});
|
||||
|
||||
if (!currentCategory || !currrentNavLink) {
|
||||
// render not found.
|
||||
if (!currentCategory || !currrentNavLink) {
|
||||
// render not found.
|
||||
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/NotFound`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
res.status(404);
|
||||
return res.render(`${ViewsPath}/NotFound`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
|
||||
res.render(`${ViewsPath}/Index`, {
|
||||
nav: DocsNav,
|
||||
content: renderedContent,
|
||||
category: currentCategory,
|
||||
link: currrentNavLink,
|
||||
githubPath: fullPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
res.status(500);
|
||||
return res.render(`${ViewsPath}/ServerError`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
res.render(`${ViewsPath}/Index`, {
|
||||
nav: DocsNav,
|
||||
content: renderedContent,
|
||||
category: currentCategory,
|
||||
link: currrentNavLink,
|
||||
githubPath: fullPath,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
res.status(500);
|
||||
return res.render(`${ViewsPath}/ServerError`, {
|
||||
nav: DocsNav,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.use('/docs/static', ExpressStatic(StaticPath));
|
||||
},
|
||||
app.use("/docs/static", ExpressStatic(StaticPath));
|
||||
},
|
||||
};
|
||||
|
||||
export default DocsFeatureSet;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const ViewsPath: string = '/usr/src/app/FeatureSet/Docs/Views';
|
||||
export const StaticPath: string = '/usr/src/app/FeatureSet/Docs/Static';
|
||||
export const ContentPath: string = '/usr/src/app/FeatureSet/Docs/Content';
|
||||
export const ViewsPath: string = "/usr/src/app/FeatureSet/Docs/Views";
|
||||
export const StaticPath: string = "/usr/src/app/FeatureSet/Docs/Static";
|
||||
export const ContentPath: string = "/usr/src/app/FeatureSet/Docs/Content";
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
export interface NavLink {
|
||||
title: string;
|
||||
url: string;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface NavGroup {
|
||||
title: string;
|
||||
links: NavLink[];
|
||||
title: string;
|
||||
links: NavLink[];
|
||||
}
|
||||
|
||||
const DocsNav: NavGroup[] = [
|
||||
{
|
||||
title: 'Introduction',
|
||||
links: [
|
||||
{
|
||||
title: 'Getting Started',
|
||||
url: '/docs/introduction/getting-started',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Installation',
|
||||
links: [
|
||||
{
|
||||
title: 'Local Development',
|
||||
url: '/docs/installation/local-development',
|
||||
},
|
||||
{
|
||||
title: 'Docker Compose',
|
||||
url: '/docs/installation/docker-compose',
|
||||
},
|
||||
{
|
||||
title: 'Kubernetes and Helm',
|
||||
url: 'https://artifacthub.io/packages/helm/oneuptime/oneuptime',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Monitor',
|
||||
links: [
|
||||
{
|
||||
title: 'Custom Code Monitor',
|
||||
url: '/docs/monitor/custom-code-monitor',
|
||||
},
|
||||
{
|
||||
title: 'Synthetic Monitor',
|
||||
url: '/docs/monitor/synthetic-monitor',
|
||||
},
|
||||
{
|
||||
title: 'JavaScript Expressions',
|
||||
url: '/docs/monitor/javascript-expression',
|
||||
},
|
||||
{
|
||||
title: 'Monitor Secrets',
|
||||
url: '/docs/monitor/monitor-secrets',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Probe',
|
||||
links: [
|
||||
{ title: 'Custom Probes', url: '/docs/probe/custom-probe' },
|
||||
{ title: 'IP Addresses', url: '/docs/probe/ip-address' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Telemetry',
|
||||
links: [
|
||||
{ title: 'OpenTelemetry', url: '/docs/telemetry/open-telemetry' },
|
||||
{ title: 'Fluentd', url: '/docs/telemetry/fluentd' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Introduction",
|
||||
links: [
|
||||
{
|
||||
title: "Getting Started",
|
||||
url: "/docs/introduction/getting-started",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Installation",
|
||||
links: [
|
||||
{
|
||||
title: "Local Development",
|
||||
url: "/docs/installation/local-development",
|
||||
},
|
||||
{
|
||||
title: "Docker Compose",
|
||||
url: "/docs/installation/docker-compose",
|
||||
},
|
||||
{
|
||||
title: "Kubernetes and Helm",
|
||||
url: "https://artifacthub.io/packages/helm/oneuptime/oneuptime",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Monitor",
|
||||
links: [
|
||||
{
|
||||
title: "Custom Code Monitor",
|
||||
url: "/docs/monitor/custom-code-monitor",
|
||||
},
|
||||
{
|
||||
title: "Synthetic Monitor",
|
||||
url: "/docs/monitor/synthetic-monitor",
|
||||
},
|
||||
{
|
||||
title: "JavaScript Expressions",
|
||||
url: "/docs/monitor/javascript-expression",
|
||||
},
|
||||
{
|
||||
title: "Monitor Secrets",
|
||||
url: "/docs/monitor/monitor-secrets",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Probe",
|
||||
links: [
|
||||
{ title: "Custom Probes", url: "/docs/probe/custom-probe" },
|
||||
{ title: "IP Addresses", url: "/docs/probe/ip-address" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Telemetry",
|
||||
links: [
|
||||
{ title: "OpenTelemetry", url: "/docs/telemetry/open-telemetry" },
|
||||
{ title: "Fluentd", url: "/docs/telemetry/fluentd" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default DocsNav;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown';
|
||||
import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown";
|
||||
|
||||
export default class DocsRender {
|
||||
public static async render(markdownContent: string): Promise<string> {
|
||||
return Markdown.convertToHTML(
|
||||
markdownContent,
|
||||
MarkdownContentType.Docs
|
||||
);
|
||||
}
|
||||
public static async render(markdownContent: string): Promise<string> {
|
||||
return Markdown.convertToHTML(markdownContent, MarkdownContentType.Docs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +1,88 @@
|
||||
import BlogPostUtil, { BlogPost, BlogPostHeader } from '../Utils/BlogPost';
|
||||
import { ViewsPath } from '../Utils/Config';
|
||||
import NotFoundUtil from '../Utils/NotFound';
|
||||
import ServerErrorUtil from '../Utils/ServerError';
|
||||
import Text from 'Common/Types/Text';
|
||||
import BlogPostUtil, { BlogPost, BlogPostHeader } from "../Utils/BlogPost";
|
||||
import { ViewsPath } from "../Utils/Config";
|
||||
import NotFoundUtil from "../Utils/NotFound";
|
||||
import ServerErrorUtil from "../Utils/ServerError";
|
||||
import Text from "Common/Types/Text";
|
||||
import Express, {
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
ExpressApplication,
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
app.get(
|
||||
'/blog/post/:file',
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fileName: string = req.params['file'] as string;
|
||||
"/blog/post/:file",
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const fileName: string = req.params["file"] as string;
|
||||
|
||||
const blogPost: BlogPost | null = await BlogPostUtil.getBlogPost(
|
||||
fileName
|
||||
);
|
||||
const blogPost: BlogPost | null =
|
||||
await BlogPostUtil.getBlogPost(fileName);
|
||||
|
||||
if (!blogPost) {
|
||||
return NotFoundUtil.renderNotFound(res);
|
||||
}
|
||||
if (!blogPost) {
|
||||
return NotFoundUtil.renderNotFound(res);
|
||||
}
|
||||
|
||||
res.render(`${ViewsPath}/Blog/Post`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPost: blogPost,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
res.render(`${ViewsPath}/Blog/Post`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPost: blogPost,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// List all blog posts with tag
|
||||
|
||||
app.get(
|
||||
'/blog/tag/:tagName',
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const tagName: string = req.params['tagName'] as string;
|
||||
"/blog/tag/:tagName",
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const tagName: string = req.params["tagName"] as string;
|
||||
|
||||
const blogPosts: Array<BlogPostHeader> =
|
||||
await BlogPostUtil.getBlogPostList(tagName);
|
||||
const blogPosts: Array<BlogPostHeader> =
|
||||
await BlogPostUtil.getBlogPostList(tagName);
|
||||
|
||||
res.render(`${ViewsPath}/Blog/ListByTag`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPosts: blogPosts,
|
||||
tagName: Text.fromDashesToPascalCase(tagName),
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
res.render(`${ViewsPath}/Blog/ListByTag`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPosts: blogPosts,
|
||||
tagName: Text.fromDashesToPascalCase(tagName),
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// main blog page
|
||||
app.get('/blog', async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const blogPosts: Array<BlogPostHeader> =
|
||||
await BlogPostUtil.getBlogPostList();
|
||||
app.get("/blog", async (_req: ExpressRequest, res: ExpressResponse) => {
|
||||
try {
|
||||
const blogPosts: Array<BlogPostHeader> =
|
||||
await BlogPostUtil.getBlogPostList();
|
||||
|
||||
res.render(`${ViewsPath}/Blog/List`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPosts: blogPosts,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
res.render(`${ViewsPath}/Blog/List`, {
|
||||
support: false,
|
||||
footerCards: true,
|
||||
cta: true,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
blogPosts: blogPosts,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return ServerErrorUtil.renderServerError(res);
|
||||
}
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
12
App/FeatureSet/Home/Static/img/skillable-logo.svg
Normal file
12
App/FeatureSet/Home/Static/img/skillable-logo.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="302" height="135" viewBox="0 0 302 135" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M225.829 109.243C225.862 105.573 225.426 101.97 223.866 98.6024C221.081 92.5075 215.51 91.2447 211.014 92.8274C208.028 93.8713 206.149 96.1779 204.84 98.9559C202.894 103.098 202.508 107.509 202.894 112.021C203.179 115.203 203.967 118.234 205.662 120.961C206.954 123.032 208.682 124.649 211.031 125.44C216.232 127.208 221.366 124.901 223.782 119.716C225.325 116.399 225.795 112.863 225.795 109.243M202.004 127.932C202.004 129.178 202.021 131.164 202.004 132.41C201.971 133.825 201.619 134.195 200.31 134.212C198.364 134.246 196.417 134.246 194.471 134.212C193.246 134.195 192.961 133.892 192.844 132.68C192.81 132.242 192.827 131.804 192.827 131.367V67.0336C192.827 64.2219 192.861 64.1882 195.595 64.1882C197.323 64.1882 199.052 64.1714 200.78 64.1882C202.474 64.2219 202.726 64.4576 202.726 66.2086C202.726 73.2295 202.726 80.2504 202.726 87.2713V89.056C203.951 88.0121 204.89 87.0524 205.981 86.2948C211.886 82.1698 218.883 82.4392 224.352 85.2677C228.882 87.6922 232.355 92.1876 234.067 97.4238C236.801 105.825 236.768 114.26 233.312 122.477C230.828 128.386 226.701 132.73 220.359 134.313C213.615 135.997 207.608 134.582 202.894 129.127C202.407 128.555 202.155 127.831 202.004 127.915" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M151.135 109.344C151.252 112.964 151.47 116.601 153.014 119.985C154.071 122.275 155.564 124.211 157.98 125.103C161.084 126.518 165.882 126.265 169.12 123.15C170.261 122.039 171.301 120.709 172.023 119.278C174.539 114.227 174.942 108.873 173.868 103.384C173.315 100.572 172.09 98.0636 170.194 95.858C165.932 90.908 156.587 90.4534 153.165 98.3161C151.621 101.852 151.269 105.539 151.135 109.344ZM174.841 89.2412C174.841 88.231 174.841 87.2208 174.841 86.2274C174.858 84.3922 175.093 84.1397 176.905 84.1228C178.851 84.106 180.797 84.0892 182.744 84.1228C184.153 84.1565 184.421 84.4596 184.438 85.8907C184.472 87.7427 184.438 131.602 184.438 131.602C184.438 134.094 184.321 134.195 181.905 134.212C180.227 134.212 178.549 134.229 176.871 134.212C175.613 134.178 175.345 133.959 175.227 132.697C175.093 131.367 175.076 129.733 174.992 128.386C174.992 128.386 174.422 128.942 174.019 129.363C171.1 132.73 167.409 134.515 163.013 134.885C153.651 135.677 146.923 130.39 143.635 122.359C141.387 116.904 140.967 111.179 141.471 105.371C141.856 100.993 142.947 96.8008 145.128 92.9621C147.93 88.0626 151.873 84.5943 157.493 83.5167C163.6 82.3381 169.087 83.6514 173.516 88.332C173.835 88.6687 174.17 88.9886 174.489 89.3254C174.489 89.3254 174.741 89.6284 174.791 89.6116C174.808 89.6116 174.791 89.2412 174.791 89.2412" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M74.6115 80.2504L81.8427 87.0692L66.0046 104.209C66.7093 105.438 77.145 123.47 81.8931 131.451C83.0339 133.37 82.5977 134.38 80.2488 134.246C78.1516 134.111 76.0376 134.145 73.9237 134.246C72.6821 134.296 71.9607 133.841 71.3399 132.764C67.6656 126.164 59.1761 111.348 59.1761 111.348C59.1761 111.348 53.5388 117.729 52.2134 119.177C51.5926 119.85 51.3074 120.574 51.3241 121.517C51.3745 124.884 51.3577 132.511 51.3074 132.949C51.2235 133.774 50.7537 134.229 49.9148 134.229C47.5324 134.229 45.1667 134.229 42.7843 134.229C41.9454 134.229 41.4756 133.791 41.4085 132.949V66.7811C41.3918 64.2724 41.4756 64.1882 43.9084 64.1882C45.8043 64.1882 47.7002 64.1545 49.5793 64.1882C50.955 64.2219 51.257 64.5586 51.3241 65.9561C51.3409 66.3938 51.3241 104.781 51.3241 105.354L74.6115 80.2335V80.2504Z" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M270.675 104.142H292.436C292.218 100.875 291.53 97.8784 289.416 95.4371C286.514 92.0866 282.722 91.6657 278.729 92.6254C274.249 93.7029 270.625 99.0064 270.692 104.142M270.407 112.998C271.011 117.342 272.185 121.197 275.759 123.891C277.789 125.423 280.121 126.012 282.604 125.979C285.977 125.945 289.114 125.036 291.849 122.982C292.067 122.813 292.302 122.679 292.637 122.46C295.003 124.177 297.402 125.911 299.902 127.73C297.302 130.323 294.517 132.293 291.211 133.471C286.178 135.239 281.027 135.525 275.91 134.06C268.813 132.023 264.25 127.124 262.001 120.187C259.334 111.954 259.451 103.653 262.907 95.6391C265.793 88.9718 270.81 84.6616 278.041 83.4494C283.98 82.4392 289.584 83.4157 294.366 87.406C298.426 90.7901 300.506 95.3024 301.462 100.438C302.134 104.041 302.066 107.694 301.848 111.331C301.764 112.661 301.462 112.93 300.187 112.981C299.801 112.981 271.128 112.981 270.424 112.981" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.523 90.4702C33.8754 89.881 33.1371 89.1402 32.9861 89.0055C26.7281 83.9713 19.5976 82.0688 11.6785 83.6851C7.0143 84.6279 3.22255 87.6417 1.36023 91.7667C-0.602759 96.3968 -0.585981 100.976 2.36689 105.253C3.89366 107.458 6.05798 108.957 8.47396 109.95C12.3999 111.482 21.4767 114.648 23.7416 115.759C24.7147 116.18 25.4697 116.904 25.8389 117.931C26.812 120.473 26.5267 124.514 22.2652 125.743C19.866 126.433 17.45 126.316 15.034 125.895C11.3262 125.255 9.12829 124.362 6.25931 121.921C5.90698 121.618 5.25265 121.601 4.93387 121.921C4.56476 122.292 0.135458 127.141 0.135458 127.141C-0.23365 127.915 0.252902 128.37 1.17567 129.195C1.89711 129.834 4.4641 131.602 5.60498 132.141C11.3597 134.852 17.3997 135.609 23.6242 134.498C30.2346 133.32 34.731 129.38 35.6035 122.426C36.1236 118.234 35.5699 114.26 32.6003 110.994C30.8554 109.075 28.6911 107.711 26.3086 106.802C23.2886 105.64 14.7656 102.946 12.3328 101.633C10.5879 100.875 9.69873 99.5284 9.71551 97.5417C9.71551 95.4371 10.5208 93.8207 12.3664 92.8611C13.1717 92.457 14.0945 92.1876 14.9837 92.0697C19.262 91.5478 23.1209 92.6759 26.5771 95.2014C26.812 95.3697 27.0301 95.5381 27.265 95.7233C27.7683 96.1105 28.4729 96.0432 28.9092 95.5886C28.9092 95.5886 33.3217 90.8407 33.523 90.5039" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M242.975 99.0906V66.7811C242.975 64.3229 243.11 64.1882 245.542 64.1882C247.438 64.1882 249.317 64.1545 251.213 64.1882C252.539 64.2219 252.824 64.5249 252.874 65.8214C252.891 66.1413 252.874 110.001 252.874 131.585C252.874 134.094 252.774 134.195 250.358 134.195C248.462 134.195 246.583 134.212 244.687 134.195C243.412 134.178 243.059 133.808 242.992 132.545C242.959 132.107 242.992 109.816 242.992 99.0906" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M124.089 134.178H134.005V62.3362H124.089V134.178Z" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M105.717 134.178H115.616V82.3381H105.717V134.178Z" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M87.3458 134.178H97.2279V96.9019H87.3458V134.178Z" fill="#a5b4fc"></path>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M146.705 0L90.834 63.307L83.1227 56.057L72 68.585L90.748 87L158 11.136L146.705 0Z" fill="#a5b4fc"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
@@ -1,47 +0,0 @@
|
||||
(function (e, t) {
|
||||
var n = e.amplitude || { _q: [], _iq: {} }; var r = t.createElement("script")
|
||||
; r.type = "text/javascript"
|
||||
; r.integrity = "sha384-vYYnQ3LPdp/RkQjoKBTGSq0X5F73gXU3G2QopHaIfna0Ct1JRWzwrmEz115NzOta"
|
||||
; r.crossOrigin = "anonymous"; r.async = true
|
||||
; r.src = "https://cdn.amplitude.com/libs/amplitude-5.8.0-min.gz.js"
|
||||
; r.onload = function () {
|
||||
if (!e.amplitude.runQueuedFunctions) {
|
||||
console.log("[Amplitude] Error: could not load SDK")
|
||||
}
|
||||
}
|
||||
; var i = t.getElementsByTagName("script")[0]; i.parentNode.insertBefore(r, i)
|
||||
; function s(e, t) {
|
||||
e.prototype[t] = function () {
|
||||
this._q.push([t].concat(Array.prototype.slice.call(arguments, 0))); return this
|
||||
}
|
||||
}
|
||||
var o = function () { this._q = []; return this }
|
||||
; var a = ["add", "append", "clearAll", "prepend", "set", "setOnce", "unset"]
|
||||
; for (var u = 0; u < a.length; u++) { s(o, a[u]) } n.Identify = o; var c = function () {
|
||||
this._q = []
|
||||
; return this
|
||||
}
|
||||
; var l = ["setProductId", "setQuantity", "setPrice", "setRevenueType", "setEventProperties"]
|
||||
; for (var p = 0; p < l.length; p++) { s(c, l[p]) } n.Revenue = c
|
||||
; var d = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "enableTracking", "setGlobalUserProperties", "identify", "clearUserProperties", "setGroup", "logRevenueV2", "regenerateDeviceId", "groupIdentify", "onInit", "logEventWithTimestamp", "logEventWithGroups", "setSessionId", "resetSessionId"]
|
||||
; function v(e) {
|
||||
function t(t) {
|
||||
e[t] = function () {
|
||||
e._q.push([t].concat(Array.prototype.slice.call(arguments, 0)))
|
||||
}
|
||||
}
|
||||
for (var n = 0; n < d.length; n++) { t(d[n]) }
|
||||
} v(n); n.getInstance = function (e) {
|
||||
e = (!e || e.length === 0 ? "$default_instance" : e).toLowerCase()
|
||||
; if (!n._iq.hasOwnProperty(e)) { n._iq[e] = { _q: [] }; v(n._iq[e]) } return n._iq[e]
|
||||
}
|
||||
; e.amplitude = n
|
||||
})(window, document);
|
||||
|
||||
amplitude.getInstance().init("802d95003af23aad17ed068b6cfdeb2b", null, {
|
||||
// include referrer information in amplitude.
|
||||
saveEvents: true,
|
||||
includeUtm: true,
|
||||
includeReferrer: true,
|
||||
includeGclid: true
|
||||
});
|
||||
@@ -1,94 +1,95 @@
|
||||
function openTab(evt, tabName) {
|
||||
// Declare all variables
|
||||
let i;
|
||||
// Declare all variables
|
||||
let i;
|
||||
|
||||
// Get all elements with class="tabcontent" and hide them
|
||||
const tabcontent = document.getElementsByClassName('tabcontent');
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].className = tabcontent[i].className.replace(' active', '');
|
||||
}
|
||||
// Get all elements with class="tabcontent" and hide them
|
||||
const tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].className = tabcontent[i].className.replace(" active", "");
|
||||
}
|
||||
|
||||
// Get all elements with class="tablinks" and remove the class "active"
|
||||
const tablinks = document.getElementsByClassName('tablinks');
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(' active', '');
|
||||
}
|
||||
// Get all elements with class="tablinks" and remove the class "active"
|
||||
const tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||
}
|
||||
|
||||
// Show the current tab, and add an "active" class to the link that opened the tab
|
||||
// Show the current tab, and add an "active" class to the link that opened the tab
|
||||
|
||||
document.getElementById(tabName).className += ' active';
|
||||
evt.currentTarget.className += ' active';
|
||||
document.getElementById(tabName).className += " active";
|
||||
evt.currentTarget.className += " active";
|
||||
|
||||
setTimeout(() => document.getElementById(tabName + '1').parentNode.click(), 200);
|
||||
setTimeout(
|
||||
() => document.getElementById(tabName + "1").parentNode.click(),
|
||||
200,
|
||||
);
|
||||
}
|
||||
function openTooltip(name) {
|
||||
// Declare all variables
|
||||
let i;
|
||||
const element = document.getElementById(name);
|
||||
// Declare all variables
|
||||
let i;
|
||||
const element = document.getElementById(name);
|
||||
|
||||
const elclass = element.className;
|
||||
const elclass = element.className;
|
||||
|
||||
const tooltip = document.getElementsByClassName('tooltiptext');
|
||||
for (i = 0; i < tooltip.length; i++) {
|
||||
tooltip[i].className = tooltip[i].className.replace(' active', '');
|
||||
}
|
||||
if (elclass.indexOf('active') > -1) {
|
||||
|
||||
element.className = element.className.replace(' active', '');
|
||||
}
|
||||
else {
|
||||
|
||||
element.classList.add('active');
|
||||
}
|
||||
const tooltip = document.getElementsByClassName("tooltiptext");
|
||||
for (i = 0; i < tooltip.length; i++) {
|
||||
tooltip[i].className = tooltip[i].className.replace(" active", "");
|
||||
}
|
||||
if (elclass.indexOf("active") > -1) {
|
||||
element.className = element.className.replace(" active", "");
|
||||
} else {
|
||||
element.classList.add("active");
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
animateHTML().init();
|
||||
const tooltext = document.getElementsByClassName('tooltiptext');
|
||||
for (let i = 0; i < tooltext.length; i++) {
|
||||
animateHTML().init();
|
||||
const tooltext = document.getElementsByClassName("tooltiptext");
|
||||
for (let i = 0; i < tooltext.length; i++) {
|
||||
tooltext[i].onclick = function (e) {
|
||||
e.stopPropagation();
|
||||
};
|
||||
}
|
||||
|
||||
tooltext[i].onclick = function (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
document.getElementsByTagName("body")[0].onclick = function (e) {
|
||||
if (
|
||||
e.target.className !== "popover-dot" &&
|
||||
e.target.className !== "tooltiptext" &&
|
||||
e.target.className !== "tablinks active"
|
||||
) {
|
||||
const tooltip = document.getElementsByClassName("tooltiptext");
|
||||
for (let i = 0; i < tooltip.length; i++) {
|
||||
tooltip[i].className = tooltip[i].className.replace(" active", "");
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementsByTagName('body')[0].onclick = function (e) {
|
||||
|
||||
if (e.target.className !== 'popover-dot' && e.target.className !== 'tooltiptext' && e.target.className !== 'tablinks active') {
|
||||
const tooltip = document.getElementsByClassName('tooltiptext');
|
||||
for (let i = 0; i < tooltip.length; i++) {
|
||||
tooltip[i].className = tooltip[i].className.replace(' active', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const animateHTML = function () {
|
||||
let elem, windowHeight;
|
||||
const init = function () {
|
||||
elem = document.getElementById('Statuspage');
|
||||
windowHeight = window.innerHeight;
|
||||
_addEventHandlers();
|
||||
let elem, windowHeight;
|
||||
const init = function () {
|
||||
elem = document.getElementById("Statuspage");
|
||||
windowHeight = window.innerHeight;
|
||||
_addEventHandlers();
|
||||
};
|
||||
const _addEventHandlers = function () {
|
||||
window.addEventListener("scroll", _checkPosition);
|
||||
window.addEventListener("resize", init);
|
||||
};
|
||||
const _checkPosition = function () {
|
||||
if (!elem) {
|
||||
return;
|
||||
}
|
||||
const _addEventHandlers = function () {
|
||||
window.addEventListener('scroll', _checkPosition)
|
||||
window.addEventListener('resize', init)
|
||||
}
|
||||
const _checkPosition = function () {
|
||||
if (!elem) {
|
||||
return;
|
||||
}
|
||||
const posFromTop = elem.getBoundingClientRect().top;
|
||||
const posFromTop = elem.getBoundingClientRect().top;
|
||||
|
||||
if (posFromTop - windowHeight <= -400) {
|
||||
|
||||
document.getElementById('Statuspage1').parentNode.click();
|
||||
window.removeEventListener('scroll', _checkPosition);
|
||||
window.removeEventListener('resize', init);
|
||||
return;
|
||||
}
|
||||
if (posFromTop - windowHeight <= -400) {
|
||||
document.getElementById("Statuspage1").parentNode.click();
|
||||
window.removeEventListener("scroll", _checkPosition);
|
||||
window.removeEventListener("resize", init);
|
||||
return;
|
||||
}
|
||||
return {
|
||||
init: init
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
init: init,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This is basicaly meant to get a cookie by name
|
||||
var getCookiebyName = function (name) {
|
||||
var pair = document.cookie.match(new RegExp(name + '=([^;]+)'));
|
||||
return !!pair ? pair[1] : null;
|
||||
};
|
||||
var pair = document.cookie.match(new RegExp(name + "=([^;]+)"));
|
||||
return pair ? pair[1] : null;
|
||||
};
|
||||
|
||||
@@ -1,69 +1,49 @@
|
||||
!(function () {
|
||||
function n(n, e) {
|
||||
$(".hidden", n)
|
||||
.eq(e)
|
||||
.css({
|
||||
transitionDelay: Math.random() + Math.random() + "s",
|
||||
transitionDuration: 2 * Math.random() + 0.2 + "s",
|
||||
}),
|
||||
$(".hidden", n).eq(e).attr("class", "shown");
|
||||
}
|
||||
|
||||
! function () {
|
||||
function n(n, e) {
|
||||
|
||||
$('.hidden', n)
|
||||
.eq(e)
|
||||
.css({
|
||||
transitionDelay: Math.random() + Math.random() + 's',
|
||||
transitionDuration: 2 * Math.random() + .2 + 's'
|
||||
|
||||
}), $('.hidden', n)
|
||||
.eq(e)
|
||||
.attr('class', 'shown')
|
||||
function e(n, e) {
|
||||
if (n.hasClass("is-visible")) {
|
||||
const a = $(".shown", n).eq(e);
|
||||
a.attr("class", "hidden"),
|
||||
setTimeout(function () {
|
||||
a.attr("class", "shown");
|
||||
}, 3e3);
|
||||
}
|
||||
|
||||
function e(n, e) {
|
||||
if (n.hasClass('is-visible')) {
|
||||
|
||||
const a = $('.shown', n)
|
||||
.eq(e);
|
||||
a.attr('class', 'hidden'), setTimeout(function () {
|
||||
a.attr('class', 'shown')
|
||||
}, 3e3)
|
||||
}
|
||||
}
|
||||
|
||||
$(".card").each(function (e, a) {
|
||||
if (window.IntersectionObserver)
|
||||
(a.observer = new IntersectionObserver((e) => {
|
||||
e.forEach((e) => {
|
||||
if (e.isIntersecting || e.intersectionRatio > 0) {
|
||||
$(a).addClass("is-visible");
|
||||
|
||||
for (let t = $(".hidden", a).length; t >= 0; t--) n(a, t);
|
||||
} else $(a).removeClass("is-visible");
|
||||
});
|
||||
})),
|
||||
a.observer.observe(a);
|
||||
else {
|
||||
$(a).addClass("is-visible");
|
||||
|
||||
for (let t = $(".hidden", a).length; t >= 0; t--) n(a, t);
|
||||
}
|
||||
|
||||
$('.card')
|
||||
.each(function (e, a) {
|
||||
if (window.IntersectionObserver) a.observer = new IntersectionObserver(e => {
|
||||
e.forEach(e => {
|
||||
if (e.isIntersecting || e.intersectionRatio > 0) {
|
||||
|
||||
$(a)
|
||||
.addClass('is-visible');
|
||||
|
||||
for (let t = $('.hidden', a)
|
||||
.length; t >= 0; t--) n(a, t)
|
||||
|
||||
} else $(a)
|
||||
.removeClass('is-visible')
|
||||
})
|
||||
}), a.observer.observe(a);
|
||||
else {
|
||||
|
||||
$(a)
|
||||
.addClass('is-visible');
|
||||
|
||||
for (let t = $('.hidden', a)
|
||||
.length; t >= 0; t--) n(a, t)
|
||||
}
|
||||
}), setInterval(function () {
|
||||
|
||||
let n = $('.card')
|
||||
|
||||
.eq(Math.floor(Math.random() * $('.card')
|
||||
.length));
|
||||
|
||||
e(n, Math.floor(Math.random() * $('.shown', n)
|
||||
.length));
|
||||
|
||||
n = $('.card')
|
||||
|
||||
.eq(Math.floor(Math.random() * $('.card')
|
||||
.length));
|
||||
|
||||
e(n, Math.floor(Math.random() * $('.shown', n)
|
||||
.length))
|
||||
}, 600)
|
||||
}();
|
||||
}),
|
||||
setInterval(function () {
|
||||
let n = $(".card").eq(Math.floor(Math.random() * $(".card").length));
|
||||
|
||||
e(n, Math.floor(Math.random() * $(".shown", n).length));
|
||||
|
||||
n = $(".card").eq(Math.floor(Math.random() * $(".card").length));
|
||||
|
||||
e(n, Math.floor(Math.random() * $(".shown", n).length));
|
||||
}, 600);
|
||||
})();
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
|
||||
let accountsUrl = window.location.origin+'/accounts';
|
||||
let backendUrl = window.location.hostname==='localhost'? 'http://localhost:3002': window.location.origin+'/api'
|
||||
|
||||
let accountsUrl = window.location.origin + "/accounts";
|
||||
let backendUrl =
|
||||
window.location.hostname === "localhost"
|
||||
? "http://localhost:3002"
|
||||
: window.location.origin + "/api";
|
||||
|
||||
//eslint-disable-next-line
|
||||
function loginUrl(extra) {
|
||||
if (extra) {
|
||||
window.location.href = `${accountsUrl}/login${extra}`;
|
||||
}
|
||||
else {
|
||||
window.location.href = `${accountsUrl}/login`;
|
||||
}
|
||||
if (extra) {
|
||||
window.location.href = `${accountsUrl}/login${extra}`;
|
||||
} else {
|
||||
window.location.href = `${accountsUrl}/login`;
|
||||
}
|
||||
}
|
||||
//eslint-disable-next-line
|
||||
function registerUrl(params) {
|
||||
if (params) {
|
||||
window.location.href = `${accountsUrl}/register${params}`;
|
||||
}
|
||||
else {
|
||||
window.location.href = `${accountsUrl}/register`;
|
||||
}
|
||||
if (params) {
|
||||
window.location.href = `${accountsUrl}/register${params}`;
|
||||
} else {
|
||||
window.location.href = `${accountsUrl}/register`;
|
||||
}
|
||||
}
|
||||
//eslint-disable-next-line
|
||||
function formUrl() {
|
||||
return `${backendUrl}/lead/`;
|
||||
return `${backendUrl}/lead/`;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
test('two plus two is four', () => {
|
||||
expect(2 + 2).toBe(4);
|
||||
test("two plus two is four", () => {
|
||||
expect(2 + 2).toBe(4);
|
||||
});
|
||||
|
||||
@@ -1,453 +1,447 @@
|
||||
import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel';
|
||||
import BaseModel from 'Common/Models/BaseModel';
|
||||
import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
|
||||
import HTTPResponse from 'Common/Types/API/HTTPResponse';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import { JSONArray, JSONObject, JSONObjectOrArray } from 'Common/Types/JSON';
|
||||
import JSONFunctions from 'Common/Types/JSONFunctions';
|
||||
import Text from 'Common/Types/Text';
|
||||
import API from 'Common/Utils/API';
|
||||
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
|
||||
import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown';
|
||||
import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel";
|
||||
import BaseModel from "Common/Models/BaseModel";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONArray, JSONObject, JSONObjectOrArray } from "Common/Types/JSON";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import Text from "Common/Types/Text";
|
||||
import API from "Common/Utils/API";
|
||||
import LocalCache from "CommonServer/Infrastructure/LocalCache";
|
||||
import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown";
|
||||
|
||||
export interface BlogPostAuthor {
|
||||
username: string;
|
||||
githubUrl: string;
|
||||
profileImageUrl: string;
|
||||
name: string;
|
||||
username: string;
|
||||
githubUrl: string;
|
||||
profileImageUrl: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface BlogPostBaseProps {
|
||||
title: string;
|
||||
description: string;
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
formattedPostDate: string;
|
||||
fileName: string;
|
||||
tags: string[];
|
||||
postDate: string;
|
||||
blogUrl: string;
|
||||
formattedPostDate: string;
|
||||
fileName: string;
|
||||
tags: string[];
|
||||
postDate: string;
|
||||
blogUrl: string;
|
||||
}
|
||||
|
||||
export interface BlogPostHeader extends BlogPostBaseProps {
|
||||
authorGitHubUsername: string;
|
||||
authorGitHubUsername: string;
|
||||
}
|
||||
|
||||
export interface BlogPost extends BlogPostBaseProps {
|
||||
htmlBody: string;
|
||||
markdownBody: string;
|
||||
socialMediaImageUrl: string;
|
||||
author: BlogPostAuthor | null;
|
||||
htmlBody: string;
|
||||
markdownBody: string;
|
||||
socialMediaImageUrl: string;
|
||||
author: BlogPostAuthor | null;
|
||||
}
|
||||
|
||||
const GitHubRawUrl: string =
|
||||
'https://raw.githubusercontent.com/oneuptime/blog/master';
|
||||
"https://raw.githubusercontent.com/oneuptime/blog/master";
|
||||
|
||||
export default class BlogPostUtil {
|
||||
public static async getBlogPostList(
|
||||
tagName?: string | undefined
|
||||
): Promise<BlogPostHeader[]> {
|
||||
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/Blogs.json`);
|
||||
public static async getBlogPostList(
|
||||
tagName?: string | undefined,
|
||||
): Promise<BlogPostHeader[]> {
|
||||
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/Blogs.json`);
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
let jsonContent: string | JSONArray =
|
||||
(fileData.data as string | JSONArray) || [];
|
||||
|
||||
if (typeof jsonContent === 'string') {
|
||||
jsonContent = JSONFunctions.parseJSONArray(jsonContent);
|
||||
}
|
||||
|
||||
const blogs: Array<JSONObject> = JSONFunctions.deserializeArray(
|
||||
jsonContent as Array<JSONObject>
|
||||
).reverse(); // reverse so new content comes first
|
||||
|
||||
const resultList: Array<BlogPostHeader> = [];
|
||||
|
||||
for (const blog of blogs) {
|
||||
const fileName: string = blog['post'] as string;
|
||||
const formattedPostDate: string =
|
||||
this.getFormattedPostDateFromFileName(fileName);
|
||||
const postDate: string = this.getPostDateFromFileName(fileName);
|
||||
|
||||
resultList.push({
|
||||
title: blog['title'] as string,
|
||||
description: blog['description'] as string,
|
||||
fileName,
|
||||
formattedPostDate,
|
||||
postDate,
|
||||
tags: blog['tags'] as string[],
|
||||
authorGitHubUsername: blog['authorGitHubUsername'] as string,
|
||||
blogUrl: `/blog/post/${fileName}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (tagName) {
|
||||
return resultList.filter((blog: BlogPostHeader) => {
|
||||
return blog.tags
|
||||
.map((item: string) => {
|
||||
return Text.replaceAll(item.toLowerCase(), ' ', '-');
|
||||
})
|
||||
.includes(tagName);
|
||||
});
|
||||
}
|
||||
|
||||
return resultList;
|
||||
if (fileData.isFailure()) {
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
public static async getBlogPost(
|
||||
fileName: string
|
||||
): Promise<BlogPost | null> {
|
||||
let blogPost: BlogPost | null = this.getBlogPostFromCache(fileName);
|
||||
let jsonContent: string | JSONArray =
|
||||
(fileData.data as string | JSONArray) || [];
|
||||
|
||||
// if (blogPost) {
|
||||
// return Promise.resolve(blogPost);
|
||||
// }
|
||||
|
||||
blogPost = await this.getBlogPostFromGitHub(fileName);
|
||||
|
||||
// save this to cache
|
||||
LocalCache.setJSON(
|
||||
'blog',
|
||||
fileName,
|
||||
JSONFunctions.serialize(blogPost as any)
|
||||
);
|
||||
|
||||
return blogPost;
|
||||
if (typeof jsonContent === "string") {
|
||||
jsonContent = JSONFunctions.parseJSONArray(jsonContent);
|
||||
}
|
||||
|
||||
public static async getNameOfGitHubUser(username: string): Promise<string> {
|
||||
const fileUrl: URL = URL.fromString(
|
||||
`https://api.github.com/users/${username}`
|
||||
);
|
||||
const blogs: Array<JSONObject> = JSONFunctions.deserializeArray(
|
||||
jsonContent as Array<JSONObject>,
|
||||
).reverse(); // reverse so new content comes first
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
const resultList: Array<BlogPostHeader> = [];
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
for (const blog of blogs) {
|
||||
const fileName: string = blog["post"] as string;
|
||||
const formattedPostDate: string =
|
||||
this.getFormattedPostDateFromFileName(fileName);
|
||||
const postDate: string = this.getPostDateFromFileName(fileName);
|
||||
|
||||
const name: string =
|
||||
(fileData.data as JSONObject)?.['name']?.toString() || '';
|
||||
return name;
|
||||
resultList.push({
|
||||
title: blog["title"] as string,
|
||||
description: blog["description"] as string,
|
||||
fileName,
|
||||
formattedPostDate,
|
||||
postDate,
|
||||
tags: blog["tags"] as string[],
|
||||
authorGitHubUsername: blog["authorGitHubUsername"] as string,
|
||||
blogUrl: `/blog/post/${fileName}`,
|
||||
});
|
||||
}
|
||||
|
||||
public static async getGitHubMarkdownFileContent(
|
||||
githubPath: string
|
||||
): Promise<string | null> {
|
||||
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/${githubPath}`);
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
if ((fileData as HTTPErrorResponse).statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
const markdownContent: string =
|
||||
(fileData.data as JSONObject)?.['data']?.toString() || '';
|
||||
return markdownContent;
|
||||
if (tagName) {
|
||||
return resultList.filter((blog: BlogPostHeader) => {
|
||||
return blog.tags
|
||||
.map((item: string) => {
|
||||
return Text.replaceAll(item.toLowerCase(), " ", "-");
|
||||
})
|
||||
.includes(tagName);
|
||||
});
|
||||
}
|
||||
|
||||
public static async getTags(): Promise<string[]> {
|
||||
// check if tags are in cache
|
||||
let tags: string[] = LocalCache.getJSON(
|
||||
'blog-tags',
|
||||
'tags'
|
||||
) as string[];
|
||||
return resultList;
|
||||
}
|
||||
|
||||
if (tags && tags.length > 0) {
|
||||
return tags;
|
||||
}
|
||||
public static async getBlogPost(fileName: string): Promise<BlogPost | null> {
|
||||
let blogPost: BlogPost | null = this.getBlogPostFromCache(fileName);
|
||||
|
||||
tags = await this.getAllTagsFromGitHub();
|
||||
// if (blogPost) {
|
||||
// return Promise.resolve(blogPost);
|
||||
// }
|
||||
|
||||
// save this to cache
|
||||
blogPost = await this.getBlogPostFromGitHub(fileName);
|
||||
|
||||
LocalCache.setJSON(
|
||||
'blog-tags',
|
||||
'tags',
|
||||
JSONFunctions.serialize(tags as any)
|
||||
);
|
||||
// save this to cache
|
||||
LocalCache.setJSON(
|
||||
"blog",
|
||||
fileName,
|
||||
JSONFunctions.serialize(blogPost as any),
|
||||
);
|
||||
|
||||
return tags;
|
||||
return blogPost;
|
||||
}
|
||||
|
||||
public static async getNameOfGitHubUser(username: string): Promise<string> {
|
||||
const fileUrl: URL = URL.fromString(
|
||||
`https://api.github.com/users/${username}`,
|
||||
);
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
public static async getAllTagsFromGitHub(): Promise<string[]> {
|
||||
const tagsMarkdownContent: string | null =
|
||||
await this.getGitHubMarkdownFileContent('Tags.md');
|
||||
const name: string =
|
||||
(fileData.data as JSONObject)?.["name"]?.toString() || "";
|
||||
return name;
|
||||
}
|
||||
|
||||
if (!tagsMarkdownContent) {
|
||||
return [];
|
||||
}
|
||||
public static async getGitHubMarkdownFileContent(
|
||||
githubPath: string,
|
||||
): Promise<string | null> {
|
||||
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/${githubPath}`);
|
||||
|
||||
const tags: Array<string> = tagsMarkdownContent
|
||||
.split('\n')
|
||||
.map((tag: string) => {
|
||||
return tag.trim();
|
||||
})
|
||||
.filter((tag: string) => {
|
||||
return tag.startsWith('-');
|
||||
})
|
||||
.map((tag: string) => {
|
||||
return tag.replace('-', '').trim();
|
||||
});
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
return tags;
|
||||
if (fileData.isFailure()) {
|
||||
if ((fileData as HTTPErrorResponse).statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
public static async getBlogPostFromGitHub(
|
||||
fileName: string
|
||||
): Promise<BlogPost | null> {
|
||||
const fileUrl: URL = URL.fromString(
|
||||
`${GitHubRawUrl}/posts/${fileName}/README.md`
|
||||
);
|
||||
const markdownContent: string =
|
||||
(fileData.data as JSONObject)?.["data"]?.toString() || "";
|
||||
return markdownContent;
|
||||
}
|
||||
|
||||
const postDate: string = this.getPostDateFromFileName(fileName);
|
||||
const formattedPostDate: string =
|
||||
this.getFormattedPostDateFromFileName(fileName);
|
||||
public static async getTags(): Promise<string[]> {
|
||||
// check if tags are in cache
|
||||
let tags: string[] = LocalCache.getJSON("blog-tags", "tags") as string[];
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
if ((fileData as HTTPErrorResponse).statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
let markdownContent: string =
|
||||
(fileData.data as JSONObject)?.['data']?.toString() || '';
|
||||
|
||||
const blogPostAuthor: BlogPostAuthor | null =
|
||||
await this.getAuthorFromFileContent(markdownContent);
|
||||
|
||||
const title: string = this.getTitleFromFileContent(markdownContent);
|
||||
const description: string =
|
||||
this.getDescriptionFromFileContent(markdownContent);
|
||||
const tags: Array<string> =
|
||||
this.getTagsFromFileContent(markdownContent);
|
||||
|
||||
markdownContent = this.getPostFromMarkdown(markdownContent);
|
||||
|
||||
const htmlBody: string = await Markdown.convertToHTML(
|
||||
markdownContent,
|
||||
MarkdownContentType.Blog
|
||||
);
|
||||
|
||||
const blogPost: BlogPost = {
|
||||
title,
|
||||
description,
|
||||
author: blogPostAuthor,
|
||||
htmlBody,
|
||||
markdownBody: markdownContent,
|
||||
fileName,
|
||||
tags,
|
||||
postDate,
|
||||
formattedPostDate,
|
||||
socialMediaImageUrl: `${GitHubRawUrl}/posts/${fileName}/social-media.png`,
|
||||
blogUrl: `https://oneuptime.com/blog/post/${fileName}`, // this has to be oneuptime.com because its used in twitter cards and faceboomk cards. Please dont change this.
|
||||
};
|
||||
|
||||
return blogPost;
|
||||
if (tags && tags.length > 0) {
|
||||
return tags;
|
||||
}
|
||||
|
||||
private static getPostDateFromFileName(fileName: string): string {
|
||||
const year: string | undefined = fileName.split('-')[0];
|
||||
const month: string | undefined = fileName.split('-')[1];
|
||||
const day: string | undefined = fileName.split('-')[2];
|
||||
tags = await this.getAllTagsFromGitHub();
|
||||
|
||||
if (!year || !month || !day) {
|
||||
throw new BadDataException('Invalid file name');
|
||||
}
|
||||
// save this to cache
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
LocalCache.setJSON(
|
||||
"blog-tags",
|
||||
"tags",
|
||||
JSONFunctions.serialize(tags as any),
|
||||
);
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
public static async getAllTagsFromGitHub(): Promise<string[]> {
|
||||
const tagsMarkdownContent: string | null =
|
||||
await this.getGitHubMarkdownFileContent("Tags.md");
|
||||
|
||||
if (!tagsMarkdownContent) {
|
||||
return [];
|
||||
}
|
||||
|
||||
private static getFormattedPostDateFromFileName(fileName: string): string {
|
||||
// file name is of the format YYYY-MM-DD-Title.md
|
||||
const year: string | undefined = fileName.split('-')[0];
|
||||
const month: string | undefined = fileName.split('-')[1];
|
||||
const day: string | undefined = fileName.split('-')[2];
|
||||
const tags: Array<string> = tagsMarkdownContent
|
||||
.split("\n")
|
||||
.map((tag: string) => {
|
||||
return tag.trim();
|
||||
})
|
||||
.filter((tag: string) => {
|
||||
return tag.startsWith("-");
|
||||
})
|
||||
.map((tag: string) => {
|
||||
return tag.replace("-", "").trim();
|
||||
});
|
||||
|
||||
if (!year || !month || !day) {
|
||||
throw new BadDataException('Invalid file name');
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
const date: Date = OneUptimeDate.getDateFromYYYYMMDD(year, month, day);
|
||||
return OneUptimeDate.getDateAsLocalFormattedString(date, true);
|
||||
public static async getBlogPostFromGitHub(
|
||||
fileName: string,
|
||||
): Promise<BlogPost | null> {
|
||||
const fileUrl: URL = URL.fromString(
|
||||
`${GitHubRawUrl}/posts/${fileName}/README.md`,
|
||||
);
|
||||
|
||||
const postDate: string = this.getPostDateFromFileName(fileName);
|
||||
const formattedPostDate: string =
|
||||
this.getFormattedPostDateFromFileName(fileName);
|
||||
|
||||
const fileData:
|
||||
| HTTPResponse<
|
||||
| JSONObjectOrArray
|
||||
| BaseModel
|
||||
| BaseModel[]
|
||||
| AnalyticsBaseModel
|
||||
| AnalyticsBaseModel[]
|
||||
>
|
||||
| HTTPErrorResponse = await API.get(fileUrl);
|
||||
|
||||
if (fileData.isFailure()) {
|
||||
if ((fileData as HTTPErrorResponse).statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw fileData as HTTPErrorResponse;
|
||||
}
|
||||
|
||||
private static getPostFromMarkdown(markdownContent: string): string {
|
||||
const authorLine: string | undefined = markdownContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('Author:');
|
||||
});
|
||||
const titleLine: string | undefined = markdownContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('#');
|
||||
});
|
||||
const descriptionLine: string | undefined =
|
||||
markdownContent.split('\n').find((line: string) => {
|
||||
return line.startsWith('Description:');
|
||||
}) || '';
|
||||
let markdownContent: string =
|
||||
(fileData.data as JSONObject)?.["data"]?.toString() || "";
|
||||
|
||||
const tagsLine: string | undefined =
|
||||
markdownContent.split('\n').find((line: string) => {
|
||||
return line.startsWith('Tags:');
|
||||
}) || '';
|
||||
const blogPostAuthor: BlogPostAuthor | null =
|
||||
await this.getAuthorFromFileContent(markdownContent);
|
||||
|
||||
if (!authorLine && !titleLine && !descriptionLine && !tagsLine) {
|
||||
return markdownContent;
|
||||
}
|
||||
const title: string = this.getTitleFromFileContent(markdownContent);
|
||||
const description: string =
|
||||
this.getDescriptionFromFileContent(markdownContent);
|
||||
const tags: Array<string> = this.getTagsFromFileContent(markdownContent);
|
||||
|
||||
const lines: string[] = markdownContent.split('\n');
|
||||
markdownContent = this.getPostFromMarkdown(markdownContent);
|
||||
|
||||
if (authorLine) {
|
||||
const authorLineIndex: number = lines.indexOf(authorLine);
|
||||
lines.splice(authorLineIndex, 1);
|
||||
}
|
||||
const htmlBody: string = await Markdown.convertToHTML(
|
||||
markdownContent,
|
||||
MarkdownContentType.Blog,
|
||||
);
|
||||
|
||||
if (titleLine) {
|
||||
const titleLineIndex: number = lines.indexOf(titleLine);
|
||||
lines.splice(titleLineIndex, 1);
|
||||
}
|
||||
const blogPost: BlogPost = {
|
||||
title,
|
||||
description,
|
||||
author: blogPostAuthor,
|
||||
htmlBody,
|
||||
markdownBody: markdownContent,
|
||||
fileName,
|
||||
tags,
|
||||
postDate,
|
||||
formattedPostDate,
|
||||
socialMediaImageUrl: `${GitHubRawUrl}/posts/${fileName}/social-media.png`,
|
||||
blogUrl: `https://oneuptime.com/blog/post/${fileName}`, // this has to be oneuptime.com because its used in twitter cards and faceboomk cards. Please dont change this.
|
||||
};
|
||||
|
||||
if (descriptionLine) {
|
||||
const descriptionLineIndex: number = lines.indexOf(descriptionLine);
|
||||
lines.splice(descriptionLineIndex, 1);
|
||||
}
|
||||
return blogPost;
|
||||
}
|
||||
|
||||
if (tagsLine) {
|
||||
const tagsLineIndex: number = lines.indexOf(tagsLine);
|
||||
lines.splice(tagsLineIndex, 1);
|
||||
}
|
||||
private static getPostDateFromFileName(fileName: string): string {
|
||||
const year: string | undefined = fileName.split("-")[0];
|
||||
const month: string | undefined = fileName.split("-")[1];
|
||||
const day: string | undefined = fileName.split("-")[2];
|
||||
|
||||
return lines.join('\n').trim();
|
||||
if (!year || !month || !day) {
|
||||
throw new BadDataException("Invalid file name");
|
||||
}
|
||||
|
||||
public static getBlogPostFromCache(fileName: string): BlogPost | null {
|
||||
const blogPost: BlogPost | null = LocalCache.getJSON(
|
||||
'blog',
|
||||
fileName
|
||||
) as BlogPost | null;
|
||||
return blogPost;
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
private static getFormattedPostDateFromFileName(fileName: string): string {
|
||||
// file name is of the format YYYY-MM-DD-Title.md
|
||||
const year: string | undefined = fileName.split("-")[0];
|
||||
const month: string | undefined = fileName.split("-")[1];
|
||||
const day: string | undefined = fileName.split("-")[2];
|
||||
|
||||
if (!year || !month || !day) {
|
||||
throw new BadDataException("Invalid file name");
|
||||
}
|
||||
|
||||
public static getTitleFromFileContent(fileContent: string): string {
|
||||
// title is the first line that stars with "#"
|
||||
const date: Date = OneUptimeDate.getDateFromYYYYMMDD(year, month, day);
|
||||
return OneUptimeDate.getDateAsLocalFormattedString(date, true);
|
||||
}
|
||||
|
||||
const titleLine: string =
|
||||
fileContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('#');
|
||||
})
|
||||
?.replace('#', '') || 'OneUptime Blog';
|
||||
private static getPostFromMarkdown(markdownContent: string): string {
|
||||
const authorLine: string | undefined = markdownContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("Author:");
|
||||
});
|
||||
const titleLine: string | undefined = markdownContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("#");
|
||||
});
|
||||
const descriptionLine: string | undefined =
|
||||
markdownContent.split("\n").find((line: string) => {
|
||||
return line.startsWith("Description:");
|
||||
}) || "";
|
||||
|
||||
return titleLine;
|
||||
const tagsLine: string | undefined =
|
||||
markdownContent.split("\n").find((line: string) => {
|
||||
return line.startsWith("Tags:");
|
||||
}) || "";
|
||||
|
||||
if (!authorLine && !titleLine && !descriptionLine && !tagsLine) {
|
||||
return markdownContent;
|
||||
}
|
||||
|
||||
public static getTagsFromFileContent(fileContent: string): string[] {
|
||||
// tags is the first line that starts with "Tags:"
|
||||
const lines: string[] = markdownContent.split("\n");
|
||||
|
||||
const tagsLine: string | undefined =
|
||||
fileContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('Tags:');
|
||||
})
|
||||
?.replace('Tags:', '') || '';
|
||||
|
||||
return tagsLine.split(',').map((tag: string) => {
|
||||
return tag.trim();
|
||||
});
|
||||
if (authorLine) {
|
||||
const authorLineIndex: number = lines.indexOf(authorLine);
|
||||
lines.splice(authorLineIndex, 1);
|
||||
}
|
||||
|
||||
public static getDescriptionFromFileContent(fileContent: string): string {
|
||||
// description is the first line that starts with ">"
|
||||
|
||||
const descriptionLine: string | undefined =
|
||||
fileContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('Description:');
|
||||
})
|
||||
?.replace('Description:', '') || '';
|
||||
|
||||
return descriptionLine;
|
||||
if (titleLine) {
|
||||
const titleLineIndex: number = lines.indexOf(titleLine);
|
||||
lines.splice(titleLineIndex, 1);
|
||||
}
|
||||
|
||||
public static async getAuthorFromFileContent(
|
||||
fileContent: string
|
||||
): Promise<BlogPostAuthor | null> {
|
||||
// author line is in this format: Author: [username](githubUrl)
|
||||
|
||||
const authorLine: string | undefined = fileContent
|
||||
.split('\n')
|
||||
.find((line: string) => {
|
||||
return line.startsWith('Author:');
|
||||
});
|
||||
const authorUsername: string | undefined = authorLine
|
||||
?.split('[')[1]
|
||||
?.split(']')[0];
|
||||
const authorGitHubUrl: string | undefined = authorLine
|
||||
?.split('(')[1]
|
||||
?.split(')')[0];
|
||||
const authorProfileImageUrl: string = `https://avatars.githubusercontent.com/${authorUsername}`;
|
||||
|
||||
if (!authorUsername || !authorGitHubUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
username: authorUsername,
|
||||
githubUrl: authorGitHubUrl,
|
||||
profileImageUrl: authorProfileImageUrl,
|
||||
name: await this.getNameOfGitHubUser(authorUsername),
|
||||
};
|
||||
if (descriptionLine) {
|
||||
const descriptionLineIndex: number = lines.indexOf(descriptionLine);
|
||||
lines.splice(descriptionLineIndex, 1);
|
||||
}
|
||||
|
||||
if (tagsLine) {
|
||||
const tagsLineIndex: number = lines.indexOf(tagsLine);
|
||||
lines.splice(tagsLineIndex, 1);
|
||||
}
|
||||
|
||||
return lines.join("\n").trim();
|
||||
}
|
||||
|
||||
public static getBlogPostFromCache(fileName: string): BlogPost | null {
|
||||
const blogPost: BlogPost | null = LocalCache.getJSON(
|
||||
"blog",
|
||||
fileName,
|
||||
) as BlogPost | null;
|
||||
return blogPost;
|
||||
}
|
||||
|
||||
public static getTitleFromFileContent(fileContent: string): string {
|
||||
// title is the first line that stars with "#"
|
||||
|
||||
const titleLine: string =
|
||||
fileContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("#");
|
||||
})
|
||||
?.replace("#", "") || "OneUptime Blog";
|
||||
|
||||
return titleLine;
|
||||
}
|
||||
|
||||
public static getTagsFromFileContent(fileContent: string): string[] {
|
||||
// tags is the first line that starts with "Tags:"
|
||||
|
||||
const tagsLine: string | undefined =
|
||||
fileContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("Tags:");
|
||||
})
|
||||
?.replace("Tags:", "") || "";
|
||||
|
||||
return tagsLine.split(",").map((tag: string) => {
|
||||
return tag.trim();
|
||||
});
|
||||
}
|
||||
|
||||
public static getDescriptionFromFileContent(fileContent: string): string {
|
||||
// description is the first line that starts with ">"
|
||||
|
||||
const descriptionLine: string | undefined =
|
||||
fileContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("Description:");
|
||||
})
|
||||
?.replace("Description:", "") || "";
|
||||
|
||||
return descriptionLine;
|
||||
}
|
||||
|
||||
public static async getAuthorFromFileContent(
|
||||
fileContent: string,
|
||||
): Promise<BlogPostAuthor | null> {
|
||||
// author line is in this format: Author: [username](githubUrl)
|
||||
|
||||
const authorLine: string | undefined = fileContent
|
||||
.split("\n")
|
||||
.find((line: string) => {
|
||||
return line.startsWith("Author:");
|
||||
});
|
||||
const authorUsername: string | undefined = authorLine
|
||||
?.split("[")[1]
|
||||
?.split("]")[0];
|
||||
const authorGitHubUrl: string | undefined = authorLine
|
||||
?.split("(")[1]
|
||||
?.split(")")[0];
|
||||
const authorProfileImageUrl: string = `https://avatars.githubusercontent.com/${authorUsername}`;
|
||||
|
||||
if (!authorUsername || !authorGitHubUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
username: authorUsername,
|
||||
githubUrl: authorGitHubUrl,
|
||||
profileImageUrl: authorProfileImageUrl,
|
||||
name: await this.getNameOfGitHubUser(authorUsername),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export const ViewsPath: string = '/usr/src/app/FeatureSet/Home/Views';
|
||||
export const StaticPath: string = '/usr/src/app/FeatureSet/Home/Static';
|
||||
export const ViewsPath: string = "/usr/src/app/FeatureSet/Home/Views";
|
||||
export const StaticPath: string = "/usr/src/app/FeatureSet/Home/Static";
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ViewsPath } from './Config';
|
||||
import { ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from "./Config";
|
||||
import { ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
export default class NotFoundUtil {
|
||||
public static renderNotFound(res: ExpressResponse): void {
|
||||
res.status(404);
|
||||
res.render(`${ViewsPath}/not-found.ejs`, {
|
||||
footerCards: false,
|
||||
support: false,
|
||||
cta: false,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
});
|
||||
}
|
||||
public static renderNotFound(res: ExpressResponse): void {
|
||||
res.status(404);
|
||||
res.render(`${ViewsPath}/not-found.ejs`, {
|
||||
footerCards: false,
|
||||
support: false,
|
||||
cta: false,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
import { ViewsPath } from './Config';
|
||||
import { ExpressResponse } from 'CommonServer/Utils/Express';
|
||||
import { ViewsPath } from "./Config";
|
||||
import { ExpressResponse } from "CommonServer/Utils/Express";
|
||||
|
||||
export default class ServerErrorUtil {
|
||||
public static renderServerError(res: ExpressResponse): void {
|
||||
res.status(500);
|
||||
res.render(`${ViewsPath}/server-error.ejs`, {
|
||||
footerCards: false,
|
||||
support: false,
|
||||
cta: false,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
});
|
||||
}
|
||||
public static renderServerError(res: ExpressResponse): void {
|
||||
res.status(500);
|
||||
res.render(`${ViewsPath}/server-error.ejs`, {
|
||||
footerCards: false,
|
||||
support: false,
|
||||
cta: false,
|
||||
blackLogo: false,
|
||||
requestDemoCta: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<img class="h-6 mt-2" src="/img/viewsonic.svg" alt="ViewSonic">
|
||||
</div>
|
||||
<div class="mt-4 ml-8 flex justify-center flex-shrink-0 flex-grow lg:ml-4 lg:flex-grow-0">
|
||||
<img class="h-9 mt-1" src="/img/Siemens-logo.svg" alt="Siemens">
|
||||
<img class="h-12 -mt-4" src="/img/skillable-logo.svg" alt="Skillable">
|
||||
</div>
|
||||
<div class="mt-4 ml-8 flex justify-center flex-shrink-0 flex-grow lg:ml-4 lg:flex-grow-0">
|
||||
<img class="h-11 -mt-2" src="/img/sodexo.svg" alt="Sodexo">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,79 +1,79 @@
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import ResellerService from 'CommonServer/Services/ResellerService';
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ResellerService from "CommonServer/Services/ResellerService";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
import Reseller from 'Model/Models/Reseller';
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import Reseller from "Model/Models/Reseller";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post(
|
||||
'/reseller/auth/:resellerid',
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const resellerId: string | undefined = req.params['resellerid'];
|
||||
"/reseller/auth/:resellerid",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const resellerId: string | undefined = req.params["resellerid"];
|
||||
|
||||
if (!resellerId) {
|
||||
throw new BadDataException('Reseller ID not found');
|
||||
}
|
||||
if (!resellerId) {
|
||||
throw new BadDataException("Reseller ID not found");
|
||||
}
|
||||
|
||||
const username: string = req.body['username'];
|
||||
const password: string = req.body['password'];
|
||||
const username: string = req.body["username"];
|
||||
const password: string = req.body["password"];
|
||||
|
||||
if (!username) {
|
||||
throw new BadDataException('Username not found');
|
||||
}
|
||||
if (!username) {
|
||||
throw new BadDataException("Username not found");
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
throw new BadDataException('Password not found');
|
||||
}
|
||||
if (!password) {
|
||||
throw new BadDataException("Password not found");
|
||||
}
|
||||
|
||||
// get the reseller user.
|
||||
const reseller: Reseller | null = await ResellerService.findOneBy({
|
||||
query: {
|
||||
resellerId: resellerId,
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
resellerId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
// get the reseller user.
|
||||
const reseller: Reseller | null = await ResellerService.findOneBy({
|
||||
query: {
|
||||
resellerId: resellerId,
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
resellerId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!reseller) {
|
||||
throw new BadDataException(
|
||||
'Reseller not found or username and password is incorrect'
|
||||
);
|
||||
}
|
||||
if (!reseller) {
|
||||
throw new BadDataException(
|
||||
"Reseller not found or username and password is incorrect",
|
||||
);
|
||||
}
|
||||
|
||||
// if found then generate a token and return it.
|
||||
// if found then generate a token and return it.
|
||||
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: { resellerId: resellerId },
|
||||
expiresInSeconds: OneUptimeDate.getDayInSeconds(365),
|
||||
});
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: { resellerId: resellerId },
|
||||
expiresInSeconds: OneUptimeDate.getDayInSeconds(365),
|
||||
});
|
||||
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
access: token,
|
||||
});
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
return Response.sendJsonObjectResponse(req, res, {
|
||||
access: token,
|
||||
});
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,471 +1,527 @@
|
||||
import AuthenticationEmail from '../Utils/AuthenticationEmail';
|
||||
import SSOUtil from '../Utils/SSO';
|
||||
import { DashboardRoute } from 'Common/ServiceRoute';
|
||||
import Hostname from 'Common/Types/API/Hostname';
|
||||
import Protocol from 'Common/Types/API/Protocol';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import Email from 'Common/Types/Email';
|
||||
import BadRequestException from 'Common/Types/Exception/BadRequestException';
|
||||
import Exception from 'Common/Types/Exception/Exception';
|
||||
import ServerException from 'Common/Types/Exception/ServerException';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import PositiveNumber from 'Common/Types/PositiveNumber';
|
||||
import DatabaseConfig from 'CommonServer/DatabaseConfig';
|
||||
import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig';
|
||||
import AccessTokenService from 'CommonServer/Services/AccessTokenService';
|
||||
import ProjectSSOService from 'CommonServer/Services/ProjectSsoService';
|
||||
import TeamMemberService from 'CommonServer/Services/TeamMemberService';
|
||||
import UserService from 'CommonServer/Services/UserService';
|
||||
import CookieUtil from 'CommonServer/Utils/Cookie';
|
||||
import AuthenticationEmail from "../Utils/AuthenticationEmail";
|
||||
import SSOUtil from "../Utils/SSO";
|
||||
import { DashboardRoute } from "Common/ServiceRoute";
|
||||
import Hostname from "Common/Types/API/Hostname";
|
||||
import Protocol from "Common/Types/API/Protocol";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import Email from "Common/Types/Email";
|
||||
import BadRequestException from "Common/Types/Exception/BadRequestException";
|
||||
import Exception from "Common/Types/Exception/Exception";
|
||||
import ServerException from "Common/Types/Exception/ServerException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PositiveNumber from "Common/Types/PositiveNumber";
|
||||
import DatabaseConfig from "CommonServer/DatabaseConfig";
|
||||
import { Host, HttpProtocol } from "CommonServer/EnvironmentConfig";
|
||||
import AccessTokenService from "CommonServer/Services/AccessTokenService";
|
||||
import ProjectSSOService from "CommonServer/Services/ProjectSsoService";
|
||||
import TeamMemberService from "CommonServer/Services/TeamMemberService";
|
||||
import UserService from "CommonServer/Services/UserService";
|
||||
import QueryHelper from "CommonServer/Types/Database/QueryHelper";
|
||||
import CookieUtil from "CommonServer/Utils/Cookie";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
import ProjectSSO from 'Model/Models/ProjectSso';
|
||||
import TeamMember from 'Model/Models/TeamMember';
|
||||
import User from 'Model/Models/User';
|
||||
import xml2js from 'xml2js';
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import ProjectSSO from "Model/Models/ProjectSso";
|
||||
import TeamMember from "Model/Models/TeamMember";
|
||||
import User from "Model/Models/User";
|
||||
import xml2js from "xml2js";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
// This route is used to get the SSO config for the user.
|
||||
// when the user logs in from OneUptime and not from the IDP.
|
||||
|
||||
router.get(
|
||||
'/sso/:projectId/:projectSsoId',
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!req.params['projectId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Project ID not found')
|
||||
);
|
||||
}
|
||||
|
||||
if (!req.params['projectSsoId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Project SSO ID not found')
|
||||
);
|
||||
}
|
||||
|
||||
const projectSSO: ProjectSSO | null =
|
||||
await ProjectSSOService.findOneBy({
|
||||
query: {
|
||||
projectId: new ObjectID(req.params['projectId']),
|
||||
_id: req.params['projectSsoId'],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
signOnURL: true,
|
||||
issuerURL: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!projectSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('SSO Config not found')
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Sign On URL not found')
|
||||
);
|
||||
}
|
||||
|
||||
if (!projectSSO.issuerURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer not found')
|
||||
);
|
||||
}
|
||||
|
||||
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
|
||||
acsUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/identity/idp-login/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`
|
||||
),
|
||||
signOnUrl: projectSSO.signOnURL!,
|
||||
issuerUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`
|
||||
),
|
||||
});
|
||||
|
||||
return Response.redirect(req, res, samlRequestUrl);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
"/service-provider-login",
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
if (!req.query["email"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Email is required"),
|
||||
);
|
||||
}
|
||||
|
||||
const email: Email = new Email(req.query["email"] as string);
|
||||
|
||||
if (!email) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Email is required"),
|
||||
);
|
||||
}
|
||||
|
||||
// get sso config for this user.
|
||||
|
||||
const user: User | null = await UserService.findOneBy({
|
||||
query: { email: email },
|
||||
select: {
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("No SSO config found for this user"),
|
||||
);
|
||||
}
|
||||
|
||||
const userId: ObjectID = user.id!;
|
||||
|
||||
if (!userId) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("No SSO config found for this user"),
|
||||
);
|
||||
}
|
||||
|
||||
const projectUserBelongsTo: Array<ObjectID> = (
|
||||
await TeamMemberService.findBy({
|
||||
query: { userId: userId },
|
||||
select: {
|
||||
projectId: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
})
|
||||
).map((teamMember: TeamMember) => {
|
||||
return teamMember.projectId!;
|
||||
});
|
||||
|
||||
if (projectUserBelongsTo.length === 0) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("No SSO config found for this user"),
|
||||
);
|
||||
}
|
||||
|
||||
const projectSSOList: Array<ProjectSSO> = await ProjectSSOService.findBy({
|
||||
query: {
|
||||
projectId: QueryHelper.any(projectUserBelongsTo),
|
||||
isEnabled: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
select: {
|
||||
name: true,
|
||||
description: true,
|
||||
_id: true,
|
||||
projectId: true,
|
||||
project: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
return Response.sendEntityArrayResponse(
|
||||
req,
|
||||
res,
|
||||
projectSSOList,
|
||||
projectSSOList.length,
|
||||
ProjectSSO,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/idp-login/:projectId/:projectSsoId',
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
return await loginUserWithSso(req, res);
|
||||
"/sso/:projectId/:projectSsoId",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!req.params["projectId"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Project ID not found"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!req.params["projectSsoId"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Project SSO ID not found"),
|
||||
);
|
||||
}
|
||||
|
||||
const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy({
|
||||
query: {
|
||||
projectId: new ObjectID(req.params["projectId"]),
|
||||
_id: req.params["projectSsoId"],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
signOnURL: true,
|
||||
issuerURL: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!projectSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("SSO Config not found"),
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Sign On URL not found"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!projectSSO.issuerURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Issuer not found"),
|
||||
);
|
||||
}
|
||||
|
||||
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
|
||||
acsUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/identity/idp-login/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`,
|
||||
),
|
||||
signOnUrl: projectSSO.signOnURL!,
|
||||
issuerUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`,
|
||||
),
|
||||
});
|
||||
|
||||
return Response.redirect(req, res, samlRequestUrl);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/idp-login/:projectId/:projectSsoId",
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
return await loginUserWithSso(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/idp-login/:projectId/:projectSsoId',
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
return await loginUserWithSso(req, res);
|
||||
}
|
||||
"/idp-login/:projectId/:projectSsoId",
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
return await loginUserWithSso(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
type LoginUserWithSsoFunction = (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
) => Promise<void>;
|
||||
|
||||
const loginUserWithSso: LoginUserWithSsoFunction = async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const samlResponseBase64: string = req.body.SAMLResponse;
|
||||
try {
|
||||
const samlResponseBase64: string = req.body.SAMLResponse;
|
||||
|
||||
if (!samlResponseBase64) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('SAMLResponse not found')
|
||||
);
|
||||
}
|
||||
|
||||
const samlResponse: string = Buffer.from(
|
||||
samlResponseBase64,
|
||||
'base64'
|
||||
).toString();
|
||||
|
||||
const response: JSONObject = await xml2js.parseStringPromise(
|
||||
samlResponse
|
||||
);
|
||||
|
||||
let issuerUrl: string = '';
|
||||
let email: Email | null = null;
|
||||
|
||||
if (!req.params['projectId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Project ID not found')
|
||||
);
|
||||
}
|
||||
|
||||
if (!req.params['projectSsoId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Project SSO ID not found')
|
||||
);
|
||||
}
|
||||
|
||||
const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy(
|
||||
{
|
||||
query: {
|
||||
projectId: new ObjectID(req.params['projectId']),
|
||||
_id: req.params['projectSsoId'],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
signOnURL: true,
|
||||
issuerURL: true,
|
||||
publicCertificate: true,
|
||||
teams: {
|
||||
_id: true,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!projectSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('SSO Config not found')
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.issuerURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer URL not found')
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Sign on URL not found')
|
||||
);
|
||||
}
|
||||
|
||||
if (!projectSSO.publicCertificate) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Public Certificate not found')
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
SSOUtil.isPayloadValid(response);
|
||||
|
||||
if (
|
||||
!SSOUtil.isSignatureValid(
|
||||
samlResponse,
|
||||
projectSSO.publicCertificate
|
||||
)
|
||||
) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException(
|
||||
'Signature is not valid or Public Certificate configured with this SSO provider is not valid'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
issuerUrl = SSOUtil.getIssuer(response);
|
||||
email = SSOUtil.getEmail(response);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Exception) {
|
||||
return Response.sendErrorResponse(req, res, err);
|
||||
}
|
||||
return Response.sendErrorResponse(req, res, new ServerException());
|
||||
}
|
||||
|
||||
if (projectSSO.issuerURL.toString() !== issuerUrl) {
|
||||
logger.error(
|
||||
'Issuer URL does not match. It should be ' +
|
||||
projectSSO.issuerURL.toString() +
|
||||
' but it is ' +
|
||||
issuerUrl.toString()
|
||||
);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer URL does not match')
|
||||
);
|
||||
}
|
||||
|
||||
// Check if he already belongs to the project, If he does - then log in.
|
||||
|
||||
let alreadySavedUser: User | null = await UserService.findOneBy({
|
||||
query: { email: email },
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
isMasterAdmin: true,
|
||||
isEmailVerified: true,
|
||||
profilePictureId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
let isNewUser: boolean = false;
|
||||
|
||||
if (!alreadySavedUser) {
|
||||
// this should never happen because user is logged in before he signs in with SSO UNLESS he initiates the login though the IDP.
|
||||
|
||||
/// Create a user.
|
||||
|
||||
alreadySavedUser = await UserService.createByEmail({
|
||||
email,
|
||||
isEmailVerified: true,
|
||||
generateRandomPassword: true,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
isNewUser = true;
|
||||
}
|
||||
|
||||
// If he does not then add him to teams that he should belong and log in.
|
||||
// This should never happen because email is verified before he logs in with SSO.
|
||||
if (!alreadySavedUser.isEmailVerified && !isNewUser) {
|
||||
await AuthenticationEmail.sendVerificationEmail(alreadySavedUser!);
|
||||
|
||||
return Response.render(
|
||||
req,
|
||||
res,
|
||||
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
|
||||
{
|
||||
title: 'Email not verified.',
|
||||
message:
|
||||
'Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// check if the user already belongs to the project
|
||||
const teamMemberCount: PositiveNumber = await TeamMemberService.countBy(
|
||||
{
|
||||
query: {
|
||||
projectId: new ObjectID(req.params['projectId'] as string),
|
||||
userId: alreadySavedUser!.id!,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (teamMemberCount.toNumber() === 0) {
|
||||
// user not in project, add him to default teams.
|
||||
|
||||
if (!projectSSO.teams || projectSSO.teams.length === 0) {
|
||||
return Response.render(
|
||||
req,
|
||||
res,
|
||||
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
|
||||
{
|
||||
title: 'No teams added.',
|
||||
message:
|
||||
'No teams have been added to this SSO config. Please contact your admin and have default teams added.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for (const team of projectSSO.teams) {
|
||||
// add user to team
|
||||
let teamMember: TeamMember = new TeamMember();
|
||||
teamMember.projectId = new ObjectID(
|
||||
req.params['projectId'] as string
|
||||
);
|
||||
teamMember.userId = alreadySavedUser.id!;
|
||||
teamMember.hasAcceptedInvitation = true;
|
||||
teamMember.invitationAcceptedAt =
|
||||
OneUptimeDate.getCurrentDate();
|
||||
teamMember.teamId = team.id!;
|
||||
|
||||
teamMember = await TeamMemberService.create({
|
||||
data: teamMember,
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const projectId: ObjectID = new ObjectID(
|
||||
req.params['projectId'] as string
|
||||
);
|
||||
|
||||
const ssoToken: string = JSONWebToken.sign({
|
||||
data: {
|
||||
userId: alreadySavedUser.id!,
|
||||
projectId: projectId,
|
||||
name: alreadySavedUser.name!,
|
||||
email: email,
|
||||
isMasterAdmin: false,
|
||||
isGeneralLogin: false,
|
||||
},
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
|
||||
const oneUptimeToken: string = JSONWebToken.signUserLoginToken({
|
||||
tokenData: {
|
||||
userId: alreadySavedUser.id!,
|
||||
email: alreadySavedUser.email!,
|
||||
name: alreadySavedUser.name!,
|
||||
isMasterAdmin: alreadySavedUser.isMasterAdmin!,
|
||||
isGlobalLogin: false, // This is a general login without SSO. So, we will set this to false. This will give access to all the projects that dont require SSO.
|
||||
},
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
|
||||
// Set a cookie with token.
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(),
|
||||
oneUptimeToken,
|
||||
{
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
httpOnly: true,
|
||||
}
|
||||
);
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserSSOKey(projectId),
|
||||
ssoToken,
|
||||
{
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
httpOnly: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(
|
||||
alreadySavedUser.id!
|
||||
);
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
logger.info('User logged in with SSO' + email.toString());
|
||||
|
||||
return Response.redirect(
|
||||
req,
|
||||
res,
|
||||
new URL(
|
||||
httpProtocol,
|
||||
host,
|
||||
new Route(DashboardRoute.toString()).addRoute(
|
||||
'/' + req.params['projectId']
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
Response.sendErrorResponse(req, res, new ServerException());
|
||||
if (!samlResponseBase64) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("SAMLResponse not found"),
|
||||
);
|
||||
}
|
||||
|
||||
const samlResponse: string = Buffer.from(
|
||||
samlResponseBase64,
|
||||
"base64",
|
||||
).toString();
|
||||
|
||||
const response: JSONObject = await xml2js.parseStringPromise(samlResponse);
|
||||
|
||||
let issuerUrl: string = "";
|
||||
let email: Email | null = null;
|
||||
|
||||
if (!req.params["projectId"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Project ID not found"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!req.params["projectSsoId"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Project SSO ID not found"),
|
||||
);
|
||||
}
|
||||
|
||||
const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy({
|
||||
query: {
|
||||
projectId: new ObjectID(req.params["projectId"]),
|
||||
_id: req.params["projectSsoId"],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
signOnURL: true,
|
||||
issuerURL: true,
|
||||
publicCertificate: true,
|
||||
teams: {
|
||||
_id: true,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!projectSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("SSO Config not found"),
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.issuerURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Issuer URL not found"),
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!projectSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Sign on URL not found"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!projectSSO.publicCertificate) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Public Certificate not found"),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
SSOUtil.isPayloadValid(response);
|
||||
|
||||
if (
|
||||
!SSOUtil.isSignatureValid(samlResponse, projectSSO.publicCertificate)
|
||||
) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException(
|
||||
"Signature is not valid or Public Certificate configured with this SSO provider is not valid",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
issuerUrl = SSOUtil.getIssuer(response);
|
||||
email = SSOUtil.getEmail(response);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Exception) {
|
||||
return Response.sendErrorResponse(req, res, err);
|
||||
}
|
||||
return Response.sendErrorResponse(req, res, new ServerException());
|
||||
}
|
||||
|
||||
if (projectSSO.issuerURL.toString() !== issuerUrl) {
|
||||
logger.error(
|
||||
"Issuer URL does not match. It should be " +
|
||||
projectSSO.issuerURL.toString() +
|
||||
" but it is " +
|
||||
issuerUrl.toString(),
|
||||
);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Issuer URL does not match"),
|
||||
);
|
||||
}
|
||||
|
||||
// Check if he already belongs to the project, If he does - then log in.
|
||||
|
||||
let alreadySavedUser: User | null = await UserService.findOneBy({
|
||||
query: { email: email },
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
isMasterAdmin: true,
|
||||
isEmailVerified: true,
|
||||
profilePictureId: true,
|
||||
timezone: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
let isNewUser: boolean = false;
|
||||
|
||||
if (!alreadySavedUser) {
|
||||
// this should never happen because user is logged in before he signs in with SSO UNLESS he initiates the login though the IDP.
|
||||
|
||||
/// Create a user.
|
||||
|
||||
alreadySavedUser = await UserService.createByEmail({
|
||||
email,
|
||||
isEmailVerified: true,
|
||||
generateRandomPassword: true,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
isNewUser = true;
|
||||
}
|
||||
|
||||
// If he does not then add him to teams that he should belong and log in.
|
||||
// This should never happen because email is verified before he logs in with SSO.
|
||||
if (!alreadySavedUser.isEmailVerified && !isNewUser) {
|
||||
await AuthenticationEmail.sendVerificationEmail(alreadySavedUser!);
|
||||
|
||||
return Response.render(
|
||||
req,
|
||||
res,
|
||||
"/usr/src/app/FeatureSet/Identity/Views/Message.ejs",
|
||||
{
|
||||
title: "Email not verified.",
|
||||
message:
|
||||
"Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// check if the user already belongs to the project
|
||||
const teamMemberCount: PositiveNumber = await TeamMemberService.countBy({
|
||||
query: {
|
||||
projectId: new ObjectID(req.params["projectId"] as string),
|
||||
userId: alreadySavedUser!.id!,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (teamMemberCount.toNumber() === 0) {
|
||||
// user not in project, add him to default teams.
|
||||
|
||||
if (!projectSSO.teams || projectSSO.teams.length === 0) {
|
||||
return Response.render(
|
||||
req,
|
||||
res,
|
||||
"/usr/src/app/FeatureSet/Identity/Views/Message.ejs",
|
||||
{
|
||||
title: "No teams added.",
|
||||
message:
|
||||
"No teams have been added to this SSO config. Please contact your admin and have default teams added.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (const team of projectSSO.teams) {
|
||||
// add user to team
|
||||
let teamMember: TeamMember = new TeamMember();
|
||||
teamMember.projectId = new ObjectID(req.params["projectId"] as string);
|
||||
teamMember.userId = alreadySavedUser.id!;
|
||||
teamMember.hasAcceptedInvitation = true;
|
||||
teamMember.invitationAcceptedAt = OneUptimeDate.getCurrentDate();
|
||||
teamMember.teamId = team.id!;
|
||||
|
||||
teamMember = await TeamMemberService.create({
|
||||
data: teamMember,
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const projectId: ObjectID = new ObjectID(req.params["projectId"] as string);
|
||||
|
||||
alreadySavedUser.email = email;
|
||||
|
||||
CookieUtil.setSSOCookie({
|
||||
user: alreadySavedUser,
|
||||
projectId: projectId,
|
||||
expressResponse: res,
|
||||
});
|
||||
|
||||
CookieUtil.setUserCookie({
|
||||
expressResponse: res,
|
||||
user: alreadySavedUser,
|
||||
isGlobalLogin: false,
|
||||
});
|
||||
|
||||
// Refresh Permissions for this user here.
|
||||
await AccessTokenService.refreshUserAllPermissions(alreadySavedUser.id!);
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
logger.info("User logged in with SSO" + email.toString());
|
||||
|
||||
return Response.redirect(
|
||||
req,
|
||||
res,
|
||||
new URL(
|
||||
httpProtocol,
|
||||
host,
|
||||
new Route(DashboardRoute.toString()).addRoute(
|
||||
"/" + req.params["projectId"],
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
Response.sendErrorResponse(req, res, new ServerException());
|
||||
}
|
||||
};
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,432 +1,422 @@
|
||||
import BaseModel from 'Common/Models/BaseModel';
|
||||
import { FileRoute } from 'Common/ServiceRoute';
|
||||
import Hostname from 'Common/Types/API/Hostname';
|
||||
import Protocol from 'Common/Types/API/Protocol';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import JSONFunctions from 'Common/Types/JSONFunctions';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import PositiveNumber from 'Common/Types/PositiveNumber';
|
||||
import DatabaseConfig from 'CommonServer/DatabaseConfig';
|
||||
import { EncryptionSecret } from 'CommonServer/EnvironmentConfig';
|
||||
import MailService from 'CommonServer/Services/MailService';
|
||||
import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivateUserService';
|
||||
import StatusPageService from 'CommonServer/Services/StatusPageService';
|
||||
import CookieUtil from 'CommonServer/Utils/Cookie';
|
||||
import BaseModel from "Common/Models/BaseModel";
|
||||
import { FileRoute } from "Common/ServiceRoute";
|
||||
import Hostname from "Common/Types/API/Hostname";
|
||||
import Protocol from "Common/Types/API/Protocol";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PositiveNumber from "Common/Types/PositiveNumber";
|
||||
import DatabaseConfig from "CommonServer/DatabaseConfig";
|
||||
import { EncryptionSecret } from "CommonServer/EnvironmentConfig";
|
||||
import MailService from "CommonServer/Services/MailService";
|
||||
import StatusPagePrivateUserService from "CommonServer/Services/StatusPagePrivateUserService";
|
||||
import StatusPageService from "CommonServer/Services/StatusPageService";
|
||||
import CookieUtil from "CommonServer/Utils/Cookie";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
import StatusPage from 'Model/Models/StatusPage';
|
||||
import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser';
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import StatusPage from "Model/Models/StatusPage";
|
||||
import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post(
|
||||
'/logout/:statuspageid',
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!req.params['statuspageid']) {
|
||||
throw new BadDataException('Status Page ID is required.');
|
||||
}
|
||||
"/logout/:statuspageid",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!req.params["statuspageid"]) {
|
||||
throw new BadDataException("Status Page ID is required.");
|
||||
}
|
||||
|
||||
const statusPageId: ObjectID = new ObjectID(
|
||||
req.params['statuspageid'].toString()
|
||||
);
|
||||
const statusPageId: ObjectID = new ObjectID(
|
||||
req.params["statuspageid"].toString(),
|
||||
);
|
||||
|
||||
CookieUtil.removeCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(statusPageId)
|
||||
); // remove the cookie.
|
||||
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId)); // remove the cookie.
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/forgot-password',
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const data: JSONObject = req.body['data'];
|
||||
"/forgot-password",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const data: JSONObject = req.body["data"];
|
||||
|
||||
if (!data['email']) {
|
||||
throw new BadDataException('Email is required.');
|
||||
}
|
||||
if (!data["email"]) {
|
||||
throw new BadDataException("Email is required.");
|
||||
}
|
||||
|
||||
const user: StatusPagePrivateUser = BaseModel.fromJSON(
|
||||
data as JSONObject,
|
||||
StatusPagePrivateUser
|
||||
) as StatusPagePrivateUser;
|
||||
const user: StatusPagePrivateUser = BaseModel.fromJSON(
|
||||
data as JSONObject,
|
||||
StatusPagePrivateUser,
|
||||
) as StatusPagePrivateUser;
|
||||
|
||||
if (!user.statusPageId) {
|
||||
throw new BadDataException('Status Page ID is required.');
|
||||
}
|
||||
if (!user.statusPageId) {
|
||||
throw new BadDataException("Status Page ID is required.");
|
||||
}
|
||||
|
||||
const statusPage: StatusPage | null =
|
||||
await StatusPageService.findOneById({
|
||||
id: user.statusPageId!,
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
pageTitle: true,
|
||||
logoFileId: true,
|
||||
requireSsoForLogin: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
const statusPage: StatusPage | null = await StatusPageService.findOneById(
|
||||
{
|
||||
id: user.statusPageId!,
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
pageTitle: true,
|
||||
logoFileId: true,
|
||||
requireSsoForLogin: true,
|
||||
projectId: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!statusPage) {
|
||||
throw new BadDataException('Status Page not found');
|
||||
}
|
||||
if (!statusPage) {
|
||||
throw new BadDataException("Status Page not found");
|
||||
}
|
||||
|
||||
if (statusPage.requireSsoForLogin) {
|
||||
throw new BadDataException(
|
||||
'Status Page supports authentication by SSO. You cannot use email and password for authentication.'
|
||||
);
|
||||
}
|
||||
if (statusPage.requireSsoForLogin) {
|
||||
throw new BadDataException(
|
||||
"Status Page supports authentication by SSO. You cannot use email and password for authentication.",
|
||||
);
|
||||
}
|
||||
|
||||
const statusPageName: string | undefined =
|
||||
statusPage.pageTitle || statusPage.name;
|
||||
const statusPageName: string | undefined =
|
||||
statusPage.pageTitle || statusPage.name;
|
||||
|
||||
const statusPageURL: string =
|
||||
await StatusPageService.getStatusPageURL(statusPage.id!);
|
||||
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
||||
statusPage.id!,
|
||||
);
|
||||
|
||||
const alreadySavedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
email: user.email!,
|
||||
statusPageId: user.statusPageId!,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
password: true,
|
||||
email: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
const alreadySavedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
email: user.email!,
|
||||
statusPageId: user.statusPageId!,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
password: true,
|
||||
email: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (alreadySavedUser) {
|
||||
const token: string = ObjectID.generate().toString();
|
||||
await StatusPagePrivateUserService.updateOneBy({
|
||||
query: {
|
||||
_id: alreadySavedUser._id!,
|
||||
},
|
||||
data: {
|
||||
resetPasswordToken: token,
|
||||
resetPasswordExpires: OneUptimeDate.getOneDayAfter(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
if (alreadySavedUser) {
|
||||
const token: string = ObjectID.generate().toString();
|
||||
await StatusPagePrivateUserService.updateOneBy({
|
||||
query: {
|
||||
_id: alreadySavedUser._id!,
|
||||
},
|
||||
data: {
|
||||
resetPasswordToken: token,
|
||||
resetPasswordExpires: OneUptimeDate.getOneDayAfter(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol =
|
||||
await DatabaseConfig.getHttpProtocol();
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
MailService.sendMail(
|
||||
{
|
||||
toEmail: user.email!,
|
||||
subject: 'Password Reset Request for ' + statusPageName,
|
||||
templateType:
|
||||
EmailTemplateType.StatusPageForgotPassword,
|
||||
vars: {
|
||||
statusPageName: statusPageName!,
|
||||
logoUrl: statusPage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute(
|
||||
'/image/' + statusPage.logoFileId
|
||||
)
|
||||
.toString()
|
||||
: '',
|
||||
homeURL: statusPageURL,
|
||||
tokenVerifyUrl: URL.fromString(statusPageURL)
|
||||
.addRoute('/reset-password/' + token)
|
||||
.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
projectId: statusPage.projectId!,
|
||||
}
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
MailService.sendMail(
|
||||
{
|
||||
toEmail: user.email!,
|
||||
subject: "Password Reset Request for " + statusPageName,
|
||||
templateType: EmailTemplateType.StatusPageForgotPassword,
|
||||
vars: {
|
||||
statusPageName: statusPageName!,
|
||||
logoUrl: statusPage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute("/image/" + statusPage.logoFileId)
|
||||
.toString()
|
||||
: "",
|
||||
homeURL: statusPageURL,
|
||||
tokenVerifyUrl: URL.fromString(statusPageURL)
|
||||
.addRoute("/reset-password/" + token)
|
||||
.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
projectId: statusPage.projectId!,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
|
||||
throw new BadDataException(
|
||||
`No user is registered with ${user.email?.toString()}`
|
||||
);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
throw new BadDataException(
|
||||
`No user is registered with ${user.email?.toString()}`,
|
||||
);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/reset-password',
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const data: JSONObject = JSONFunctions.deserialize(
|
||||
req.body['data']
|
||||
);
|
||||
"/reset-password",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const data: JSONObject = JSONFunctions.deserialize(req.body["data"]);
|
||||
|
||||
if (!data['statusPageId']) {
|
||||
throw new BadDataException('Status Page ID is required.');
|
||||
}
|
||||
if (!data["statusPageId"]) {
|
||||
throw new BadDataException("Status Page ID is required.");
|
||||
}
|
||||
|
||||
const user: StatusPagePrivateUser = BaseModel.fromJSON(
|
||||
data as JSONObject,
|
||||
StatusPagePrivateUser
|
||||
) as StatusPagePrivateUser;
|
||||
const user: StatusPagePrivateUser = BaseModel.fromJSON(
|
||||
data as JSONObject,
|
||||
StatusPagePrivateUser,
|
||||
) as StatusPagePrivateUser;
|
||||
|
||||
await user.password?.hashValue(EncryptionSecret);
|
||||
await user.password?.hashValue(EncryptionSecret);
|
||||
|
||||
const alreadySavedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
statusPageId: new ObjectID(
|
||||
data['statusPageId'].toString()
|
||||
),
|
||||
resetPasswordToken:
|
||||
(user.resetPasswordToken as string) || '',
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
password: true,
|
||||
email: true,
|
||||
resetPasswordExpires: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
const alreadySavedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
statusPageId: new ObjectID(data["statusPageId"].toString()),
|
||||
resetPasswordToken: (user.resetPasswordToken as string) || "",
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
password: true,
|
||||
email: true,
|
||||
resetPasswordExpires: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!alreadySavedUser) {
|
||||
throw new BadDataException(
|
||||
'Invalid link. Please go to forgot password page again and request a new link.'
|
||||
);
|
||||
}
|
||||
if (!alreadySavedUser) {
|
||||
throw new BadDataException(
|
||||
"Invalid link. Please go to forgot password page again and request a new link.",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
alreadySavedUser &&
|
||||
OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!)
|
||||
) {
|
||||
throw new BadDataException(
|
||||
'Expired link. Please go to forgot password page again and request a new link.'
|
||||
);
|
||||
}
|
||||
if (
|
||||
alreadySavedUser &&
|
||||
OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!)
|
||||
) {
|
||||
throw new BadDataException(
|
||||
"Expired link. Please go to forgot password page again and request a new link.",
|
||||
);
|
||||
}
|
||||
|
||||
const statusPage: StatusPage | null =
|
||||
await StatusPageService.findOneById({
|
||||
id: new ObjectID(data['statusPageId'].toString()),
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
pageTitle: true,
|
||||
logoFileId: true,
|
||||
requireSsoForLogin: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
const statusPage: StatusPage | null = await StatusPageService.findOneById(
|
||||
{
|
||||
id: new ObjectID(data["statusPageId"].toString()),
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
name: true,
|
||||
pageTitle: true,
|
||||
logoFileId: true,
|
||||
requireSsoForLogin: true,
|
||||
projectId: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!statusPage) {
|
||||
throw new BadDataException('Status Page not found');
|
||||
}
|
||||
if (!statusPage) {
|
||||
throw new BadDataException("Status Page not found");
|
||||
}
|
||||
|
||||
if (statusPage.requireSsoForLogin) {
|
||||
throw new BadDataException(
|
||||
'Status Page supports authentication by SSO. You cannot use email and password for authentication.'
|
||||
);
|
||||
}
|
||||
if (statusPage.requireSsoForLogin) {
|
||||
throw new BadDataException(
|
||||
"Status Page supports authentication by SSO. You cannot use email and password for authentication.",
|
||||
);
|
||||
}
|
||||
|
||||
const statusPageName: string | undefined =
|
||||
statusPage.pageTitle || statusPage.name;
|
||||
const statusPageName: string | undefined =
|
||||
statusPage.pageTitle || statusPage.name;
|
||||
|
||||
const statusPageURL: string =
|
||||
await StatusPageService.getStatusPageURL(statusPage.id!);
|
||||
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
||||
statusPage.id!,
|
||||
);
|
||||
|
||||
await StatusPagePrivateUserService.updateOneById({
|
||||
id: alreadySavedUser.id!,
|
||||
data: {
|
||||
password: user.password!,
|
||||
resetPasswordToken: null!,
|
||||
resetPasswordExpires: null!,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
await StatusPagePrivateUserService.updateOneById({
|
||||
id: alreadySavedUser.id!,
|
||||
data: {
|
||||
password: user.password!,
|
||||
resetPasswordToken: null!,
|
||||
resetPasswordExpires: null!,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol =
|
||||
await DatabaseConfig.getHttpProtocol();
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
MailService.sendMail(
|
||||
{
|
||||
toEmail: alreadySavedUser.email!,
|
||||
subject: 'Password Changed.',
|
||||
templateType: EmailTemplateType.StatusPagePasswordChanged,
|
||||
vars: {
|
||||
homeURL: statusPageURL,
|
||||
statusPageName: statusPageName || '',
|
||||
logoUrl: statusPage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute('/image/' + statusPage.logoFileId)
|
||||
.toString()
|
||||
: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
projectId: statusPage.projectId!,
|
||||
}
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
MailService.sendMail(
|
||||
{
|
||||
toEmail: alreadySavedUser.email!,
|
||||
subject: "Password Changed.",
|
||||
templateType: EmailTemplateType.StatusPagePasswordChanged,
|
||||
vars: {
|
||||
homeURL: statusPageURL,
|
||||
statusPageName: statusPageName || "",
|
||||
logoUrl: statusPage.logoFileId
|
||||
? new URL(httpProtocol, host)
|
||||
.addRoute(FileRoute)
|
||||
.addRoute("/image/" + statusPage.logoFileId)
|
||||
.toString()
|
||||
: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
projectId: statusPage.projectId!,
|
||||
},
|
||||
).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/login',
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const data: JSONObject = req.body['data'];
|
||||
"/login",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const data: JSONObject = req.body["data"];
|
||||
|
||||
const user: StatusPagePrivateUser = BaseModel.fromJSON(
|
||||
data as JSONObject,
|
||||
StatusPagePrivateUser
|
||||
) as StatusPagePrivateUser;
|
||||
const user: StatusPagePrivateUser = BaseModel.fromJSON(
|
||||
data as JSONObject,
|
||||
StatusPagePrivateUser,
|
||||
) as StatusPagePrivateUser;
|
||||
|
||||
if (!user.statusPageId) {
|
||||
throw new BadDataException('Status Page ID not found');
|
||||
}
|
||||
if (!user.statusPageId) {
|
||||
throw new BadDataException("Status Page ID not found");
|
||||
}
|
||||
|
||||
const statusPage: StatusPage | null =
|
||||
await StatusPageService.findOneById({
|
||||
id: user.statusPageId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
select: {
|
||||
requireSsoForLogin: true,
|
||||
},
|
||||
});
|
||||
const statusPage: StatusPage | null = await StatusPageService.findOneById(
|
||||
{
|
||||
id: user.statusPageId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
select: {
|
||||
requireSsoForLogin: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!statusPage) {
|
||||
throw new BadDataException('Status Page not found');
|
||||
}
|
||||
if (!statusPage) {
|
||||
throw new BadDataException("Status Page not found");
|
||||
}
|
||||
|
||||
if (statusPage.requireSsoForLogin) {
|
||||
throw new BadDataException(
|
||||
'Status Page supports authentication by SSO. You cannot use email and password for authentication.'
|
||||
);
|
||||
}
|
||||
if (statusPage.requireSsoForLogin) {
|
||||
throw new BadDataException(
|
||||
"Status Page supports authentication by SSO. You cannot use email and password for authentication.",
|
||||
);
|
||||
}
|
||||
|
||||
await user.password?.hashValue(EncryptionSecret);
|
||||
await user.password?.hashValue(EncryptionSecret);
|
||||
|
||||
const alreadySavedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
email: user.email!,
|
||||
password: user.password!,
|
||||
statusPageId: user.statusPageId!,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
password: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
const alreadySavedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: {
|
||||
email: user.email!,
|
||||
password: user.password!,
|
||||
statusPageId: user.statusPageId!,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
password: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (alreadySavedUser) {
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
if (alreadySavedUser) {
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30),
|
||||
),
|
||||
});
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
|
||||
token,
|
||||
{
|
||||
httpOnly: true,
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
}
|
||||
);
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
|
||||
token,
|
||||
{
|
||||
httpOnly: true,
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
||||
},
|
||||
);
|
||||
|
||||
return Response.sendEntityResponse(
|
||||
req,
|
||||
res,
|
||||
alreadySavedUser,
|
||||
StatusPagePrivateUser,
|
||||
{
|
||||
miscData: {
|
||||
token: token,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
throw new BadDataException(
|
||||
'Invalid login: Email or password does not match.'
|
||||
);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
return Response.sendEntityResponse(
|
||||
req,
|
||||
res,
|
||||
alreadySavedUser,
|
||||
StatusPagePrivateUser,
|
||||
{
|
||||
miscData: {
|
||||
token: token,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
throw new BadDataException(
|
||||
"Invalid login: Email or password does not match.",
|
||||
);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,323 +1,312 @@
|
||||
import SSOUtil from '../Utils/SSO';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import Email from 'Common/Types/Email';
|
||||
import BadRequestException from 'Common/Types/Exception/BadRequestException';
|
||||
import Exception from 'Common/Types/Exception/Exception';
|
||||
import ServerException from 'Common/Types/Exception/ServerException';
|
||||
import HashedString from 'Common/Types/HashedString';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import PositiveNumber from 'Common/Types/PositiveNumber';
|
||||
import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig';
|
||||
import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivateUserService';
|
||||
import StatusPageService from 'CommonServer/Services/StatusPageService';
|
||||
import StatusPageSsoService from 'CommonServer/Services/StatusPageSsoService';
|
||||
import CookieUtil from 'CommonServer/Utils/Cookie';
|
||||
import SSOUtil from "../Utils/SSO";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import Email from "Common/Types/Email";
|
||||
import BadRequestException from "Common/Types/Exception/BadRequestException";
|
||||
import Exception from "Common/Types/Exception/Exception";
|
||||
import ServerException from "Common/Types/Exception/ServerException";
|
||||
import HashedString from "Common/Types/HashedString";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import PositiveNumber from "Common/Types/PositiveNumber";
|
||||
import { Host, HttpProtocol } from "CommonServer/EnvironmentConfig";
|
||||
import StatusPagePrivateUserService from "CommonServer/Services/StatusPagePrivateUserService";
|
||||
import StatusPageService from "CommonServer/Services/StatusPageService";
|
||||
import StatusPageSsoService from "CommonServer/Services/StatusPageSsoService";
|
||||
import CookieUtil from "CommonServer/Utils/Cookie";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser';
|
||||
import StatusPageSSO from 'Model/Models/StatusPageSso';
|
||||
import xml2js from 'xml2js';
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
NextFunction,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser";
|
||||
import StatusPageSSO from "Model/Models/StatusPageSso";
|
||||
import xml2js from "xml2js";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.get(
|
||||
'/status-page-sso/:statusPageId/:statusPageSsoId',
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!req.params['statusPageId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Status Page ID not found')
|
||||
);
|
||||
}
|
||||
"/status-page-sso/:statusPageId/:statusPageSsoId",
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (!req.params["statusPageId"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Status Page ID not found"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!req.params['statusPageSsoId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Status Page SSO ID not found')
|
||||
);
|
||||
}
|
||||
if (!req.params["statusPageSsoId"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Status Page SSO ID not found"),
|
||||
);
|
||||
}
|
||||
|
||||
const statusPageId: ObjectID = new ObjectID(
|
||||
req.params['statusPageId']
|
||||
);
|
||||
const statusPageId: ObjectID = new ObjectID(req.params["statusPageId"]);
|
||||
|
||||
const statusPageSSO: StatusPageSSO | null =
|
||||
await StatusPageSsoService.findOneBy({
|
||||
query: {
|
||||
statusPageId: statusPageId,
|
||||
_id: req.params['statusPageSsoId'],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
signOnURL: true,
|
||||
statusPageId: true,
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
const statusPageSSO: StatusPageSSO | null =
|
||||
await StatusPageSsoService.findOneBy({
|
||||
query: {
|
||||
statusPageId: statusPageId,
|
||||
_id: req.params["statusPageSsoId"],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
signOnURL: true,
|
||||
statusPageId: true,
|
||||
_id: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!statusPageSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('SSO Config not found')
|
||||
);
|
||||
}
|
||||
if (!statusPageSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("SSO Config not found"),
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!statusPageSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Sign On URL not found')
|
||||
);
|
||||
}
|
||||
if (!statusPageSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Sign On URL not found"),
|
||||
);
|
||||
}
|
||||
|
||||
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
|
||||
acsUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/identity/status-page-idp-login/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`
|
||||
),
|
||||
signOnUrl: statusPageSSO.signOnURL!,
|
||||
issuerUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`
|
||||
),
|
||||
});
|
||||
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
|
||||
acsUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/identity/status-page-idp-login/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`,
|
||||
),
|
||||
signOnUrl: statusPageSSO.signOnURL!,
|
||||
issuerUrl: URL.fromString(
|
||||
`${HttpProtocol}${Host}/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`,
|
||||
),
|
||||
});
|
||||
|
||||
return Response.redirect(req, res, samlRequestUrl);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
return Response.redirect(req, res, samlRequestUrl);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/status-page-idp-login/:statusPageId/:statusPageSsoId',
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
try {
|
||||
const samlResponseBase64: string = req.body.SAMLResponse;
|
||||
"/status-page-idp-login/:statusPageId/:statusPageSsoId",
|
||||
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
|
||||
try {
|
||||
const samlResponseBase64: string = req.body.SAMLResponse;
|
||||
|
||||
const samlResponse: string = Buffer.from(
|
||||
samlResponseBase64,
|
||||
'base64'
|
||||
).toString();
|
||||
const samlResponse: string = Buffer.from(
|
||||
samlResponseBase64,
|
||||
"base64",
|
||||
).toString();
|
||||
|
||||
const response: JSONObject = await xml2js.parseStringPromise(
|
||||
samlResponse
|
||||
);
|
||||
const response: JSONObject =
|
||||
await xml2js.parseStringPromise(samlResponse);
|
||||
|
||||
let issuerUrl: string = '';
|
||||
let email: Email | null = null;
|
||||
let issuerUrl: string = "";
|
||||
let email: Email | null = null;
|
||||
|
||||
if (!req.params['statusPageId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Status Page ID not found')
|
||||
);
|
||||
}
|
||||
if (!req.params["statusPageId"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Status Page ID not found"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!req.params['statusPageSsoId']) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Status Page SSO ID not found')
|
||||
);
|
||||
}
|
||||
if (!req.params["statusPageSsoId"]) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Status Page SSO ID not found"),
|
||||
);
|
||||
}
|
||||
|
||||
const statusPageId: ObjectID = new ObjectID(
|
||||
req.params['statusPageId']
|
||||
);
|
||||
const statusPageId: ObjectID = new ObjectID(req.params["statusPageId"]);
|
||||
|
||||
const statusPageSSO: StatusPageSSO | null =
|
||||
await StatusPageSsoService.findOneBy({
|
||||
query: {
|
||||
statusPageId: statusPageId,
|
||||
_id: req.params['statusPageSsoId'],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
signOnURL: true,
|
||||
issuerURL: true,
|
||||
publicCertificate: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
const statusPageSSO: StatusPageSSO | null =
|
||||
await StatusPageSsoService.findOneBy({
|
||||
query: {
|
||||
statusPageId: statusPageId,
|
||||
_id: req.params["statusPageSsoId"],
|
||||
isEnabled: true,
|
||||
},
|
||||
select: {
|
||||
signOnURL: true,
|
||||
issuerURL: true,
|
||||
publicCertificate: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!statusPageSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('SSO Config not found')
|
||||
);
|
||||
}
|
||||
if (!statusPageSSO) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("SSO Config not found"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!statusPageSSO.projectId) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('SSO Config Project ID not found')
|
||||
);
|
||||
}
|
||||
if (!statusPageSSO.projectId) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("SSO Config Project ID not found"),
|
||||
);
|
||||
}
|
||||
|
||||
const projectId: ObjectID = statusPageSSO.projectId;
|
||||
const projectId: ObjectID = statusPageSSO.projectId;
|
||||
|
||||
// redirect to Identity Provider.
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!statusPageSSO.issuerURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer URL not found')
|
||||
);
|
||||
}
|
||||
if (!statusPageSSO.issuerURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Issuer URL not found"),
|
||||
);
|
||||
}
|
||||
|
||||
// redirect to Identity Provider.
|
||||
// redirect to Identity Provider.
|
||||
|
||||
if (!statusPageSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Sign on URL not found')
|
||||
);
|
||||
}
|
||||
if (!statusPageSSO.signOnURL) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Sign on URL not found"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!statusPageSSO.publicCertificate) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Public Certificate not found')
|
||||
);
|
||||
}
|
||||
if (!statusPageSSO.publicCertificate) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Public Certificate not found"),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
SSOUtil.isPayloadValid(response);
|
||||
try {
|
||||
SSOUtil.isPayloadValid(response);
|
||||
|
||||
if (
|
||||
!SSOUtil.isSignatureValid(
|
||||
samlResponse,
|
||||
statusPageSSO.publicCertificate
|
||||
)
|
||||
) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Signature is not valid')
|
||||
);
|
||||
}
|
||||
|
||||
issuerUrl = SSOUtil.getIssuer(response);
|
||||
|
||||
email = SSOUtil.getEmail(response);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Exception) {
|
||||
return Response.sendErrorResponse(req, res, err);
|
||||
}
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new ServerException()
|
||||
);
|
||||
}
|
||||
|
||||
if (statusPageSSO.issuerURL.toString() !== issuerUrl) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException('Issuer URL does not match')
|
||||
);
|
||||
}
|
||||
|
||||
// Check if he already belongs to the project, If he does - then log in.
|
||||
|
||||
let alreadySavedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: { email: email, statusPageId: statusPageId },
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!alreadySavedUser) {
|
||||
/// Create a user.
|
||||
|
||||
alreadySavedUser = new StatusPagePrivateUser();
|
||||
alreadySavedUser.projectId = projectId;
|
||||
alreadySavedUser.statusPageId = statusPageId;
|
||||
alreadySavedUser.email = email;
|
||||
alreadySavedUser.password = new HashedString(
|
||||
ObjectID.generate().toString()
|
||||
);
|
||||
alreadySavedUser.isSsoUser = true;
|
||||
|
||||
alreadySavedUser = await StatusPagePrivateUserService.create({
|
||||
data: alreadySavedUser,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
}
|
||||
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
});
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
|
||||
token,
|
||||
{
|
||||
httpOnly: true,
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(
|
||||
new PositiveNumber(30)
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
// get status page URL.
|
||||
const statusPageURL: string =
|
||||
await StatusPageService.getStatusPageFirstURL(statusPageId);
|
||||
|
||||
return Response.redirect(
|
||||
req,
|
||||
res,
|
||||
URL.fromString(statusPageURL).addQueryParams({
|
||||
token: token,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
Response.sendErrorResponse(req, res, new ServerException());
|
||||
if (
|
||||
!SSOUtil.isSignatureValid(
|
||||
samlResponse,
|
||||
statusPageSSO.publicCertificate,
|
||||
)
|
||||
) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Signature is not valid"),
|
||||
);
|
||||
}
|
||||
|
||||
issuerUrl = SSOUtil.getIssuer(response);
|
||||
|
||||
email = SSOUtil.getEmail(response);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Exception) {
|
||||
return Response.sendErrorResponse(req, res, err);
|
||||
}
|
||||
return Response.sendErrorResponse(req, res, new ServerException());
|
||||
}
|
||||
|
||||
if (statusPageSSO.issuerURL.toString() !== issuerUrl) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadRequestException("Issuer URL does not match"),
|
||||
);
|
||||
}
|
||||
|
||||
// Check if he already belongs to the project, If he does - then log in.
|
||||
|
||||
let alreadySavedUser: StatusPagePrivateUser | null =
|
||||
await StatusPagePrivateUserService.findOneBy({
|
||||
query: { email: email, statusPageId: statusPageId },
|
||||
select: {
|
||||
_id: true,
|
||||
email: true,
|
||||
statusPageId: true,
|
||||
projectId: true,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!alreadySavedUser) {
|
||||
/// Create a user.
|
||||
|
||||
alreadySavedUser = new StatusPagePrivateUser();
|
||||
alreadySavedUser.projectId = projectId;
|
||||
alreadySavedUser.statusPageId = statusPageId;
|
||||
alreadySavedUser.email = email;
|
||||
alreadySavedUser.password = new HashedString(
|
||||
ObjectID.generate().toString(),
|
||||
);
|
||||
alreadySavedUser.isSsoUser = true;
|
||||
|
||||
alreadySavedUser = await StatusPagePrivateUserService.create({
|
||||
data: alreadySavedUser,
|
||||
props: { isRoot: true },
|
||||
});
|
||||
}
|
||||
|
||||
const token: string = JSONWebToken.sign({
|
||||
data: alreadySavedUser,
|
||||
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
||||
new PositiveNumber(30),
|
||||
),
|
||||
});
|
||||
|
||||
CookieUtil.setCookie(
|
||||
res,
|
||||
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
|
||||
token,
|
||||
{
|
||||
httpOnly: true,
|
||||
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
||||
},
|
||||
);
|
||||
|
||||
// get status page URL.
|
||||
const statusPageURL: string =
|
||||
await StatusPageService.getStatusPageFirstURL(statusPageId);
|
||||
|
||||
return Response.redirect(
|
||||
req,
|
||||
res,
|
||||
URL.fromString(statusPageURL).addQueryParams({
|
||||
token: token,
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
Response.sendErrorResponse(req, res, new ServerException());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import AuthenticationAPI from './API/Authentication';
|
||||
import ResellerAPI from './API/Reseller';
|
||||
import SsoAPI from './API/SSO';
|
||||
import StatusPageAuthenticationAPI from './API/StatusPageAuthentication';
|
||||
import StatusPageSsoAPI from './API/StatusPageSSO';
|
||||
import FeatureSet from 'CommonServer/Types/FeatureSet';
|
||||
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
|
||||
import 'ejs';
|
||||
import AuthenticationAPI from "./API/Authentication";
|
||||
import ResellerAPI from "./API/Reseller";
|
||||
import SsoAPI from "./API/SSO";
|
||||
import StatusPageAuthenticationAPI from "./API/StatusPageAuthentication";
|
||||
import StatusPageSsoAPI from "./API/StatusPageSSO";
|
||||
import FeatureSet from "CommonServer/Types/FeatureSet";
|
||||
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
|
||||
import "ejs";
|
||||
|
||||
const IdentityFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
init: async (): Promise<void> => {
|
||||
const app: ExpressApplication = Express.getExpressApp();
|
||||
|
||||
const APP_NAME: string = 'api/identity';
|
||||
const APP_NAME: string = "api/identity";
|
||||
|
||||
app.use([`/${APP_NAME}`, '/'], AuthenticationAPI);
|
||||
app.use([`/${APP_NAME}`, "/"], AuthenticationAPI);
|
||||
|
||||
app.use([`/${APP_NAME}`, '/'], ResellerAPI);
|
||||
app.use([`/${APP_NAME}`, "/"], ResellerAPI);
|
||||
|
||||
app.use([`/${APP_NAME}`, '/'], SsoAPI);
|
||||
app.use([`/${APP_NAME}`, "/"], SsoAPI);
|
||||
|
||||
app.use([`/${APP_NAME}`, '/'], StatusPageSsoAPI);
|
||||
app.use([`/${APP_NAME}`, "/"], StatusPageSsoAPI);
|
||||
|
||||
app.use(
|
||||
[`/${APP_NAME}/status-page`, '/status-page'],
|
||||
StatusPageAuthenticationAPI
|
||||
);
|
||||
},
|
||||
app.use(
|
||||
[`/${APP_NAME}/status-page`, "/status-page"],
|
||||
StatusPageAuthenticationAPI,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default IdentityFeatureSet;
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
import { AccountsRoute } from 'Common/ServiceRoute';
|
||||
import Hostname from 'Common/Types/API/Hostname';
|
||||
import Protocol from 'Common/Types/API/Protocol';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import Email from 'Common/Types/Email';
|
||||
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import DatabaseConfig from 'CommonServer/DatabaseConfig';
|
||||
import EmailVerificationTokenService from 'CommonServer/Services/EmailVerificationTokenService';
|
||||
import MailService from 'CommonServer/Services/MailService';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import EmailVerificationToken from 'Model/Models/EmailVerificationToken';
|
||||
import User from 'Model/Models/User';
|
||||
import { AccountsRoute } from "Common/ServiceRoute";
|
||||
import Hostname from "Common/Types/API/Hostname";
|
||||
import Protocol from "Common/Types/API/Protocol";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import Email from "Common/Types/Email";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import DatabaseConfig from "CommonServer/DatabaseConfig";
|
||||
import EmailVerificationTokenService from "CommonServer/Services/EmailVerificationTokenService";
|
||||
import MailService from "CommonServer/Services/MailService";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import EmailVerificationToken from "Model/Models/EmailVerificationToken";
|
||||
import User from "Model/Models/User";
|
||||
|
||||
export default class AuthenticationEmail {
|
||||
public static async sendVerificationEmail(user: User): Promise<void> {
|
||||
const generatedToken: ObjectID = ObjectID.generate();
|
||||
public static async sendVerificationEmail(user: User): Promise<void> {
|
||||
const generatedToken: ObjectID = ObjectID.generate();
|
||||
|
||||
const emailVerificationToken: EmailVerificationToken =
|
||||
new EmailVerificationToken();
|
||||
emailVerificationToken.userId = user?.id as ObjectID;
|
||||
emailVerificationToken.email = user?.email as Email;
|
||||
emailVerificationToken.token = generatedToken;
|
||||
emailVerificationToken.expires = OneUptimeDate.getOneDayAfter();
|
||||
const emailVerificationToken: EmailVerificationToken =
|
||||
new EmailVerificationToken();
|
||||
emailVerificationToken.userId = user?.id as ObjectID;
|
||||
emailVerificationToken.email = user?.email as Email;
|
||||
emailVerificationToken.token = generatedToken;
|
||||
emailVerificationToken.expires = OneUptimeDate.getOneDayAfter();
|
||||
|
||||
await EmailVerificationTokenService.create({
|
||||
data: emailVerificationToken,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
await EmailVerificationTokenService.create({
|
||||
data: emailVerificationToken,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
const host: Hostname = await DatabaseConfig.getHost();
|
||||
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
||||
|
||||
MailService.sendMail({
|
||||
toEmail: user.email!,
|
||||
subject: 'Please verify email.',
|
||||
templateType: EmailTemplateType.SignupWelcomeEmail,
|
||||
vars: {
|
||||
name: user.name?.toString() || '',
|
||||
tokenVerifyUrl: new URL(
|
||||
httpProtocol,
|
||||
host,
|
||||
new Route(AccountsRoute.toString()).addRoute(
|
||||
'/verify-email/' + generatedToken.toString()
|
||||
)
|
||||
).toString(),
|
||||
homeUrl: new URL(httpProtocol, host).toString(),
|
||||
},
|
||||
}).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
MailService.sendMail({
|
||||
toEmail: user.email!,
|
||||
subject: "Please verify email.",
|
||||
templateType: EmailTemplateType.SignupWelcomeEmail,
|
||||
vars: {
|
||||
name: user.name?.toString() || "",
|
||||
tokenVerifyUrl: new URL(
|
||||
httpProtocol,
|
||||
host,
|
||||
new Route(AccountsRoute.toString()).addRoute(
|
||||
"/verify-email/" + generatedToken.toString(),
|
||||
),
|
||||
).toString(),
|
||||
homeUrl: new URL(httpProtocol, host).toString(),
|
||||
},
|
||||
}).catch((err: Error) => {
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,230 +1,220 @@
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import Email from 'Common/Types/Email';
|
||||
import BadRequestException from 'Common/Types/Exception/BadRequestException';
|
||||
import { JSONArray, JSONObject } from 'Common/Types/JSON';
|
||||
import Text from 'Common/Types/Text';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import xmlCrypto, { FileKeyInfo } from 'xml-crypto';
|
||||
import xmldom from 'xmldom';
|
||||
import zlib from 'zlib';
|
||||
import URL from "Common/Types/API/URL";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import Email from "Common/Types/Email";
|
||||
import BadRequestException from "Common/Types/Exception/BadRequestException";
|
||||
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
||||
import Text from "Common/Types/Text";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import xmlCrypto, { FileKeyInfo } from "xml-crypto";
|
||||
import xmldom from "xmldom";
|
||||
import zlib from "zlib";
|
||||
|
||||
export default class SSOUtil {
|
||||
public static createSAMLRequestUrl(data: {
|
||||
acsUrl: URL;
|
||||
signOnUrl: URL;
|
||||
issuerUrl: URL;
|
||||
}): URL {
|
||||
const { acsUrl, signOnUrl } = data;
|
||||
public static createSAMLRequestUrl(data: {
|
||||
acsUrl: URL;
|
||||
signOnUrl: URL;
|
||||
issuerUrl: URL;
|
||||
}): URL {
|
||||
const { acsUrl, signOnUrl } = data;
|
||||
|
||||
const samlRequest: string = `<samlp:AuthnRequest xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="${Text.generateRandomText(
|
||||
10
|
||||
).toUpperCase()}" Version="2.0" IssueInstant="${OneUptimeDate.getCurrentDate().toISOString()}" IsPassive="false" AssertionConsumerServiceURL="${acsUrl.toString()}" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ForceAuthn="false"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">${data.issuerUrl.toString()}</Issuer></samlp:AuthnRequest>`;
|
||||
const samlRequest: string = `<samlp:AuthnRequest xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="${Text.generateRandomText(
|
||||
10,
|
||||
).toUpperCase()}" Version="2.0" IssueInstant="${OneUptimeDate.getCurrentDate().toISOString()}" IsPassive="false" AssertionConsumerServiceURL="${acsUrl.toString()}" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ForceAuthn="false"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">${data.issuerUrl.toString()}</Issuer></samlp:AuthnRequest>`;
|
||||
|
||||
const deflated: Buffer = zlib.deflateRawSync(samlRequest);
|
||||
const deflated: Buffer = zlib.deflateRawSync(samlRequest);
|
||||
|
||||
const base64Encoded: string = deflated.toString('base64');
|
||||
const base64Encoded: string = deflated.toString("base64");
|
||||
|
||||
return URL.fromString(signOnUrl.toString()).addQueryParam(
|
||||
'SAMLRequest',
|
||||
base64Encoded,
|
||||
true
|
||||
);
|
||||
return URL.fromString(signOnUrl.toString()).addQueryParam(
|
||||
"SAMLRequest",
|
||||
base64Encoded,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
public static isPayloadValid(payload: JSONObject): void {
|
||||
if (
|
||||
!payload["saml2p:Response"] &&
|
||||
!payload["samlp:Response"] &&
|
||||
!payload["samlp:Response"]
|
||||
) {
|
||||
throw new BadRequestException("SAML Response not found.");
|
||||
}
|
||||
|
||||
public static isPayloadValid(payload: JSONObject): void {
|
||||
if (
|
||||
!payload['saml2p:Response'] &&
|
||||
!payload['samlp:Response'] &&
|
||||
!payload['samlp:Response']
|
||||
) {
|
||||
throw new BadRequestException('SAML Response not found.');
|
||||
}
|
||||
payload =
|
||||
(payload["saml2p:Response"] as JSONObject) ||
|
||||
(payload["samlp:Response"] as JSONObject) ||
|
||||
(payload["samlp:Response"] as JSONObject) ||
|
||||
(payload["Response"] as JSONObject);
|
||||
|
||||
payload =
|
||||
(payload['saml2p:Response'] as JSONObject) ||
|
||||
(payload['samlp:Response'] as JSONObject) ||
|
||||
(payload['samlp:Response'] as JSONObject) ||
|
||||
(payload['Response'] as JSONObject);
|
||||
const issuers: JSONArray =
|
||||
(payload["saml2:Issuer"] as JSONArray) ||
|
||||
(payload["saml:Issuer"] as JSONArray) ||
|
||||
(payload["Issuer"] as JSONArray);
|
||||
|
||||
const issuers: JSONArray =
|
||||
(payload['saml2:Issuer'] as JSONArray) ||
|
||||
(payload['saml:Issuer'] as JSONArray) ||
|
||||
(payload['Issuer'] as JSONArray);
|
||||
|
||||
if (issuers.length === 0) {
|
||||
throw new BadRequestException('Issuers not found');
|
||||
}
|
||||
|
||||
const issuer: JSONObject | string | undefined = issuers[0];
|
||||
|
||||
if (typeof issuer === 'string') {
|
||||
return issuer;
|
||||
}
|
||||
if (!issuer) {
|
||||
throw new BadRequestException('Issuer not found');
|
||||
}
|
||||
|
||||
const issuerUrl: string = issuer['_'] as string;
|
||||
|
||||
if (!issuerUrl) {
|
||||
throw new BadRequestException(
|
||||
'Issuer URL not found in SAML response'
|
||||
);
|
||||
}
|
||||
|
||||
const samlAssertion: JSONArray =
|
||||
(payload['saml2:Assertion'] as JSONArray) ||
|
||||
(payload['saml:Assertion'] as JSONArray) ||
|
||||
(payload['Assertion'] as JSONArray);
|
||||
|
||||
if (!samlAssertion || samlAssertion.length === 0) {
|
||||
throw new BadRequestException('SAML Assertion not found');
|
||||
}
|
||||
|
||||
const samlSubject: JSONArray =
|
||||
((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)['Subject'] as JSONArray);
|
||||
|
||||
if (!samlSubject || samlSubject.length === 0) {
|
||||
throw new BadRequestException('SAML Subject not found');
|
||||
}
|
||||
|
||||
const samlNameId: JSONArray =
|
||||
((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)['NameID'] as JSONArray);
|
||||
|
||||
if (!samlNameId || samlNameId.length === 0) {
|
||||
throw new BadRequestException('SAML NAME ID not found');
|
||||
}
|
||||
|
||||
const emailString: string = (samlNameId[0] as JSONObject)[
|
||||
'_'
|
||||
] as string;
|
||||
|
||||
if (!emailString) {
|
||||
if (!samlNameId || samlNameId.length === 0) {
|
||||
throw new BadRequestException('SAML Email not found');
|
||||
}
|
||||
}
|
||||
if (issuers.length === 0) {
|
||||
throw new BadRequestException("Issuers not found");
|
||||
}
|
||||
|
||||
public static isSignatureValid(
|
||||
samlPayload: string,
|
||||
certificate: string
|
||||
): boolean {
|
||||
try {
|
||||
const dom: Document = new xmldom.DOMParser().parseFromString(
|
||||
samlPayload
|
||||
);
|
||||
const signature: Element | undefined = dom.getElementsByTagNameNS(
|
||||
'http://www.w3.org/2000/09/xmldsig#',
|
||||
'Signature'
|
||||
)[0];
|
||||
const sig: xmlCrypto.SignedXml = new xmlCrypto.SignedXml();
|
||||
const issuer: JSONObject | string | undefined = issuers[0];
|
||||
|
||||
sig.keyInfoProvider = {
|
||||
getKeyInfo: function (_key: any) {
|
||||
return `<X509Data><X509Certificate>${certificate}</X509Certificate></X509Data>`;
|
||||
},
|
||||
getKey: function () {
|
||||
return certificate;
|
||||
} as any,
|
||||
} as FileKeyInfo;
|
||||
|
||||
sig.loadSignature(signature!.toString());
|
||||
const res: boolean = sig.checkSignature(samlPayload);
|
||||
|
||||
return res;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return false;
|
||||
}
|
||||
if (typeof issuer === "string") {
|
||||
return issuer;
|
||||
}
|
||||
if (!issuer) {
|
||||
throw new BadRequestException("Issuer not found");
|
||||
}
|
||||
|
||||
public static getEmail(payload: JSONObject): Email {
|
||||
if (!payload['saml2p:Response'] && !payload['samlp:Response']) {
|
||||
throw new BadRequestException('SAML Response not found.');
|
||||
}
|
||||
const issuerUrl: string = issuer["_"] as string;
|
||||
|
||||
payload =
|
||||
(payload['saml2p:Response'] as JSONObject) ||
|
||||
(payload['samlp:Response'] as JSONObject) ||
|
||||
(payload['Response'] as JSONObject);
|
||||
|
||||
const samlAssertion: JSONArray =
|
||||
(payload['saml2:Assertion'] as JSONArray) ||
|
||||
(payload['saml:Assertion'] as JSONArray) ||
|
||||
(payload['Assertion'] as JSONArray);
|
||||
|
||||
if (!samlAssertion || samlAssertion.length === 0) {
|
||||
throw new BadRequestException('SAML Assertion not found');
|
||||
}
|
||||
|
||||
const samlSubject: JSONArray =
|
||||
((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)['Subject'] as JSONArray);
|
||||
|
||||
if (!samlSubject || samlSubject.length === 0) {
|
||||
throw new BadRequestException('SAML Subject not found');
|
||||
}
|
||||
|
||||
const samlNameId: JSONArray =
|
||||
((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)['NameID'] as JSONArray);
|
||||
|
||||
if (!samlNameId || samlNameId.length === 0) {
|
||||
throw new BadRequestException('SAML NAME ID not found');
|
||||
}
|
||||
|
||||
const emailString: string = (samlNameId[0] as JSONObject)[
|
||||
'_'
|
||||
] as string;
|
||||
|
||||
return new Email(emailString.trim());
|
||||
if (!issuerUrl) {
|
||||
throw new BadRequestException("Issuer URL not found in SAML response");
|
||||
}
|
||||
|
||||
public static getIssuer(payload: JSONObject): string {
|
||||
if (!payload['saml2p:Response'] && !payload['samlp:Response']) {
|
||||
throw new BadRequestException('SAML Response not found.');
|
||||
}
|
||||
const samlAssertion: JSONArray =
|
||||
(payload["saml2:Assertion"] as JSONArray) ||
|
||||
(payload["saml:Assertion"] as JSONArray) ||
|
||||
(payload["Assertion"] as JSONArray);
|
||||
|
||||
payload =
|
||||
(payload['saml2p:Response'] as JSONObject) ||
|
||||
(payload['samlp:Response'] as JSONObject) ||
|
||||
(payload['Response'] as JSONObject);
|
||||
|
||||
const issuers: JSONArray =
|
||||
(payload['saml2:Issuer'] as JSONArray) ||
|
||||
(payload['saml:Issuer'] as JSONArray) ||
|
||||
(payload['Issuer'] as JSONArray);
|
||||
|
||||
if (issuers.length === 0) {
|
||||
throw new BadRequestException('Issuers not found');
|
||||
}
|
||||
|
||||
const issuer: JSONObject | string | undefined = issuers[0];
|
||||
|
||||
if (typeof issuer === 'string') {
|
||||
return issuer;
|
||||
}
|
||||
|
||||
if (!issuer) {
|
||||
throw new BadRequestException('Issuer not found');
|
||||
}
|
||||
|
||||
const issuerUrl: string = issuer['_'] as string;
|
||||
|
||||
if (!issuerUrl) {
|
||||
throw new BadRequestException(
|
||||
'Issuer URL not found in SAML response'
|
||||
);
|
||||
}
|
||||
|
||||
return issuerUrl.trim();
|
||||
if (!samlAssertion || samlAssertion.length === 0) {
|
||||
throw new BadRequestException("SAML Assertion not found");
|
||||
}
|
||||
|
||||
const samlSubject: JSONArray =
|
||||
((samlAssertion[0] as JSONObject)["saml2:Subject"] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)["saml:Subject"] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)["Subject"] as JSONArray);
|
||||
|
||||
if (!samlSubject || samlSubject.length === 0) {
|
||||
throw new BadRequestException("SAML Subject not found");
|
||||
}
|
||||
|
||||
const samlNameId: JSONArray =
|
||||
((samlSubject[0] as JSONObject)["saml2:NameID"] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)["saml:NameID"] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)["NameID"] as JSONArray);
|
||||
|
||||
if (!samlNameId || samlNameId.length === 0) {
|
||||
throw new BadRequestException("SAML NAME ID not found");
|
||||
}
|
||||
|
||||
const emailString: string = (samlNameId[0] as JSONObject)["_"] as string;
|
||||
|
||||
if (!emailString) {
|
||||
if (!samlNameId || samlNameId.length === 0) {
|
||||
throw new BadRequestException("SAML Email not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static isSignatureValid(
|
||||
samlPayload: string,
|
||||
certificate: string,
|
||||
): boolean {
|
||||
try {
|
||||
const dom: Document = new xmldom.DOMParser().parseFromString(samlPayload);
|
||||
const signature: Element | undefined = dom.getElementsByTagNameNS(
|
||||
"http://www.w3.org/2000/09/xmldsig#",
|
||||
"Signature",
|
||||
)[0];
|
||||
const sig: xmlCrypto.SignedXml = new xmlCrypto.SignedXml();
|
||||
|
||||
sig.keyInfoProvider = {
|
||||
getKeyInfo: function (_key: any) {
|
||||
return `<X509Data><X509Certificate>${certificate}</X509Certificate></X509Data>`;
|
||||
},
|
||||
getKey: function () {
|
||||
return certificate;
|
||||
} as any,
|
||||
} as FileKeyInfo;
|
||||
|
||||
sig.loadSignature(signature!.toString());
|
||||
const res: boolean = sig.checkSignature(samlPayload);
|
||||
|
||||
return res;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static getEmail(payload: JSONObject): Email {
|
||||
if (!payload["saml2p:Response"] && !payload["samlp:Response"]) {
|
||||
throw new BadRequestException("SAML Response not found.");
|
||||
}
|
||||
|
||||
payload =
|
||||
(payload["saml2p:Response"] as JSONObject) ||
|
||||
(payload["samlp:Response"] as JSONObject) ||
|
||||
(payload["Response"] as JSONObject);
|
||||
|
||||
const samlAssertion: JSONArray =
|
||||
(payload["saml2:Assertion"] as JSONArray) ||
|
||||
(payload["saml:Assertion"] as JSONArray) ||
|
||||
(payload["Assertion"] as JSONArray);
|
||||
|
||||
if (!samlAssertion || samlAssertion.length === 0) {
|
||||
throw new BadRequestException("SAML Assertion not found");
|
||||
}
|
||||
|
||||
const samlSubject: JSONArray =
|
||||
((samlAssertion[0] as JSONObject)["saml2:Subject"] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)["saml:Subject"] as JSONArray) ||
|
||||
((samlAssertion[0] as JSONObject)["Subject"] as JSONArray);
|
||||
|
||||
if (!samlSubject || samlSubject.length === 0) {
|
||||
throw new BadRequestException("SAML Subject not found");
|
||||
}
|
||||
|
||||
const samlNameId: JSONArray =
|
||||
((samlSubject[0] as JSONObject)["saml2:NameID"] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)["saml:NameID"] as JSONArray) ||
|
||||
((samlSubject[0] as JSONObject)["NameID"] as JSONArray);
|
||||
|
||||
if (!samlNameId || samlNameId.length === 0) {
|
||||
throw new BadRequestException("SAML NAME ID not found");
|
||||
}
|
||||
|
||||
const emailString: string = (samlNameId[0] as JSONObject)["_"] as string;
|
||||
|
||||
return new Email(emailString.trim());
|
||||
}
|
||||
|
||||
public static getIssuer(payload: JSONObject): string {
|
||||
if (!payload["saml2p:Response"] && !payload["samlp:Response"]) {
|
||||
throw new BadRequestException("SAML Response not found.");
|
||||
}
|
||||
|
||||
payload =
|
||||
(payload["saml2p:Response"] as JSONObject) ||
|
||||
(payload["samlp:Response"] as JSONObject) ||
|
||||
(payload["Response"] as JSONObject);
|
||||
|
||||
const issuers: JSONArray =
|
||||
(payload["saml2:Issuer"] as JSONArray) ||
|
||||
(payload["saml:Issuer"] as JSONArray) ||
|
||||
(payload["Issuer"] as JSONArray);
|
||||
|
||||
if (issuers.length === 0) {
|
||||
throw new BadRequestException("Issuers not found");
|
||||
}
|
||||
|
||||
const issuer: JSONObject | string | undefined = issuers[0];
|
||||
|
||||
if (typeof issuer === "string") {
|
||||
return issuer;
|
||||
}
|
||||
|
||||
if (!issuer) {
|
||||
throw new BadRequestException("Issuer not found");
|
||||
}
|
||||
|
||||
const issuerUrl: string = issuer["_"] as string;
|
||||
|
||||
if (!issuerUrl) {
|
||||
throw new BadRequestException("Issuer URL not found in SAML response");
|
||||
}
|
||||
|
||||
return issuerUrl.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +1,143 @@
|
||||
import CallService from '../Services/CallService';
|
||||
import CallRequest from 'Common/Types/Call/CallRequest';
|
||||
import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import JSONFunctions from 'Common/Types/JSONFunctions';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import Phone from 'Common/Types/Phone';
|
||||
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
|
||||
import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService';
|
||||
import CallService from "../Services/CallService";
|
||||
import CallRequest from "Common/Types/Call/CallRequest";
|
||||
import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Phone from "Common/Types/Phone";
|
||||
import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization";
|
||||
import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig';
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post(
|
||||
'/make-call',
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
"/make-call",
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
|
||||
await CallService.makeCall(body['callRequest'] as CallRequest, {
|
||||
projectId: body['projectId'] as ObjectID,
|
||||
isSensitive: (body['isSensitive'] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body['userOnCallLogTimelineId'] as ObjectID) || undefined,
|
||||
customTwilioConfig: body['customTwilioConfig'] as any,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const callSMSConfigId: ObjectID = new ObjectID(
|
||||
body['callSMSConfigId'] as string
|
||||
);
|
||||
|
||||
const config: ProjectCallSMSConfig | null =
|
||||
await ProjectCallSMSConfigService.findOneById({
|
||||
id: callSMSConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPhoneNumber: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'call and sms config not found for id' +
|
||||
callSMSConfigId.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const toPhone: Phone = new Phone(body['toPhone'] as string);
|
||||
|
||||
if (!toPhone) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('toPhone is required')
|
||||
);
|
||||
}
|
||||
|
||||
// if any of the twilio config is missing, we will not send make the call
|
||||
|
||||
if (!config.twilioAccountSID) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('twilioAccountSID is required')
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioAuthToken) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('twilioAuthToken is required')
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioPhoneNumber) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('twilioPhoneNumber is required')
|
||||
);
|
||||
}
|
||||
|
||||
const twilioConfig: TwilioConfig | undefined =
|
||||
ProjectCallSMSConfigService.toTwilioConfig(config);
|
||||
|
||||
try {
|
||||
if (!twilioConfig) {
|
||||
throw new BadDataException('twilioConfig is undefined');
|
||||
}
|
||||
|
||||
const testCallRequest: CallRequest = {
|
||||
data: [
|
||||
{
|
||||
sayMessage: 'This is a test call from OneUptime.',
|
||||
},
|
||||
],
|
||||
to: toPhone,
|
||||
};
|
||||
|
||||
await CallService.makeCall(testCallRequest, {
|
||||
projectId: config.projectId,
|
||||
customTwilioConfig: twilioConfig,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'Error making test call. Please check the twilio logs for more details'
|
||||
)
|
||||
);
|
||||
}
|
||||
await CallService.makeCall(body["callRequest"] as CallRequest, {
|
||||
projectId: body["projectId"] as ObjectID,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
customTwilioConfig: body["customTwilioConfig"] as any,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const callSMSConfigId: ObjectID = new ObjectID(
|
||||
body["callSMSConfigId"] as string,
|
||||
);
|
||||
|
||||
const config: ProjectCallSMSConfig | null =
|
||||
await ProjectCallSMSConfigService.findOneById({
|
||||
id: callSMSConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPhoneNumber: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"call and sms config not found for id" + callSMSConfigId.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const toPhone: Phone = new Phone(body["toPhone"] as string);
|
||||
|
||||
if (!toPhone) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("toPhone is required"),
|
||||
);
|
||||
}
|
||||
|
||||
// if any of the twilio config is missing, we will not send make the call
|
||||
|
||||
if (!config.twilioAccountSID) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAccountSID is required"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioAuthToken) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAuthToken is required"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioPhoneNumber) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioPhoneNumber is required"),
|
||||
);
|
||||
}
|
||||
|
||||
const twilioConfig: TwilioConfig | undefined =
|
||||
ProjectCallSMSConfigService.toTwilioConfig(config);
|
||||
|
||||
try {
|
||||
if (!twilioConfig) {
|
||||
throw new BadDataException("twilioConfig is undefined");
|
||||
}
|
||||
|
||||
const testCallRequest: CallRequest = {
|
||||
data: [
|
||||
{
|
||||
sayMessage: "This is a test call from OneUptime.",
|
||||
},
|
||||
],
|
||||
to: toPhone,
|
||||
};
|
||||
|
||||
await CallService.makeCall(testCallRequest, {
|
||||
projectId: config.projectId,
|
||||
customTwilioConfig: twilioConfig,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"Error making test call. Please check the twilio logs for more details",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
import MailService from '../Services/MailService';
|
||||
import Dictionary from 'Common/Types/Dictionary';
|
||||
import Email from 'Common/Types/Email';
|
||||
import EmailMessage from 'Common/Types/Email/EmailMessage';
|
||||
import EmailServer from 'Common/Types/Email/EmailServer';
|
||||
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
|
||||
import MailService from "../Services/MailService";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import Email from "Common/Types/Email";
|
||||
import EmailMessage from "Common/Types/Email/EmailMessage";
|
||||
import EmailServer from "Common/Types/Email/EmailServer";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post(
|
||||
'/send',
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
"/send",
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const mail: EmailMessage = {
|
||||
templateType: body['templateType'] as EmailTemplateType,
|
||||
toEmail: new Email(body['toEmail'] as string),
|
||||
subject: body['subject'] as string,
|
||||
vars: body['vars'] as Dictionary<string>,
|
||||
body: (body['body'] as string) || '',
|
||||
};
|
||||
const mail: EmailMessage = {
|
||||
templateType: body["templateType"] as EmailTemplateType,
|
||||
toEmail: new Email(body["toEmail"] as string),
|
||||
subject: body["subject"] as string,
|
||||
vars: body["vars"] as Dictionary<string>,
|
||||
body: (body["body"] as string) || "",
|
||||
};
|
||||
|
||||
let mailServer: EmailServer | undefined = undefined;
|
||||
let mailServer: EmailServer | undefined = undefined;
|
||||
|
||||
if (hasMailServerSettingsInBody(body)) {
|
||||
mailServer = MailService.getEmailServer(req.body);
|
||||
}
|
||||
|
||||
await MailService.send(mail, {
|
||||
projectId: body['projectId']
|
||||
? new ObjectID(body['projectId'] as string)
|
||||
: undefined,
|
||||
emailServer: mailServer,
|
||||
userOnCallLogTimelineId:
|
||||
(body['userOnCallLogTimelineId'] as ObjectID) || undefined,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
if (hasMailServerSettingsInBody(body)) {
|
||||
mailServer = MailService.getEmailServer(req.body);
|
||||
}
|
||||
|
||||
await MailService.send(mail, {
|
||||
projectId: body["projectId"]
|
||||
? new ObjectID(body["projectId"] as string)
|
||||
: undefined,
|
||||
emailServer: mailServer,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
type HasMailServerSettingsInBody = (body: JSONObject) => boolean;
|
||||
|
||||
const hasMailServerSettingsInBody: HasMailServerSettingsInBody = (
|
||||
body: JSONObject
|
||||
body: JSONObject,
|
||||
): boolean => {
|
||||
return (
|
||||
body &&
|
||||
Object.keys(body).filter((key: string) => {
|
||||
return key.startsWith('SMTP_');
|
||||
}).length > 0
|
||||
);
|
||||
return (
|
||||
body &&
|
||||
Object.keys(body).filter((key: string) => {
|
||||
return key.startsWith("SMTP_");
|
||||
}).length > 0
|
||||
);
|
||||
};
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,142 +1,133 @@
|
||||
import SmsService from '../Services/SmsService';
|
||||
import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import JSONFunctions from 'Common/Types/JSONFunctions';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import Phone from 'Common/Types/Phone';
|
||||
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
|
||||
import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService';
|
||||
import SmsService from "../Services/SmsService";
|
||||
import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Phone from "Common/Types/Phone";
|
||||
import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization";
|
||||
import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig';
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post(
|
||||
'/send',
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
"/send",
|
||||
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
|
||||
async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = JSONFunctions.deserialize(req.body);
|
||||
|
||||
await SmsService.sendSms(
|
||||
body['to'] as Phone,
|
||||
body['message'] as string,
|
||||
{
|
||||
projectId: body['projectId'] as ObjectID,
|
||||
isSensitive: (body['isSensitive'] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body['userOnCallLogTimelineId'] as ObjectID) || undefined,
|
||||
customTwilioConfig: body['customTwilioConfig'] as any,
|
||||
}
|
||||
);
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
}
|
||||
);
|
||||
|
||||
router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const callSMSConfigId: ObjectID = new ObjectID(
|
||||
body['callSMSConfigId'] as string
|
||||
);
|
||||
|
||||
const config: ProjectCallSMSConfig | null =
|
||||
await ProjectCallSMSConfigService.findOneById({
|
||||
id: callSMSConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPhoneNumber: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'call and sms config not found for id' +
|
||||
callSMSConfigId.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const toPhone: Phone = new Phone(body['toPhone'] as string);
|
||||
|
||||
if (!toPhone) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('toPhone is required')
|
||||
);
|
||||
}
|
||||
|
||||
// if any of the twilio config is missing, we will not send make the call
|
||||
|
||||
if (!config.twilioAccountSID) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('twilioAccountSID is required')
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioAuthToken) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('twilioAuthToken is required')
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioPhoneNumber) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('twilioPhoneNumber is required')
|
||||
);
|
||||
}
|
||||
|
||||
const twilioConfig: TwilioConfig | undefined =
|
||||
ProjectCallSMSConfigService.toTwilioConfig(config);
|
||||
|
||||
try {
|
||||
if (!twilioConfig) {
|
||||
throw new BadDataException('twilioConfig is undefined');
|
||||
}
|
||||
|
||||
await SmsService.sendSms(
|
||||
toPhone,
|
||||
'This is a test SMS from OneUptime.',
|
||||
{
|
||||
projectId: config.projectId,
|
||||
customTwilioConfig: twilioConfig,
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'Failed to send test SMS. Please check the twilio logs for more details.'
|
||||
)
|
||||
);
|
||||
}
|
||||
await SmsService.sendSms(body["to"] as Phone, body["message"] as string, {
|
||||
projectId: body["projectId"] as ObjectID,
|
||||
isSensitive: (body["isSensitive"] as boolean) || false,
|
||||
userOnCallLogTimelineId:
|
||||
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
|
||||
customTwilioConfig: body["customTwilioConfig"] as any,
|
||||
});
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
},
|
||||
);
|
||||
|
||||
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const callSMSConfigId: ObjectID = new ObjectID(
|
||||
body["callSMSConfigId"] as string,
|
||||
);
|
||||
|
||||
const config: ProjectCallSMSConfig | null =
|
||||
await ProjectCallSMSConfigService.findOneById({
|
||||
id: callSMSConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPhoneNumber: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"call and sms config not found for id" + callSMSConfigId.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const toPhone: Phone = new Phone(body["toPhone"] as string);
|
||||
|
||||
if (!toPhone) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("toPhone is required"),
|
||||
);
|
||||
}
|
||||
|
||||
// if any of the twilio config is missing, we will not send make the call
|
||||
|
||||
if (!config.twilioAccountSID) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAccountSID is required"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioAuthToken) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioAuthToken is required"),
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.twilioPhoneNumber) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("twilioPhoneNumber is required"),
|
||||
);
|
||||
}
|
||||
|
||||
const twilioConfig: TwilioConfig | undefined =
|
||||
ProjectCallSMSConfigService.toTwilioConfig(config);
|
||||
|
||||
try {
|
||||
if (!twilioConfig) {
|
||||
throw new BadDataException("twilioConfig is undefined");
|
||||
}
|
||||
|
||||
await SmsService.sendSms(toPhone, "This is a test SMS from OneUptime.", {
|
||||
projectId: config.projectId,
|
||||
customTwilioConfig: twilioConfig,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"Failed to send test SMS. Please check the twilio logs for more details.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,104 +1,104 @@
|
||||
import MailService from '../Services/MailService';
|
||||
import Email from 'Common/Types/Email';
|
||||
import EmailMessage from 'Common/Types/Email/EmailMessage';
|
||||
import EmailServer from 'Common/Types/Email/EmailServer';
|
||||
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import ProjectSMTPConfigService from 'CommonServer/Services/ProjectSmtpConfigService';
|
||||
import MailService from "../Services/MailService";
|
||||
import Email from "Common/Types/Email";
|
||||
import EmailMessage from "Common/Types/Email/EmailMessage";
|
||||
import EmailServer from "Common/Types/Email/EmailServer";
|
||||
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import ProjectSMTPConfigService from "CommonServer/Services/ProjectSmtpConfigService";
|
||||
import Express, {
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
} from 'CommonServer/Utils/Express';
|
||||
import logger from 'CommonServer/Utils/Logger';
|
||||
import Response from 'CommonServer/Utils/Response';
|
||||
import ProjectSmtpConfig from 'Model/Models/ProjectSmtpConfig';
|
||||
ExpressRequest,
|
||||
ExpressResponse,
|
||||
ExpressRouter,
|
||||
} from "CommonServer/Utils/Express";
|
||||
import logger from "CommonServer/Utils/Logger";
|
||||
import Response from "CommonServer/Utils/Response";
|
||||
import ProjectSmtpConfig from "Model/Models/ProjectSmtpConfig";
|
||||
|
||||
const router: ExpressRouter = Express.getRouter();
|
||||
|
||||
router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
|
||||
const body: JSONObject = req.body;
|
||||
|
||||
const smtpConfigId: ObjectID = new ObjectID(body['smtpConfigId'] as string);
|
||||
const smtpConfigId: ObjectID = new ObjectID(body["smtpConfigId"] as string);
|
||||
|
||||
const config: ProjectSmtpConfig | null =
|
||||
await ProjectSMTPConfigService.findOneById({
|
||||
id: smtpConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
hostname: true,
|
||||
port: true,
|
||||
username: true,
|
||||
password: true,
|
||||
fromEmail: true,
|
||||
fromName: true,
|
||||
secure: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
const config: ProjectSmtpConfig | null =
|
||||
await ProjectSMTPConfigService.findOneById({
|
||||
id: smtpConfigId,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
hostname: true,
|
||||
port: true,
|
||||
username: true,
|
||||
password: true,
|
||||
fromEmail: true,
|
||||
fromName: true,
|
||||
secure: true,
|
||||
projectId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'smtp-config not found for id' + smtpConfigId.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!config) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"smtp-config not found for id" + smtpConfigId.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const toEmail: Email = new Email(body['toEmail'] as string);
|
||||
const toEmail: Email = new Email(body["toEmail"] as string);
|
||||
|
||||
if (!toEmail) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException('toEmail is required')
|
||||
);
|
||||
}
|
||||
if (!toEmail) {
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException("toEmail is required"),
|
||||
);
|
||||
}
|
||||
|
||||
const mail: EmailMessage = {
|
||||
templateType: EmailTemplateType.SMTPTest,
|
||||
toEmail: new Email(body['toEmail'] as string),
|
||||
subject: 'Test Email from OneUptime',
|
||||
vars: {},
|
||||
body: '',
|
||||
};
|
||||
const mail: EmailMessage = {
|
||||
templateType: EmailTemplateType.SMTPTest,
|
||||
toEmail: new Email(body["toEmail"] as string),
|
||||
subject: "Test Email from OneUptime",
|
||||
vars: {},
|
||||
body: "",
|
||||
};
|
||||
|
||||
const mailServer: EmailServer = {
|
||||
id: config.id!,
|
||||
host: config.hostname!,
|
||||
port: config.port!,
|
||||
username: config.username!,
|
||||
password: config.password!,
|
||||
fromEmail: config.fromEmail!,
|
||||
fromName: config.fromName!,
|
||||
secure: Boolean(config.secure),
|
||||
};
|
||||
const mailServer: EmailServer = {
|
||||
id: config.id!,
|
||||
host: config.hostname!,
|
||||
port: config.port!,
|
||||
username: config.username!,
|
||||
password: config.password!,
|
||||
fromEmail: config.fromEmail!,
|
||||
fromName: config.fromName!,
|
||||
secure: Boolean(config.secure),
|
||||
};
|
||||
|
||||
try {
|
||||
await MailService.send(mail, {
|
||||
emailServer: mailServer,
|
||||
projectId: config.projectId!,
|
||||
timeout: 4000,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
'Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.'
|
||||
)
|
||||
);
|
||||
}
|
||||
try {
|
||||
await MailService.send(mail, {
|
||||
emailServer: mailServer,
|
||||
projectId: config.projectId!,
|
||||
timeout: 4000,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return Response.sendErrorResponse(
|
||||
req,
|
||||
res,
|
||||
new BadDataException(
|
||||
"Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
return Response.sendEmptySuccessResponse(req, res);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import Hostname from 'Common/Types/API/Hostname';
|
||||
import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig';
|
||||
import Email from 'Common/Types/Email';
|
||||
import EmailServer from 'Common/Types/Email/EmailServer';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import Port from 'Common/Types/Port';
|
||||
import GlobalConfigService from 'CommonServer/Services/GlobalConfigService';
|
||||
import GlobalConfig, { EmailServerType } from 'Model/Models/GlobalConfig';
|
||||
import Hostname from "Common/Types/API/Hostname";
|
||||
import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig";
|
||||
import Email from "Common/Types/Email";
|
||||
import EmailServer from "Common/Types/Email/EmailServer";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Port from "Common/Types/Port";
|
||||
import GlobalConfigService from "CommonServer/Services/GlobalConfigService";
|
||||
import GlobalConfig, { EmailServerType } from "Model/Models/GlobalConfig";
|
||||
|
||||
export const InternalSmtpPassword: string =
|
||||
process.env['INTERNAL_SMTP_PASSWORD'] || '';
|
||||
process.env["INTERNAL_SMTP_PASSWORD"] || "";
|
||||
|
||||
export const InternalSmtpHost: Hostname = new Hostname(
|
||||
process.env['INTERNAL_SMTP_HOST'] || 'haraka'
|
||||
process.env["INTERNAL_SMTP_HOST"] || "haraka",
|
||||
);
|
||||
|
||||
export const InternalSmtpPort: Port = new Port(2525);
|
||||
@@ -20,187 +20,187 @@ export const InternalSmtpPort: Port = new Port(2525);
|
||||
export const InternalSmtpSecure: boolean = false;
|
||||
|
||||
export const InternalSmtpEmail: Email = new Email(
|
||||
process.env['INTERNAL_SMTP_EMAIL'] || 'noreply@oneuptime.com'
|
||||
process.env["INTERNAL_SMTP_EMAIL"] || "noreply@oneuptime.com",
|
||||
);
|
||||
|
||||
export const InternalSmtpFromName: string =
|
||||
process.env['INTERNAL_SMTP_FROM_NAME'] || 'OneUptime';
|
||||
process.env["INTERNAL_SMTP_FROM_NAME"] || "OneUptime";
|
||||
|
||||
type GetGlobalSMTPConfig = () => Promise<EmailServer | null>;
|
||||
|
||||
export const getGlobalSMTPConfig: GetGlobalSMTPConfig =
|
||||
async (): Promise<EmailServer | null> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
smtpFromEmail: true,
|
||||
smtpHost: true,
|
||||
smtpPort: true,
|
||||
smtpUsername: true,
|
||||
smtpPassword: true,
|
||||
isSMTPSecure: true,
|
||||
smtpFromName: true,
|
||||
},
|
||||
});
|
||||
async (): Promise<EmailServer | null> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
smtpFromEmail: true,
|
||||
smtpHost: true,
|
||||
smtpPort: true,
|
||||
smtpUsername: true,
|
||||
smtpPassword: true,
|
||||
isSMTPSecure: true,
|
||||
smtpFromName: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!globalConfig) {
|
||||
throw new BadDataException('Global Config not found');
|
||||
}
|
||||
if (!globalConfig) {
|
||||
throw new BadDataException("Global Config not found");
|
||||
}
|
||||
|
||||
if (
|
||||
!globalConfig.smtpFromEmail ||
|
||||
!globalConfig.smtpHost ||
|
||||
!globalConfig.smtpPort ||
|
||||
!globalConfig.smtpUsername ||
|
||||
!globalConfig.smtpPassword ||
|
||||
!globalConfig.smtpFromName
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
!globalConfig.smtpFromEmail ||
|
||||
!globalConfig.smtpHost ||
|
||||
!globalConfig.smtpPort ||
|
||||
!globalConfig.smtpUsername ||
|
||||
!globalConfig.smtpPassword ||
|
||||
!globalConfig.smtpFromName
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
host: globalConfig.smtpHost,
|
||||
port: globalConfig.smtpPort,
|
||||
username: globalConfig.smtpUsername,
|
||||
password: globalConfig.smtpPassword,
|
||||
secure: globalConfig.isSMTPSecure || false,
|
||||
fromEmail: globalConfig.smtpFromEmail,
|
||||
fromName: globalConfig.smtpFromName,
|
||||
};
|
||||
return {
|
||||
host: globalConfig.smtpHost,
|
||||
port: globalConfig.smtpPort,
|
||||
username: globalConfig.smtpUsername,
|
||||
password: globalConfig.smtpPassword,
|
||||
secure: globalConfig.isSMTPSecure || false,
|
||||
fromEmail: globalConfig.smtpFromEmail,
|
||||
fromName: globalConfig.smtpFromName,
|
||||
};
|
||||
};
|
||||
|
||||
type GetEmailServerTypeFunction = () => Promise<EmailServerType>;
|
||||
|
||||
export const getEmailServerType: GetEmailServerTypeFunction =
|
||||
async (): Promise<EmailServerType> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
emailServerType: true,
|
||||
},
|
||||
});
|
||||
async (): Promise<EmailServerType> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
emailServerType: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!globalConfig) {
|
||||
return EmailServerType.Internal;
|
||||
}
|
||||
if (!globalConfig) {
|
||||
return EmailServerType.Internal;
|
||||
}
|
||||
|
||||
return globalConfig.emailServerType || EmailServerType.Internal;
|
||||
};
|
||||
return globalConfig.emailServerType || EmailServerType.Internal;
|
||||
};
|
||||
|
||||
export interface SendGridConfig {
|
||||
apiKey: string;
|
||||
fromName: string;
|
||||
fromEmail: Email;
|
||||
apiKey: string;
|
||||
fromName: string;
|
||||
fromEmail: Email;
|
||||
}
|
||||
|
||||
type GetSendgridConfigFunction = () => Promise<SendGridConfig | null>;
|
||||
|
||||
export const getSendgridConfig: GetSendgridConfigFunction =
|
||||
async (): Promise<SendGridConfig | null> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
sendgridApiKey: true,
|
||||
sendgridFromEmail: true,
|
||||
sendgridFromName: true,
|
||||
},
|
||||
});
|
||||
async (): Promise<SendGridConfig | null> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
sendgridApiKey: true,
|
||||
sendgridFromEmail: true,
|
||||
sendgridFromName: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!globalConfig) {
|
||||
return null;
|
||||
}
|
||||
if (!globalConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
globalConfig.sendgridApiKey &&
|
||||
globalConfig.sendgridFromEmail &&
|
||||
globalConfig.sendgridFromName
|
||||
) {
|
||||
return {
|
||||
apiKey: globalConfig.sendgridApiKey,
|
||||
fromName: globalConfig.sendgridFromName,
|
||||
fromEmail: globalConfig.sendgridFromEmail,
|
||||
};
|
||||
}
|
||||
if (
|
||||
globalConfig.sendgridApiKey &&
|
||||
globalConfig.sendgridFromEmail &&
|
||||
globalConfig.sendgridFromName
|
||||
) {
|
||||
return {
|
||||
apiKey: globalConfig.sendgridApiKey,
|
||||
fromName: globalConfig.sendgridFromName,
|
||||
fromEmail: globalConfig.sendgridFromEmail,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
return null;
|
||||
};
|
||||
|
||||
type GetTwilioConfigFunction = () => Promise<TwilioConfig | null>;
|
||||
|
||||
export const getTwilioConfig: GetTwilioConfigFunction =
|
||||
async (): Promise<TwilioConfig | null> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
});
|
||||
async (): Promise<TwilioConfig | null> => {
|
||||
const globalConfig: GlobalConfig | null =
|
||||
await GlobalConfigService.findOneBy({
|
||||
query: {
|
||||
_id: ObjectID.getZeroObjectID().toString(),
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
select: {
|
||||
twilioAccountSID: true,
|
||||
twilioAuthToken: true,
|
||||
twilioPhoneNumber: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!globalConfig) {
|
||||
throw new BadDataException('Global Config not found');
|
||||
}
|
||||
if (!globalConfig) {
|
||||
throw new BadDataException("Global Config not found");
|
||||
}
|
||||
|
||||
if (
|
||||
!globalConfig.twilioAccountSID ||
|
||||
!globalConfig.twilioAuthToken ||
|
||||
!globalConfig.twilioPhoneNumber
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
!globalConfig.twilioAccountSID ||
|
||||
!globalConfig.twilioAuthToken ||
|
||||
!globalConfig.twilioPhoneNumber
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
accountSid: globalConfig.twilioAccountSID,
|
||||
authToken: globalConfig.twilioAuthToken,
|
||||
phoneNumber: globalConfig.twilioPhoneNumber,
|
||||
};
|
||||
return {
|
||||
accountSid: globalConfig.twilioAccountSID,
|
||||
authToken: globalConfig.twilioAuthToken,
|
||||
phoneNumber: globalConfig.twilioPhoneNumber,
|
||||
};
|
||||
};
|
||||
|
||||
export const SMSDefaultCostInCents: number = process.env[
|
||||
'SMS_DEFAULT_COST_IN_CENTS'
|
||||
"SMS_DEFAULT_COST_IN_CENTS"
|
||||
]
|
||||
? parseInt(process.env['SMS_DEFAULT_COST_IN_CENTS'])
|
||||
: 0;
|
||||
? parseInt(process.env["SMS_DEFAULT_COST_IN_CENTS"])
|
||||
: 0;
|
||||
|
||||
export const SMSHighRiskCostInCents: number = process.env[
|
||||
'SMS_HIGH_RISK_COST_IN_CENTS'
|
||||
"SMS_HIGH_RISK_COST_IN_CENTS"
|
||||
]
|
||||
? parseInt(process.env['SMS_HIGH_RISK_COST_IN_CENTS'])
|
||||
: 0;
|
||||
? parseInt(process.env["SMS_HIGH_RISK_COST_IN_CENTS"])
|
||||
: 0;
|
||||
|
||||
export const CallHighRiskCostInCentsPerMinute: number = process.env[
|
||||
'CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE'
|
||||
"CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE"
|
||||
]
|
||||
? parseInt(process.env['CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE'])
|
||||
: 0;
|
||||
? parseInt(process.env["CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE"])
|
||||
: 0;
|
||||
|
||||
export const CallDefaultCostInCentsPerMinute: number = process.env[
|
||||
'CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE'
|
||||
"CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE"
|
||||
]
|
||||
? parseInt(process.env['CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE'])
|
||||
: 0;
|
||||
? parseInt(process.env["CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE"])
|
||||
: 0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user