Compare commits

...

400 Commits

Author SHA1 Message Date
Simon Larsen
aea1ec24ef refactor: Update EntityFilter to handle FieldType.Entity and FieldType.EntityArray filters 2024-05-09 12:01:31 +01:00
Simon Larsen
7c250a25e3 refactor: Update StatementGenerator to include createdAt and updatedAt in SELECT query 2024-05-09 11:54:28 +01:00
Simon Larsen
e7799adc1c feat: Update StatementGenerator to include createdAt in SELECT query 2024-05-09 10:54:04 +01:00
Simon Larsen
2c949150ef refactor: Improve error handling and formatting in DatabaseService 2024-05-09 10:54:04 +01:00
Simon Larsen
983892d8b3 refactor: Update AnalyticsDatabaseService to support groupBy in queries 2024-05-09 10:54:04 +01:00
Simon Larsen
054234116b refactor: Update sort field in StatusPageAPI to use startsAt instead of createdAt 2024-05-09 10:54:04 +01:00
Simon Larsen
d6aafa8493 feat: Add support for counting with groupBy in DatabaseService 2024-05-09 10:54:04 +01:00
Simon Larsen
b5407b47b2 feat: Add onViewPage function to Metrics component 2024-05-09 10:54:04 +01:00
Simon Larsen
f84bec7e66 refactor: Fix formatting and remove unnecessary code in AnalyticsDatabaseService 2024-05-09 10:54:04 +01:00
Simon Larsen
43354d850b feat: Add AnalyticsModelTable component to Metrics page 2024-05-09 10:54:04 +01:00
Simon Larsen
1977dc3e62 refactor: Add GroupBy support to analytics database queries 2024-05-09 10:54:04 +01:00
Simon Larsen
c2bd27b0f9 refactor: Add GroupBy support to analytics database queries 2024-05-09 10:54:04 +01:00
Simon Larsen
264c0f0347 Merge pull request #1379 from OneUptime/snyk-upgrade-c9d2d1e5a53e5e8a69bea4eb64fe195b
[Snyk] Upgrade css-loader from 6.10.0 to 6.11.0
2024-05-09 08:56:09 +01:00
snyk-bot
0de98ae262 fix: upgrade css-loader from 6.10.0 to 6.11.0
Snyk has created this PR to upgrade css-loader from 6.10.0 to 6.11.0.

See this package in npm:
https://www.npmjs.com/package/css-loader

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/11bb5528-45f9-473c-a635-dc097fd03b3c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-08 21:54:23 +00:00
Simon Larsen
573a12075b Merge pull request #1377 from OneUptime/snyk-upgrade-5ae69998d12b665f469b0997420ea928
[Snyk] Upgrade posthog-js from 1.125.0 to 1.126.0
2024-05-08 20:55:12 +01:00
snyk-bot
0a85e245d7 fix: upgrade posthog-js from 1.125.0 to 1.126.0
Snyk has created this PR to upgrade posthog-js from 1.125.0 to 1.126.0.

See this package in npm:
https://www.npmjs.com/package/posthog-js

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/f6446ec8-d441-487e-b58f-38373430e213?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-08 17:21:50 +00:00
Simon Larsen
41d01cceee Merge pull request #1373 from OneUptime/snyk-upgrade-0f03e56c8fcfe93e0825bfeb309bddb8
[Snyk] Upgrade @opentelemetry/instrumentation-xml-http-request from 0.49.1 to 0.50.0
2024-05-08 12:56:44 +01:00
Simon Larsen
dcd9aeccb3 Merge pull request #1371 from OneUptime/snyk-upgrade-3ed96983eb9a1ec2fc0be8285f56cff9
[Snyk] Upgrade @opentelemetry/exporter-trace-otlp-http from 0.49.1 to 0.50.0
2024-05-08 12:56:17 +01:00
Simon Larsen
c59ebc2373 refactor: Update monitor steps to include monitor name 2024-05-08 12:49:07 +01:00
Simon Larsen
643f4acddd refactor: Improve error handling in API class and include URL in exception message 2024-05-08 12:17:14 +01:00
Simon Larsen
b3fb21af59 refactor: Update markdown field types for root cause in Incident and IncidentStateTimeline models 2024-05-08 11:43:54 +01:00
Simon Larsen
2164f45a68 refactor: Update UptimeUtil to use startsAt and endsAt properties for event dates 2024-05-08 11:21:34 +01:00
Simon Larsen
8a4afe992c refactor: Improve calculation of incident duration in IncidentView component 2024-05-08 11:10:20 +01:00
Simon Larsen
f35e50bab3 refactor: Add startsAt property to monitorStatusTimelines in StatusPageAPI 2024-05-08 10:34:59 +01:00
Simon Larsen
63064c587c refactor: Remove dark mode styles from progress bar and pagination components 2024-05-08 08:41:59 +01:00
snyk-bot
edd11ffade fix: upgrade @opentelemetry/instrumentation-xml-http-request from 0.49.1 to 0.50.0
Snyk has created this PR to upgrade @opentelemetry/instrumentation-xml-http-request from 0.49.1 to 0.50.0.

See this package in npm:
https://www.npmjs.com/package/@opentelemetry/instrumentation-xml-http-request

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/47001ef1-7b3a-49c2-88cd-8025c56346d0?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-07 21:06:49 +00:00
snyk-bot
b7e97a29ee fix: upgrade @opentelemetry/exporter-trace-otlp-http from 0.49.1 to 0.50.0
Snyk has created this PR to upgrade @opentelemetry/exporter-trace-otlp-http from 0.49.1 to 0.50.0.

See this package in npm:
https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-http

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/47001ef1-7b3a-49c2-88cd-8025c56346d0?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-07 21:06:41 +00:00
Simon Larsen
fdf6e3dfa3 refactor: Optimize sorting of monitorStatusTimelines by createdAt in StatusPageAPI 2024-05-07 20:01:37 +01:00
Simon Larsen
b59b8257cc refactor: Use explicit type annotations for mutex and mutexId in Semaphore class 2024-05-06 22:42:09 +01:00
Simon Larsen
9bde353e93 refactor: Add Semaphore locking for monitor probe creation and release mutex after completion 2024-05-06 22:18:14 +01:00
Simon Larsen
a2c2867469 refactor: Add Semaphore locking for monitor probe creation and release mutex after completion 2024-05-06 21:52:09 +01:00
Simon Larsen
bd3ba94da8 chore: Update package-lock.json and package.json to include redis-semaphore dependency 2024-05-06 21:47:47 +01:00
Simon Larsen
8e6800c17b refactor: Update UserAuthorization middleware to improve project permissions handling 2024-05-06 20:47:15 +01:00
Simon Larsen
e481e043dd refactor: Update REDIS_PORT value in _helpers.tpl to use dynamic value from values.yaml 2024-05-06 20:47:09 +01:00
Simon Larsen
a4e6552c91 refactor: Disable redis persistence and add common configuration in values.yaml 2024-05-06 15:20:42 +01:00
Simon Larsen
6ce5cde46e Merge pull request #1365 from OneUptime/snyk-upgrade-e95d2830e79bc050ba332cb3d9670e5b
[Snyk] Upgrade reactflow from 11.10.4 to 11.11.1
2024-05-06 15:16:47 +01:00
Simon Larsen
22014c81c4 Merge pull request #1366 from OneUptime/snyk-upgrade-ec50f233945652edca96198c892ea002
[Snyk] Upgrade @opentelemetry/context-zone from 1.22.0 to 1.23.0
2024-05-06 15:16:40 +01:00
Simon Larsen
bff63cdf58 Merge pull request #1367 from OneUptime/snyk-upgrade-9bdef45fe03889844c5f043354578d14
[Snyk] Upgrade @opentelemetry/instrumentation-fetch from 0.49.1 to 0.50.0
2024-05-06 15:16:32 +01:00
Simon Larsen
5f9d57a099 Merge pull request #1369 from OneUptime/snyk-upgrade-1b2e0c757b53983d536db6e5be033842
[Snyk] Upgrade @opentelemetry/resources from 1.22.0 to 1.23.0
2024-05-06 15:16:22 +01:00
Simon Larsen
ca100f9de9 refactor: Update REDIS_PORT value in _helpers.tpl to use dynamic value from values.yaml 2024-05-06 13:53:37 +01:00
Simon Larsen
68dbb010aa refactor: Disable redis persistence and add common configuration in values.yaml 2024-05-06 13:49:08 +01:00
Simon Larsen
5cd213a750 refactor: Update SSO.ts and StatusPageSSO.ts to include issuer URL in SAML request 2024-05-06 13:20:34 +01:00
Simon Larsen
26683914bc refactor: Update SSO.ts and StatusPageSSO.ts to include issuer URL in SAML request 2024-05-06 13:13:52 +01:00
Simon Larsen
8a5adfd589 refactor: Remove unnecessary code in UserAuthorization.ts 2024-05-06 12:39:11 +01:00
Simon Larsen
d2eefeabba refactor: Include issuer URL in SAML request for SSO 2024-05-06 12:26:18 +01:00
Simon Larsen
1148b59416 refactor: Include issuer URL in SAML request for SSO 2024-05-06 12:02:55 +01:00
Simon Larsen
848c0c8100 refactor: Update SSO.ts and StatusPageSSO.ts to include issuer URL in SAML request 2024-05-06 12:00:23 +01:00
Simon Larsen
21f40961cf refactor: Update isGlobalLogin flag in SSO.ts and UserAuthorization.ts 2024-05-06 11:53:45 +01:00
Simon Larsen
2f74fbe0a8 refactor: Simplify SSO.ts code for SAMLRequest encoding and redirect 2024-05-06 10:17:49 +01:00
Simon Larsen
571784a523 refactor: Simplify SSO.ts code for SAMLRequest encoding and redirect 2024-05-06 09:52:14 +01:00
snyk-bot
7c60fe8009 fix: upgrade @opentelemetry/resources from 1.22.0 to 1.23.0
Snyk has created this PR to upgrade @opentelemetry/resources from 1.22.0 to 1.23.0.

See this package in npm:
https://www.npmjs.com/package/@opentelemetry/resources

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/47001ef1-7b3a-49c2-88cd-8025c56346d0?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-05 22:21:24 +00:00
snyk-bot
1a2fe1d16d fix: upgrade @opentelemetry/instrumentation-fetch from 0.49.1 to 0.50.0
Snyk has created this PR to upgrade @opentelemetry/instrumentation-fetch from 0.49.1 to 0.50.0.

See this package in npm:
https://www.npmjs.com/package/@opentelemetry/instrumentation-fetch

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/47001ef1-7b3a-49c2-88cd-8025c56346d0?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-05 22:21:17 +00:00
snyk-bot
a28a041c8d fix: upgrade @opentelemetry/context-zone from 1.22.0 to 1.23.0
Snyk has created this PR to upgrade @opentelemetry/context-zone from 1.22.0 to 1.23.0.

See this package in npm:
https://www.npmjs.com/package/@opentelemetry/context-zone

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/47001ef1-7b3a-49c2-88cd-8025c56346d0?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-05 22:21:13 +00:00
snyk-bot
cfcce00060 fix: upgrade reactflow from 11.10.4 to 11.11.1
Snyk has created this PR to upgrade reactflow from 11.10.4 to 11.11.1.

See this package in npm:
https://www.npmjs.com/package/reactflow

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/47001ef1-7b3a-49c2-88cd-8025c56346d0?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-05 22:21:08 +00:00
Simon Larsen
da0fa045e6 refactor: Simplify SSO.ts code for SAMLRequest encoding and redirect 2024-05-05 21:37:05 +01:00
Simon Larsen
859f62bf93 refactor: Simplify base64 encoding in SSO.ts 2024-05-04 23:26:13 +01:00
Simon Larsen
6a1cc1bd8b refactor: Update SSO.ts to use zlib.deflateRawSync for SAML message encoding 2024-05-04 22:55:29 +01:00
Simon Larsen
5b9abe8aea refactor: Add SAMLRequest encoding and redirect to Identity Provider 2024-05-04 22:54:06 +01:00
Simon Larsen
d0ec483a0f refactor: Update Register.tsx and UserAuthorization.test.ts for isGlobalLogin flag 2024-05-04 22:00:33 +01:00
Simon Larsen
c03a3d6f56 refactor: Remove debugger statement in SSO.ts 2024-05-04 21:53:00 +01:00
Simon Larsen
8761dbd75b refactor: Update login page SSO message for clarity and consistency 2024-05-04 21:51:09 +01:00
Simon Larsen
5541922b25 refactor: Update login page SSO message for clarity and consistency 2024-05-04 21:50:35 +01:00
Simon Larsen
8693ab065e refactor: Add isGlobalLogin flag to user object in ProjectAuthorization middleware 2024-05-04 21:29:53 +01:00
Simon Larsen
fe4daa7937 refactor: Update login page SSO message for clarity and consistency 2024-05-04 21:27:21 +01:00
Simon Larsen
20d7f2f8b4 refactor: Update JSONWebToken.signUserLoginToken() method to include isGlobalLogin flag 2024-05-04 21:13:34 +01:00
Simon Larsen
7f94f3d4d4 refactor: Update column_1 and column_2 definitions in StatementGenerator.test.ts 2024-05-04 20:54:19 +01:00
Simon Larsen
bc46370f7c refactor: Update JSONWebToken.sign() method to accept an object with data and expiresInSeconds properties 2024-05-04 20:37:58 +01:00
Simon Larsen
b3346a9702 refactor: Update login page SSO message for clarity and consistency 2024-05-04 20:11:02 +01:00
Simon Larsen
f793f7dd16 refactor: Update email regex to improve validation accuracy 2024-05-04 20:02:17 +01:00
Simon Larsen
766f1f6178 update email regex 2024-05-04 19:33:59 +01:00
Simon Larsen
9f76748037 update deps 2024-05-04 19:33:45 +01:00
Simon Larsen
469e06280a refactor: Improve code readability and maintainability in AnalyticsDatabaseService.ts 2024-05-03 19:05:56 +01:00
Simon Larsen
aede9af03d chore: Remove unnecessary line in BaseModel.ts 2024-05-03 14:48:07 +01:00
Nawaz Dhandala
0bc7cf345f chore: Add missing newline character in BaseModel.ts 2024-05-03 14:45:26 +01:00
Nawaz Dhandala
95c7b10ce0 Refactor AnalyticsDatabaseService.ts for improved code readability and maintainability 2024-05-03 14:04:24 +01:00
Simon Larsen
daceab164c Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2024-05-03 13:38:00 +01:00
Simon Larsen
2d28fbaf85 refactor: Add required flag and default value for attributes column in Log model 2024-05-03 13:37:57 +01:00
Simon Larsen
8d2cbe49ad Merge pull request #1364 from OneUptime/snyk-upgrade-8c197b0383ff1f076301a2077e18b9b2
[Snyk] Upgrade reactflow from 11.11.0 to 11.11.1
2024-05-03 13:10:26 +01:00
Simon Larsen
86f262583c feat: Generate new certificates for status page domains
This commit adds a new data migration script, GenerateNewCertsForStatusPage.ts, which generates new certificates for status page domains. It retrieves all domains in the greenlock certs, orders them, and updates the certificates accordingly. This will ensure that the status page domains have up-to-date and valid certificates.

Refactor and delete unused Metric services and models
Refactor Queue class to use explicit type annotation for queue variable
Add Queue caching to improve performance and reduce redundant connections
Refactor test-e2e job in release.yml workflow
Fix error handling in StatusPageDomainService.ts
Update select field in MoveGreenlockCertsToAcmeCerts.ts to use _id instead of id
Update ingress configuration for OneUptime Helm chart
Refactor StatusPageAPI to improve code readability and maintainability
Refactor StatusPageAPI and UpdateByID to improve code readability and maintainability
Refactor Let's Encrypt configuration and update GreenlockUtil class
2024-05-03 13:07:45 +01:00
Simon Larsen
4e748d1626 Refactor and delete unused Metric services and models 2024-05-03 13:03:01 +01:00
Simon Larsen
1dbea8e636 Refactor Queue class to use explicit type annotation for queue variable 2024-05-03 10:30:41 +01:00
Simon Larsen
3991acf5fc Add Queue caching to improve performance and reduce redundant connections 2024-05-03 10:04:19 +01:00
snyk-bot
1285830a9b fix: upgrade reactflow from 11.11.0 to 11.11.1
Snyk has created this PR to upgrade reactflow from 11.11.0 to 11.11.1.

See this package in npm:
https://www.npmjs.com/package/reactflow

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/8ca4ee75-8bc5-43a1-a3bc-244ceebf1437?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-02 17:57:26 +00:00
Simon Larsen
96d3243205 Refactor test-e2e job in release.yml workflow 2024-05-01 17:22:12 +01:00
Simon Larsen
08cbdb15d0 Fix error handling in StatusPageDomainService.ts 2024-05-01 15:16:59 +01:00
Simon Larsen
f59284e4d6 Update select field in MoveGreenlockCertsToAcmeCerts.ts to use _id instead of id 2024-05-01 13:57:46 +01:00
Simon Larsen
0c5d0e81a5 Update ingress configuration for OneUptime Helm chart 2024-05-01 13:41:35 +01:00
Simon Larsen
82829a5b97 Refactor StatusPageAPI to improve code readability and maintainability 2024-05-01 13:12:16 +01:00
Simon Larsen
d3c188a804 Refactor StatusPageAPI and UpdateByID to improve code readability and maintainability 2024-05-01 13:08:15 +01:00
Simon Larsen
85ffc36fad Refactor Let's Encrypt configuration and update GreenlockUtil class 2024-05-01 12:44:49 +01:00
Simon Larsen
8d34e9ccbe Refactor LetsEncrypt configuration and update GreenlockUtil class 2024-05-01 12:42:32 +01:00
Simon Larsen
c6643d7f7c Update GreenlockUtil class to handle lets encrypt account key properly 2024-05-01 12:37:41 +01:00
Simon Larsen
1433823efe Update LetsEncrypt configuration for OneUptime deployment 2024-05-01 12:13:01 +01:00
Simon Larsen
bd5902ed6d Update ingress configuration for OneUptime Helm chart 2024-05-01 11:44:30 +01:00
Simon Larsen
b64666f0ff Update oneuptime logo URL in README.md 2024-05-01 11:32:30 +01:00
Simon Larsen
9706cced86 Update OneUptime Helm chart with production readiness checklist 2024-05-01 11:29:14 +01:00
Simon Larsen
49cb3024ef Update nginx.yaml for OneUptime Helm chart 2024-05-01 11:09:30 +01:00
Simon Larsen
2fec007c5f Update ingressClassName in values.yaml for OneUptime Helm chart 2024-05-01 10:57:26 +01:00
Simon Larsen
0f40f5f730 Update HelmChart templates and values for OneUptime deployment 2024-05-01 10:30:21 +01:00
Simon Larsen
120ca78760 Update HelmChart/Public/oneuptime/templates/extra-templates.yaml and HelmChart/Public/oneuptime/values.yaml 2024-05-01 10:19:30 +01:00
Simon Larsen
d50fdf3464 Update nginx.yaml and values.yaml for OneUptime Helm chart 2024-05-01 10:14:02 +01:00
Simon Larsen
34ddb8efdb Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2024-05-01 09:49:38 +01:00
Simon Larsen
3724cce4f0 Add cron job to verify CNAMEs in StatusPageCerts.ts 2024-05-01 09:49:11 +01:00
Simon Larsen
d864992bdb Add MoveGreenlockCertsToAcmeCerts migration and update DataMigrations in Index.ts 2024-05-01 09:31:45 +01:00
Simon Larsen
3515752a95 Add MoveGreenlockCertsToAcmeCerts migration 2024-05-01 09:29:29 +01:00
Simon Larsen
53d66db8cb Merge pull request #1359 from OneUptime/snyk-fix-6d9e266d473ce0fed1ec1645f0bf6548
[Snyk] Security upgrade ejs from 3.1.9 to 3.1.10
2024-04-30 22:13:44 +01:00
Simon Larsen
548250f837 Merge pull request #1360 from OneUptime/snyk-fix-4fe336667711cbbe5c8ccb29e8c049e7
[Snyk] Security upgrade ejs from 3.1.9 to 3.1.10
2024-04-30 22:13:36 +01:00
Simon Larsen
b3b343d4aa Merge pull request #1361 from OneUptime/snyk-fix-eff57b0386677ae55037cf20bb1b38e3
[Snyk] Security upgrade ejs from 3.1.9 to 3.1.10
2024-04-30 22:13:31 +01:00
Simon Larsen
69a8f438f7 Merge pull request #1362 from OneUptime/snyk-fix-b0f6bc6acf45920714d4b0cacfb5c537
[Snyk] Security upgrade ejs from 3.1.9 to 3.1.10
2024-04-30 22:13:25 +01:00
Simon Larsen
7b571f15a9 Merge pull request #1363 from OneUptime/snyk-fix-2342a863e5405ba1a2449f7819d9a66b
[Snyk] Security upgrade ejs from 3.1.9 to 3.1.10
2024-04-30 22:13:18 +01:00
Simon Larsen
d783eaea9d Refactor table name in PostgresDatabase.ts, remove unused volume in app.yaml, fix typos in Domains.tsx and Index.ts, update nodemon.json files, remove greenlock dependency, and add validation for cname in StatusPageDomainService.ts 2024-04-30 21:24:04 +01:00
Simon Larsen
2e5aafe151 Update table name in PostgresDatabase.ts to use "domain" instead of "key" 2024-04-30 21:16:31 +01:00
Simon Larsen
dea938bcbb Add FetchCertificateJobs.init() to Nginx/Index.ts 2024-04-30 20:18:36 +01:00
Simon Larsen
7c16b14ab4 Update table name in PostgresDatabase.ts and remove unused volume in app.yaml 2024-04-30 19:45:43 +01:00
Simon Larsen
e22ff26dc0 Refactor error handling in StatusAPI.ts 2024-04-30 19:30:35 +01:00
Simon Larsen
75bd6ca6ed Fix DatabaseNotConnectedException code in ExceptionCode.ts and DatabaseNotConnectedException.test.ts 2024-04-30 19:24:54 +01:00
Simon Larsen
f0bdb76999 Fix typos in Domains.tsx and Index.ts 2024-04-30 19:12:03 +01:00
Simon Larsen
a9e9d14f69 Update StatusPageAPI and StatusPageDomainAPI to use AcmeChallenge model and remove Greenlock dependencies 2024-04-30 18:43:15 +01:00
Simon Larsen
21daac0400 Update nodemon.json files for Accounts, AdminDashboard, and Dashboard 2024-04-30 17:34:15 +01:00
Simon Larsen
3cdf71906b Remove greenlock dependency from package.json 2024-04-30 15:10:54 +01:00
Simon Larsen
0714161460 Add validation for cname in StatusPageDomainService.ts 2024-04-30 15:02:54 +01:00
Simon Larsen
78b7252743 Refactor GreenlockUtil challengeCreateFn and challengeRemoveFn to improve readability 2024-04-30 14:16:35 +01:00
Simon Larsen
2ba406f802 Add AcmeChallenge model, update StatusPageDomain model, and import AcmeCertificate in Model and Service files 2024-04-30 14:15:18 +01:00
Simon Larsen
48ebfc17dd Add AcmeChallenge model and update StatusPageDomain model 2024-04-30 14:08:05 +01:00
Simon Larsen
9d180a2dcb Remove unused Greenlock configuration files and import AcmeCertificate in Model and Service files 2024-04-30 13:22:06 +01:00
Simon Larsen
0f2a970ede Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2024-04-30 11:37:28 +01:00
Simon Larsen
4ab3c99fe4 Update dependencies in package.json 2024-04-30 11:37:24 +01:00
Simon Larsen
ae2e452d27 Merge pull request #1356 from sainak/sainak/fix/sert-scripts
Update nginx service name and host variable in cert helper scripts
2024-04-30 09:37:30 +01:00
snyk-bot
825d733f92 fix: Probe/package.json & Probe/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-EJS-6689533
2024-04-29 21:39:29 +00:00
snyk-bot
27039bc646 fix: Scripts/package.json & Scripts/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-EJS-6689533
2024-04-29 21:09:12 +00:00
snyk-bot
1bb1d80b09 fix: TestServer/package.json & TestServer/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-EJS-6689533
2024-04-29 20:56:52 +00:00
snyk-bot
983f587ce4 fix: Ingestor/package.json & Ingestor/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-EJS-6689533
2024-04-29 20:32:33 +00:00
Simon Larsen
e1f9325512 Fix renewOffset value in GreenlockUtil 2024-04-29 20:34:06 +01:00
snyk-bot
96c27c86ca fix: CommonServer/package.json & CommonServer/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-EJS-6689533
2024-04-29 17:18:14 +00:00
Simon Larsen
a172596e46 Update GreenlockUtil to include renewOffset and renewStagger options 2024-04-29 16:59:28 +01:00
Simon Larsen
3d30e9356a Fix column handling in ModelPermission.ts 2024-04-29 16:34:00 +01:00
Simon Larsen
7c04ecec35 Fix column handling in ModelPermission.ts and update GreenlockCertificateService in Store.ts 2024-04-29 15:59:37 +01:00
Simon Larsen
80511a5f8d Fix expiration date calculation in Greenlock Manager.ts 2024-04-29 14:19:50 +01:00
Simon Larsen
6692fb00b8 Refactor BaseModel and ModelPermission classes to improve column handling 2024-04-29 14:15:11 +01:00
Simon Larsen
83e207a7de Refactor OneUptimeDate class to improve convertMinutesToDaysHoursAndMinutes method in Date.ts 2024-04-29 11:35:25 +01:00
Simon Larsen
d62aa7f5a6 Refactor Index.tsx to import PromiseVoidFunction from 'Common/Types/FunctionTypes' 2024-04-29 11:06:08 +01:00
Simon Larsen
8a130f96e2 Refactor InfoCard component to add className prop and improve readability 2024-04-29 11:05:57 +01:00
Simon Larsen
66a470ebe1 Refactor GreenlockChallenge and GreenlockCertificate models to add deletedByUser and deletedByUserId properties 2024-04-29 10:26:10 +01:00
Simon Larsen
30a59aa21e Refactor InfoCard component to improve readability and fix indentation issue 2024-04-29 10:17:10 +01:00
Simon Larsen
7a6a5c141b Refactor IncidentView component to add incidentStates state and update getTimeToAcknowledge and getTimeToResolve functions 2024-04-29 10:15:17 +01:00
Simon Larsen
c4bf458074 Refactor OneUptimeDate class to add convertMinutesToHoursAndMinutes method in Date.ts 2024-04-29 09:44:33 +01:00
Simon Larsen
ae873e296c Improve CNAME verification process and add DNS propagation reminder 2024-04-29 09:23:12 +01:00
Simon Larsen
4a973d7107 Fix indentation issue and handle CNAME verification failure in StatusPageDomainAPI.ts 2024-04-29 08:24:33 +01:00
Simon Larsen
9bbf460b97 Fix indentation issue in EnvironmentConfig.ts 2024-04-28 21:31:29 +01:00
Simon Larsen
c348a8bd05 Refactor StatusPageDomainAPI to enable custom domains and improve CNAME verification process 2024-04-28 21:20:45 +01:00
Simon Larsen
76d8f5d5ef Remove @socket.io/redis-adapter dependency 2024-04-28 21:03:29 +01:00
Simon Larsen
72ac305aad Fix multiple slashes in route and handle CNAME verification failure in StatusPageDomainAPI.ts 2024-04-28 20:55:35 +01:00
Simon Larsen
1ac05450e7 Refactor StatusPageDomainAPI to set isRoot property in StatusPageDomainAPI.ts 2024-04-28 20:46:33 +01:00
Simon Larsen
67165375ac Add isSslOrdered property to StatusPageDomain model and update orderCert method in StatusPageDomainService 2024-04-28 20:44:54 +01:00
Simon Larsen
6325e36bd0 Refactor BaseAPI/Index.ts to remove unused imports and clean up code
Refactor StatusPageDomainAPI and related components to improve CNAME verification process
Refactor StatusPageDomainService and Domains.tsx to improve CNAME verification process
Refactor API endpoints to use sendEmptySuccessResponse instead of sendEmptyResponse
Refactor SocketIO.ts to remove Redis adapter initialization
Refactor StatusPageDelete component: Add SSL auto-renewal feature and improve CNAME verification process
Refactor GreenlockUtil and StatusPageDomainService to improve CNAME validation and configuration paths
Refactor StatusPageDomainService: Add onBeforeUpdate hook to validate CNAME and update GreenlockUtil configuration paths
Refactor StatusPageDomainService: Add onBeforeUpdate hook to validate CNAME before updating
Refactor StatusPageCerts:RemoveCerts method to use statusPageDomain.fullDomain instead of domain.fullDomain
2024-04-28 20:13:32 +01:00
Simon Larsen
e6375087cd Refactor BaseAPI/Index.ts to remove unused imports and clean up code 2024-04-28 20:06:34 +01:00
Simon Larsen
737e234ec0 Refactor StatusPageDomainAPI and related components to improve CNAME verification process 2024-04-28 20:04:38 +01:00
Simon Larsen
1427d52c9f Refactor StatusPageDomainService and Domains.tsx to improve CNAME verification process 2024-04-28 20:03:00 +01:00
Simon Larsen
8377285a27 Refactor API endpoints to use sendEmptySuccessResponse instead of sendEmptyResponse 2024-04-28 19:52:57 +01:00
Simon Larsen
7414c6563d Refactor SocketIO.ts to remove Redis adapter initialization 2024-04-28 14:45:51 +01:00
Simon Larsen
0669e96a5d Refactor StatusPageDelete component: Add SSL auto-renewal feature and improve CNAME verification process 2024-04-28 14:37:52 +01:00
Simon Larsen
54a6101315 Refactor GreenlockUtil and StatusPageDomainService to improve CNAME validation and configuration paths 2024-04-28 14:26:12 +01:00
Simon Larsen
eaaef7280f Refactor StatusPageDomainService: Add onBeforeUpdate hook to validate CNAME and update GreenlockUtil configuration paths 2024-04-28 14:24:24 +01:00
Simon Larsen
b368633ff9 Refactor StatusPageDomainService: Add onBeforeUpdate hook to validate CNAME before updating 2024-04-28 14:22:41 +01:00
Simon Larsen
37b25c3a8f Refactor StatusPageCerts:RemoveCerts method to use statusPageDomain.fullDomain instead of domain.fullDomain 2024-04-28 13:48:42 +01:00
Simon Larsen
191d0154b4 Update LETSENCRYPT_NOTIFICATION_EMAIL configuration option 2024-04-28 13:18:09 +01:00
Simon Larsen
1f786699d2 Update GreenlockUtil configuration paths 2024-04-28 13:16:53 +01:00
Simon Larsen
00b5453b3f Add LETSENCRYPT_NOTIFICATION_EMAIL configuration option 2024-04-28 13:09:23 +01:00
Simon Larsen
077ca1037b Add IS_BILLING_ENABLED flag to E2E tests 2024-04-27 17:16:23 +01:00
Simon Larsen
1be827741e Update E2E Config.ts to use E2E_TEST_STATUS_PAGE_URL instead of E2E_TEST_REGISTERED_USER_PASSWORD 2024-04-27 16:55:22 +01:00
Simon Larsen
d51e818d10 Update E2E Config.ts to use E2E_TEST_STATUS_PAGE_URL instead of E2E_TEST_REGISTERED_USER_PASSWORD 2024-04-27 09:42:41 +01:00
Simon Larsen
d312c2b0cf Update E2E workflow to include failed webhook URL 2024-04-26 22:50:20 +01:00
Simon Larsen
96a09353fb Update password field in UserProfile/Password.tsx 2024-04-26 22:15:30 +01:00
Simon Larsen
fe40c5bb8d Add test results artifact upload to E2E workflow 2024-04-26 22:04:12 +01:00
Simon Larsen
7cbbce4067 Add IS_BILLING_ENABLED flag to account registration form in E2E tests 2024-04-26 21:15:01 +01:00
Simon Larsen
2870d28354 Fix restart value in e2e service of docker-compose.base.yml 2024-04-26 19:47:24 +01:00
Simon Larsen
899bc1d205 Update E2E cron job environment variables to use consistent naming convention 2024-04-26 19:19:38 +01:00
Simon Larsen
308dba7f24 Add CORS configuration to OTelCollector HTTP receiver 2024-04-26 19:12:38 +01:00
Simon Larsen
a0e565485b Update environment variables and configurations in E2E and HelmChart templates 2024-04-26 19:07:20 +01:00
Simon Larsen
35c21d7611 Update E2E workflow to include failed webhook URL 2024-04-26 18:54:23 +01:00
Simon Larsen
a0ef85b8cd Refactor test files to use consistent formatting 2024-04-26 18:46:22 +01:00
Simon Larsen
818f8cc421 Update E2E workflow to use environment variables from config.env in test.e2e.yaml 2024-04-26 18:46:10 +01:00
Simon Larsen
eaeb40ec49 Update Kubernetes metadata and environment variables in HelmChart templates 2024-04-26 18:10:26 +01:00
Simon Larsen
74273e6e9b Refactor E2E workflow to use environment variables from config.env in test.e2e.yaml 2024-04-26 18:01:15 +01:00
Aakash Singh
c092adfa3d Update nginx service name and host variable in cert helper scripts 2024-04-26 16:58:09 +00:00
Simon Larsen
9eec9042bd Refactor E2E workflow to use npm run prerun command before running docker-compose in test.e2e.yaml 2024-04-26 16:40:02 +01:00
Simon Larsen
496c2ca32c Update Kubernetes metadata in HelmChart templates 2024-04-26 15:05:30 +01:00
Simon Larsen
499ab07cd3 Update E2E cron job environment variables to use quoted values 2024-04-26 15:01:17 +01:00
Simon Larsen
c634526215 Update OpenTelemetry exporter configuration in HelmChart 2024-04-26 14:56:33 +01:00
Simon Larsen
cda9921f5d Update OpenTelemetry exporter configuration in HelmChart 2024-04-26 14:54:10 +01:00
Simon Larsen
d3ca5a97ea Update OpenTelemetry exporter configuration in HelmChart 2024-04-26 14:53:25 +01:00
Simon Larsen
f22ae5844d Refactor E2E workflow to use status-check.sh script for server availability check 2024-04-26 14:45:01 +01:00
Simon Larsen
a71190493a Refactor code to remove unused imports and routes 2024-04-26 14:30:17 +01:00
Simon Larsen
caf3377cee Refactor E2E workflow to use parentheses for command grouping in docker-compose run step 2024-04-26 13:36:52 +01:00
Simon Larsen
f0e4d6bf71 Update E2E workflow to use --abort-on-container-exit flag in docker-compose command 2024-04-26 13:36:40 +01:00
Simon Larsen
f34e4a23d6 Update E2E workflow to use docker-compose for running E2E tests 2024-04-26 13:35:25 +01:00
Simon Larsen
e03ecb0e0d Update E2E package.json scripts order 2024-04-26 12:59:05 +01:00
Simon Larsen
0a8574678c Update E2E workflow to wait for 60 minutes for server to be up 2024-04-26 10:54:55 +01:00
Simon Larsen
0fdf63ce0c Add npm install command for Common directory in E2E workflow 2024-04-25 22:56:26 +01:00
Simon Larsen
d1586f75da Add data test IDs to BasicForm fields 2024-04-25 22:05:10 +01:00
Simon Larsen
f47b53c5b9 Fix formatting issues in code blocks 2024-04-25 22:03:12 +01:00
Simon Larsen
7523c3ad4c Merge pull request #1354 from UnkAtreus/fix/table-header-button-state-not-re-render
fix: setting header buttons in BaseModelTable
2024-04-25 21:54:54 +01:00
Simon Larsen
11d580a373 Add E2E test environment variables and update E2E tests 2024-04-25 21:53:51 +01:00
Simon Larsen
9a78c4c24d Refactor Docker Compose file to use common variables 2024-04-25 19:22:02 +01:00
Simon Larsen
ec936b43e2 Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2024-04-25 19:15:16 +01:00
Simon Larsen
3bc2e639d9 Add Jest and Babel ESLint parser to devDependencies 2024-04-25 19:15:14 +01:00
Simon Larsen
fa672be557 Merge pull request #1355 from OneUptime/snyk-upgrade-50073c5b5ba8c99a5ba48e8562e31e3c
[Snyk] Upgrade reactflow from 11.10.4 to 11.11.0
2024-04-25 18:55:05 +01:00
Simon Larsen
695f16ca1e Add _id field to Acknowledge Incident and Resolve Incident elements 2024-04-25 18:50:13 +01:00
snyk-bot
003b638170 fix: upgrade reactflow from 11.10.4 to 11.11.0
Snyk has created this PR to upgrade reactflow from 11.10.4 to 11.11.0.

See this package in npm:
https://www.npmjs.com/package/reactflow

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/8ca4ee75-8bc5-43a1-a3bc-244ceebf1437?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-25 17:35:16 +00:00
Simon Larsen
1cf208387f Add server readiness check before running E2E tests 2024-04-25 18:34:13 +01:00
UnkAtreus
625cb49939 fix: setting header buttons in BaseModelTable 2024-04-25 23:41:57 +07:00
Simon Larsen
7d44eb1a33 Update preinstall script in E2E package.json to include install-deps command 2024-04-25 16:20:30 +01:00
Simon Larsen
ab42ca1ef9 Add test-e2e job to GitHub Actions workflow 2024-04-25 15:30:08 +01:00
Simon Larsen
910aa113b5 Add npm install step for Common directory 2024-04-25 15:22:52 +01:00
Simon Larsen
1516f81180 Refactor E2E configuration and Docker Compose file 2024-04-25 15:07:33 +01:00
Simon Larsen
b9f86d029a Update Dockerfile.tpl to use non-Alpine Node.js image for Playwright compatibility 2024-04-25 15:04:05 +01:00
Simon Larsen
ecfc47ee59 Update dependencies and test directory in E2E package files 2024-04-25 13:50:33 +01:00
Simon Larsen
5d9a11fb48 Refactor E2E tests and Docker configuration 2024-04-25 12:58:39 +01:00
Simon Larsen
894d15918b Fix BASE_URL construction in Config.ts and SignUp.test.ts 2024-04-25 12:45:08 +01:00
Simon Larsen
73e6a20503 Refactor e2e-cron.yml to include common environment variables and change restart policy 2024-04-25 12:33:58 +01:00
Simon Larsen
ef0dc5305a Update test.e2e.yaml to free up disk space in GitHub Actions runner 2024-04-25 12:32:22 +01:00
Simon Larsen
73dc057c99 Update test.e2e.yaml and Config.ts files 2024-04-25 12:31:38 +01:00
Simon Larsen
092564434f Refactor Dockerfile.tpl to remove duplicate CMD instruction 2024-04-25 12:27:36 +01:00
Simon Larsen
7623fe7df4 Add Docker build step for E2E tests 2024-04-25 12:08:58 +01:00
Simon Larsen
da03636bd0 Add Docker build step for E2E tests 2024-04-25 12:01:49 +01:00
Simon Larsen
21b119d8a0 Refactor import statements in E2E test files to use Config instead of Utils/BaseURL 2024-04-25 12:01:13 +01:00
Simon Larsen
4ee62fcca4 Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2024-04-25 11:53:43 +01:00
Simon Larsen
8acfb0f798 add e2e tests 2024-04-25 11:53:11 +01:00
Simon Larsen
6e2d343264 Update package-lock.json and package.json with latest dependencies 2024-04-25 11:51:26 +01:00
Simon Larsen
3dd03cda46 Refactor and remove unused test files in E2E/Tests/Home directory 2024-04-25 11:46:22 +01:00
Simon Larsen
87c8993141 Merge pull request #1350 from OneUptime/snyk-upgrade-83479794e36d11372eff5b3bc5f5d6ec
[Snyk] Upgrade css-loader from 6.10.0 to 6.11.0
2024-04-25 09:01:07 +01:00
Simon Larsen
323601db3a Merge pull request #1352 from OneUptime/snyk-fix-eecb134aa680c310c30f2dd5bd870f70
[Snyk] Security upgrade nginx from 1.25.4-alpine to 1.25.5-alpine
2024-04-25 09:00:57 +01:00
snyk-bot
1ac7d4920f fix: Nginx/Dockerfile.tpl to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE318-OPENSSL-6593964
- https://snyk.io/vuln/SNYK-ALPINE318-OPENSSL-6593964
2024-04-25 03:39:23 +00:00
snyk-bot
617a436eb5 fix: upgrade css-loader from 6.10.0 to 6.11.0
Snyk has created this PR to upgrade css-loader from 6.10.0 to 6.11.0.

See this package in npm:
https://www.npmjs.com/package/css-loader

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/40b17bc5-1bd4-48b1-88f1-5b4dc1400e80?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-24 20:10:37 +00:00
Simon Larsen
7e6af77d70 Refactor Schedule component to include run method in WebhookTrigger 2024-04-24 19:33:38 +01:00
Simon Larsen
59a6771e73 Refactor initialization code and add WorkflowFeatureSet to App/Index.ts 2024-04-24 19:17:38 +01:00
Simon Larsen
b88320bcbb Refactor cleanup cron jobs in HelmChart/Public/oneuptime/templates/cron-job.yaml and values.yaml 2024-04-24 18:50:13 +01:00
Simon Larsen
06bafdfce6 Refactor cleanup cron jobs in HelmChart/Public/oneuptime/templates/cron-job.yaml and HelmChart/Public/oneuptime/values.yaml 2024-04-24 18:44:00 +01:00
Simon Larsen
441aef4823 Refactor initialization code in Nginx/Index.ts 2024-04-24 18:31:16 +01:00
Simon Larsen
0f86812258 Add new configurations for 'ingestor' in HelmChart/Public/oneuptime/values.yaml and Nginx/Index.ts 2024-04-24 18:26:13 +01:00
Simon Larsen
f601e93101 Refactor status API to return JSON object with status 'ok' 2024-04-24 18:00:35 +01:00
Simon Larsen
bf8d4757b1 Refactor logging statements in StatementGenerator.test.ts 2024-04-24 17:54:54 +01:00
Simon Larsen
4b69717687 Refactor initialization code and add PromiseVoidFunction type to addDefaultRoutes in StartServer.ts 2024-04-24 17:46:02 +01:00
Simon Larsen
0647f7d22a Refactor initialization code and add default routes in multiple files 2024-04-24 17:44:29 +01:00
Simon Larsen
ae6852d5eb Refactor connection check methods for Redis, Postgres, and Clickhouse 2024-04-24 17:05:50 +01:00
Simon Larsen
d4902784c2 Refactor Redis, Postgres, and Clickhouse connection check methods 2024-04-24 14:55:48 +01:00
Simon Larsen
e2644586b6 Refactor connection check methods for Redis, Postgres, and Clickhouse 2024-04-24 14:30:29 +01:00
Simon Larsen
cc80f6fa54 Refactor initialization code in multiple files 2024-04-24 14:02:17 +01:00
Simon Larsen
32c78f24e9 Refactor Redis, Postgres, and Clickhouse connection check methods 2024-04-24 13:03:07 +01:00
Simon Larsen
8b0f23e18f Refactor Express routing in Notification and Identity FeatureSets 2024-04-24 12:20:04 +01:00
Simon Larsen
b76d16262c Update logging statements in StatementGenerator.ts 2024-04-24 12:04:03 +01:00
Simon Larsen
4c60f080f0 Add cron job to delete completed pods 2024-04-24 11:34:01 +01:00
Simon Larsen
5f40c393b1 Update cron job schedule to run every 5 minutes 2024-04-24 10:18:43 +01:00
Simon Larsen
7b22d293f2 add cron job to clean up crash loop backoff states 2024-04-24 10:18:17 +01:00
Simon Larsen
7f6c247652 Update dependencies versions in HelmChart 2024-04-24 10:02:29 +01:00
Simon Larsen
267950bb54 Refactor Tab component to include countBadge and tabType properties 2024-04-23 20:56:16 +01:00
Simon Larsen
d4b6bf6ca0 Merge pull request #1349 from OneUptime/nginx-crash-prevention
Refactor Tab component to include countBadge and tabType properties
2024-04-23 20:46:35 +01:00
Simon Larsen
9d0c110a74 Update dependencies versions in HelmChart 2024-04-23 20:38:08 +01:00
Simon Larsen
9d3c4df5e7 Update Greenlock packageRoot path in app.yaml 2024-04-23 16:41:36 +01:00
Simon Larsen
ca89818cc1 Update Greenlock packageRoot path in StatusPageCerts.ts and app.yaml 2024-04-23 16:39:43 +01:00
Simon Larsen
6b2caebd0d Refactor Tab component to include countBadge and tabType properties 2024-04-23 15:34:37 +01:00
Simon Larsen
f716ece35f Refactor Tab component to include countBadge and tabType properties 2024-04-23 14:03:37 +01:00
Simon Larsen
6dcc72e708 Refactor getEvents function in SpanViewer.tsx to separate events and exceptions 2024-04-23 13:44:46 +01:00
Simon Larsen
47a718ad2a Refactor SpanViewer component to display exceptions and events separately 2024-04-23 13:39:37 +01:00
Simon Larsen
e4e3d5d362 Update component types and import statements in Accordion.tsx and SpanViewer.tsx 2024-04-23 13:34:24 +01:00
Simon Larsen
73b4987b32 Update dependencies versions in HelmChart 2024-04-23 12:02:42 +01:00
Simon Larsen
7574d3921a Refactor logging implementation and remove console log statements 2024-04-23 10:22:58 +01:00
Simon Larsen
c6b47c3a1b Refactor import statements and update component types in Accordion.tsx and SpanViewer.tsx 2024-04-22 22:05:35 +01:00
Simon Larsen
a5fec2560a Refactor import statements and update component types in multiple files 2024-04-22 20:41:36 +01:00
Simon Larsen
b139d667d2 Refactor SpanUtil class and JSONFunctions class 2024-04-22 20:19:35 +01:00
Simon Larsen
906a42c218 Update TableColumnType enum to include JSONArray type 2024-04-22 19:02:03 +01:00
Simon Larsen
529b22c97c Refactor import statements and update component types in JSONFunctions.ts 2024-04-22 13:39:46 +01:00
Simon Larsen
89f3508ce0 Refactor import statements and update component types in Detail.tsx, CodeEditor.tsx, and JSONFunctions.ts 2024-04-22 13:35:58 +01:00
Simon Larsen
a9cc7f41ba Refactor import statements and update component types in Tabs.tsx, SpanViewer.tsx, HiddenText.test.tsx, Tabs.test.tsx, and Detail.tsx 2024-04-22 12:09:21 +01:00
Simon Larsen
cc517d36dc Refactor import statements and update component types in SpanViewer.tsx 2024-04-22 11:07:23 +01:00
Simon Larsen
8363279050 Refactor import statements and update component types in SpanViewer.tsx and TeamView.tsx 2024-04-22 11:05:54 +01:00
Simon Larsen
7c0f9c307a Refactor import statements and update component types in TeamView.tsx 2024-04-22 11:00:15 +01:00
Simon Larsen
3a7c9f7fab Refactor import statements and update component types in multiple files 2024-04-22 10:47:40 +01:00
Simon Larsen
ba993ba09a Refactor import statements and update component types in multiple files 2024-04-22 10:14:53 +01:00
Simon Larsen
fca1f80243 Refactor import statements and update component types in multiple files 2024-04-22 10:04:28 +01:00
Simon Larsen
96926ffa63 Refactor import statements and update component types in multiple files 2024-04-22 09:24:58 +01:00
Simon Larsen
48b850d350 Refactor field types and import statements in multiple components 2024-04-21 21:14:22 +01:00
Simon Larsen
ab6a01e59b Refactor field types and import statements in multiple components 2024-04-21 18:50:34 +01:00
Simon Larsen
cd5eb62f61 Refactor field types and import statements in multiple components 2024-04-21 18:44:40 +01:00
Simon Larsen
d04aa43b6d Refactor component types and import statements in multiple files 2024-04-21 18:37:15 +01:00
Simon Larsen
308fe269fc Refactor component types and import statements in multiple files 2024-04-21 18:17:36 +01:00
Simon Larsen
0f92e37712 Refactor field types, import statements, and column types in multiple components 2024-04-21 18:08:31 +01:00
Simon Larsen
7f41cebdf7 Refactor field types in multiple components to use specific model types 2024-04-21 18:06:21 +01:00
Simon Larsen
351734d661 Refactor labels handling in multiple components 2024-04-21 17:59:35 +01:00
Simon Larsen
961de94f81 Refactor import statements in Email and Probes settings pages 2024-04-21 17:47:18 +01:00
Simon Larsen
2a89d57560 Refactor Filter interface to include Array<DropdownValue> in FilterData 2024-04-21 17:44:41 +01:00
Simon Larsen
039e0b17a4 Refactor Filter interface to use keyof T for key variable 2024-04-21 17:37:02 +01:00
Simon Larsen
77802eec58 Refactor key variable in Field interface to use keyof T | null 2024-04-21 17:16:00 +01:00
Simon Larsen
5db4cc8d21 Refactor column types and field types to use generic objects 2024-04-21 16:59:05 +01:00
Simon Larsen
f44260ee41 Refactor column types and field types to use generic objects 2024-04-21 09:42:41 +01:00
Simon Larsen
895bacfc11 Refactor ModelDetail component to use keyof TBaseModel for key variable 2024-04-20 20:44:28 +01:00
Simon Larsen
de6363574c Refactor model table column types and field types to use generic objects 2024-04-20 19:33:25 +01:00
Simon Larsen
2a07d62146 Refactor model table column types and field types to use generic objects 2024-04-20 19:16:03 +01:00
Simon Larsen
3138415bd0 Refactor SpanViewer component to import TelemetryServiceElement and use it for fieldType.Element 2024-04-20 18:01:57 +01:00
Simon Larsen
527ba63c94 Refactor Tabs component to improve UI and fix styling issues 2024-04-20 17:29:16 +01:00
Simon Larsen
6910648daf Update Nginx Helm chart to include a new volume for /var/run and /var/cache/nginx 2024-04-20 16:59:40 +01:00
Simon Larsen
9fb68afe79 Update Nginx Helm chart to include a new volume for /var/run 2024-04-20 16:54:50 +01:00
Simon Larsen
350f808b44 Update SideOver component to use "Close" instead of "Cancel" for the close button title 2024-04-19 18:16:55 +01:00
Simon Larsen
6438953c91 update values for nginx 2024-04-19 18:11:58 +01:00
Simon Larsen
7f98e469cc Update Nginx and Docker ports to use custom ports 7849 and 7850 2024-04-19 18:11:48 +01:00
Simon Larsen
d49ba093f0 tabs test update 2024-04-19 17:59:50 +01:00
Simon Larsen
1ebcbeeba2 Refactor Tabs component to remove unused code and improve readability 2024-04-19 17:51:25 +01:00
Simon Larsen
c4eb6cd44a Refactor SpanViewer component to simplify conditional rendering of span 2024-04-19 16:43:19 +01:00
Simon Larsen
48a58951e8 Update SideOver component to use "Close" instead of "Cancel" for the close button title 2024-04-19 16:40:13 +01:00
Simon Larsen
414f8c7208 Update SideOver component to use "Close" instead of "Cancel" for the close button title 2024-04-19 16:22:12 +01:00
Simon Larsen
cb1d640c56 Update import paths for DashboardLogsViewer in Index.tsx and View/Index.tsx 2024-04-19 15:45:54 +01:00
Simon Larsen
8a271608e8 Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2024-04-19 14:23:48 +01:00
Simon Larsen
dd3627dca8 Refactor GanttChart components to support multi-select feature 2024-04-19 14:23:45 +01:00
Simon Larsen
fe6c917862 Merge pull request #1347 from OneUptime/snyk-upgrade-f72cdda21b6b90f7a0a31bdba3eab5ed
[Snyk] Upgrade reflect-metadata from 0.2.1 to 0.2.2
2024-04-19 13:27:21 +01:00
Simon Larsen
0dfa3f912e Refactor TraceView component to improve readability and add missing font styles 2024-04-19 13:26:29 +01:00
Simon Larsen
d8bfab3ae0 Update BarLabel component to use label prop instead of title prop in Bar component 2024-04-19 12:32:59 +01:00
Simon Larsen
d7a9adf791 Update BrandColors imports to use Green instead of Green500 2024-04-19 11:56:24 +01:00
Simon Larsen
fb070b9448 Update BrandColors imports to use Green instead of Green500 2024-04-19 11:53:28 +01:00
Simon Larsen
add313980c Update SendMessageToChannel.ts to include msteams width in AdaptiveCard 2024-04-19 10:14:34 +01:00
snyk-bot
29a909d547 fix: upgrade reflect-metadata from 0.2.1 to 0.2.2
Snyk has created this PR to upgrade reflect-metadata from 0.2.1 to 0.2.2.

See this package in npm:
https://www.npmjs.com/package/reflect-metadata

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/f6446ec8-d441-487e-b58f-38373430e213?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-19 02:31:24 +00:00
Simon Larsen
13b10ca874 Merge pull request #1346 from OneUptime/helm-dep-update
Helm dep update
2024-04-18 23:01:42 +01:00
Simon Larsen
d0738ad64d Update Helm dependencies and versions 2024-04-18 23:00:56 +01:00
Simon Larsen
a21b9ca555 Update Helm dependencies and versions 2024-04-18 22:55:21 +01:00
Simon Larsen
f0e2910bd5 update helm dep 2024-04-18 22:06:09 +01:00
Simon Larsen
8f84027813 Refactor HelmChart templates to fix volume mount paths in haraka.yaml and nginx.yaml 2024-04-18 20:38:29 +01:00
Simon Larsen
970b6fbb92 Merge pull request #1345 from OneUptime/error-spans
Error spans
2024-04-18 13:34:41 +01:00
Simon Larsen
c8db6f237f Refactor GanttChartRow component to improve readability and add padding to child rows 2024-04-18 13:34:25 +01:00
Simon Larsen
01477fd102 Merge branch 'master' into error-spans 2024-04-18 12:58:21 +01:00
Simon Larsen
0bd173dd93 Add basic ops query to check size of tables in Clickhouse 2024-04-18 12:04:48 +01:00
Simon Larsen
87b7bbad16 Refactor HelmChart templates to use emptyDir volumes for haraka.yaml 2024-04-18 11:04:38 +01:00
Simon Larsen
2524684b1f Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2024-04-18 11:01:35 +01:00
Simon Larsen
6cc00a1e5b Refactor HelmChart templates to use emptyDir volumes instead of persistentVolumeClaim 2024-04-18 11:00:21 +01:00
Simon Larsen
c2285848cd add enable to haraka 2024-04-18 10:43:31 +01:00
Simon Larsen
506a89aeb3 Merge pull request #1344 from OneUptime/snyk-upgrade-6271cd99ae391fe1209a66241b751143
[Snyk] Upgrade @types/lodash from 4.14.202 to 4.17.0
2024-04-17 13:04:03 +01:00
snyk-bot
7c0221e6b8 fix: upgrade @types/lodash from 4.14.202 to 4.17.0
Snyk has created this PR to upgrade @types/lodash from 4.14.202 to 4.17.0.

See this package in npm:
https://www.npmjs.com/package/@types/lodash

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/c3622982-05c8-495c-809c-20f301c75f92?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-17 00:48:15 +00:00
Simon Larsen
e9d8c5b2bc Refactor GanttChart components and update import paths for Row and GanttChartRow 2024-04-16 22:14:55 +01:00
Simon Larsen
8e5fc32f4d Refactor GanttChart components and update row level in Row component 2024-04-16 20:54:56 +01:00
Simon Larsen
2eaeec326f Refactor GanttChart components and update title and description types to support React elements 2024-04-16 20:22:47 +01:00
Simon Larsen
c5823308af Refactor GanttChart components and fix line wrapping issue in TraceView component 2024-04-16 20:04:23 +01:00
Simon Larsen
f0e255eb24 Refactor GanttChart components and fix line wrapping issue in SendMessageToChannel.ts 2024-04-16 15:29:08 +01:00
Simon Larsen
bccd2fd5d2 Refactor GanttChart components and fix line wrapping issue in SendMessageToChannel.ts 2024-04-16 15:27:01 +01:00
Simon Larsen
534c798028 Merge branch 'master' into error-spans 2024-04-16 13:31:57 +01:00
Simon Larsen
2a48b585a4 Refactor color variables in BrandColors.test.ts to use updated hex codes 2024-04-16 13:29:41 +01:00
Simon Larsen
8de699cb94 Refactor unique span filtering logic in TraceView component 2024-04-16 13:27:26 +01:00
Simon Larsen
03ffeba683 Merge branch 'master' into error-spans 2024-04-16 13:14:29 +01:00
Simon Larsen
153d55b774 Refactor field types and filters in Telemetry/Services/View/Traces/View/Index.tsx component 2024-04-16 13:14:26 +01:00
Simon Larsen
1b95dfa5ed Fix line wrapping issue in SendMessageToChannel.ts 2024-04-16 13:12:05 +01:00
Simon Larsen
d9451af991 Fix line wrapping issue in SendMessageToChannel.ts 2024-04-16 13:08:26 +01:00
Simon Larsen
da375dce63 Refactor BaseModelTable and TelemetryServiceElement components 2024-04-16 13:04:48 +01:00
Simon Larsen
d12e2f7622 Refactor spanId variable name in TraceView component 2024-04-16 12:38:35 +01:00
Simon Larsen
d0e9120559 Refactor TracesList component in Index.tsx to remove unused imports and update span status colors 2024-04-16 12:19:02 +01:00
Simon Larsen
d35ae2f075 Refactor BrandColors.test.ts and ProjectService.ts to use the new color variables 2024-04-16 12:03:04 +01:00
Simon Larsen
92076dad8b Refactor class names and variables for Gray to Gray500 2024-04-16 11:56:51 +01:00
Simon Larsen
a1ded23b46 Refactor field types and filters in BrandColors.ts and LineChart.tsx components 2024-04-16 11:56:22 +01:00
Simon Larsen
4d618e6d93 Fix casing of "Gray" in class names and variables 2024-04-16 11:54:57 +01:00
Simon Larsen
d3e461e0bd Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2024-04-16 11:30:20 +01:00
Simon Larsen
7a94f4fcbc Add TelemetryServiceColor migration to DataMigrations 2024-04-16 11:30:08 +01:00
Simon Larsen
2758b5273b Add TelemetryServiceColor migration to DataMigrations 2024-04-16 11:28:18 +01:00
Simon Larsen
79d18ab920 Refactor field types and filters in BrandColors.ts and LineChart.tsx components 2024-04-16 11:27:09 +01:00
Simon Larsen
13dd7c2db5 Merge pull request #1342 from OneUptime/snyk-upgrade-52e97e539fcfb5c5cd429cca08838406
[Snyk] Upgrade posthog-js from 1.111.0 to 1.116.6
2024-04-15 19:58:27 +01:00
snyk-bot
2486cd93c0 fix: upgrade posthog-js from 1.111.0 to 1.116.6
Snyk has created this PR to upgrade posthog-js from 1.111.0 to 1.116.6.

See this package in npm:
https://www.npmjs.com/package/posthog-js

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/f6446ec8-d441-487e-b58f-38373430e213?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-15 18:53:20 +00:00
Simon Larsen
980c82ce31 Merge pull request #1339 from OneUptime/snyk-upgrade-e8ac578e50c1c3aa78c497fb48d0d3b1
[Snyk] Upgrade posthog-js from 1.111.0 to 1.116.5
2024-04-15 13:17:39 +01:00
Simon Larsen
1f6a4dbf40 Merge pull request #1340 from OneUptime/snyk-upgrade-3db1fd0a1346faee5053fb7c52f22d07
[Snyk] Upgrade @babel/runtime from 7.24.0 to 7.24.1
2024-04-15 12:40:07 +01:00
Simon Larsen
b14f518461 Merge pull request #1341 from OneUptime/snyk-fix-df93c133d994b2d52436acb86973db95
[Snyk] Security upgrade OpenTelemetry.Instrumentation.AspNetCore from 1.5.1-beta.1 to 1.8.1
2024-04-15 11:56:17 +01:00
snyk-bot
70d243aa7b fix: Examples/otel-dotnet/otel-dotnet.csproj to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-DOTNET-OPENTELEMETRYINSTRUMENTATIONASPNETCORE-6613064
2024-04-14 23:00:18 +00:00
Simon Larsen
3f3996b155 Refactor variable name in BrandColors.test.ts for consistency 2024-04-14 19:30:22 +01:00
snyk-bot
5b3915fc65 fix: upgrade @babel/runtime from 7.24.0 to 7.24.1
Snyk has created this PR to upgrade @babel/runtime from 7.24.0 to 7.24.1.

See this package in npm:
https://www.npmjs.com/package/@babel/runtime

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/47001ef1-7b3a-49c2-88cd-8025c56346d0?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-14 17:11:21 +00:00
snyk-bot
53d33eb0fb fix: upgrade posthog-js from 1.111.0 to 1.116.5
Snyk has created this PR to upgrade posthog-js from 1.111.0 to 1.116.5.

See this package in npm:
https://www.npmjs.com/package/posthog-js

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/f6446ec8-d441-487e-b58f-38373430e213?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-04-14 17:11:17 +00:00
Simon Larsen
c2722cb66a Refactor field types and filters in BrandColors.ts and Index.tsx components 2024-04-14 12:31:44 +01:00
Simon Larsen
2ccc7a8061 Refactor field types and filters in ExecutionLogsTable, ExecutionLogsTimelineTable, and Escalation components 2024-04-12 12:13:48 +01:00
Simon Larsen
97244b5c2c Refactor field types and filters in various components 2024-04-12 12:05:39 +01:00
Simon Larsen
90a0988e06 Refactor field types and filters in Invoices.tsx component 2024-04-12 11:59:41 +01:00
Simon Larsen
a3778b8ce6 Refactor field types and filters in various components 2024-04-12 11:56:37 +01:00
Simon Larsen
79bac7fd3f Refactor filter handling and permissions in List, ListBody, and BaseModelTable components 2024-04-12 11:29:28 +01:00
Simon Larsen
112863a52b Refactor field types and filters in various components 2024-04-12 09:48:26 +01:00
Simon Larsen
c1150c06e0 Refactor field types in WorkflowStatus, Variable, IncidentState, MonitorStatus, IncidentSeverity, and ScheduledMaintenanceState components 2024-04-11 21:58:45 +01:00
Simon Larsen
fcfb3f7e50 Refactor FilterComponent to handle additional field types in Filter.tsx 2024-04-11 21:41:55 +01:00
Simon Larsen
7c6c5ccac1 Refactor filter dropdown options in Domains.tsx and SMSSubscribers.tsx components 2024-04-11 21:39:06 +01:00
Simon Larsen
03ad9bf349 Refactor date fields to use FieldType.Date instead of FieldType.DateTime 2024-04-11 21:33:18 +01:00
Simon Larsen
f95ab83ae2 Refactor filter handling and permissions in List, ListBody, and BaseModelTable components 2024-04-11 21:05:49 +01:00
Simon Larsen
a25951f330 Refactor filter handling and permissions in Labels, Variable, Services, MonitorProbes, Users, MonitorTable, APIKeys, Domains, Variable, Call, Teams, SSO, Owners, and ModelTable components 2024-04-11 20:35:38 +01:00
Simon Larsen
5b0f450802 Merge branch 'master' into error-spans 2024-04-11 17:29:04 +01:00
Simon Larsen
14d049bd0f Refactor ShowTableAs to ShowAs in MonitorStatus, IncidentState, IncidentSeverity, ScheduledMaintenanceState, PublicNote, and Escalation components 2024-04-11 16:56:36 +01:00
Simon Larsen
47b5f44796 Refactor filter handling and permissions in Labels, Variable, Services, MonitorProbes, Users, MonitorTable, APIKeys, Domains, Variable, Call, Teams, and SSO components 2024-04-11 16:27:57 +01:00
Simon Larsen
ec8bb3d67b Refactor MonitorTypeHelper to include doesMonitorTypeHaveDocumentation and doesMonitorTypeHaveInterval methods 2024-04-11 15:19:10 +01:00
Simon Larsen
71abac2c3f Refactor MonitorTypeHelper to include isProbableMonitors method 2024-04-11 15:05:06 +01:00
Simon Larsen
21a227d67a Refactor filter handling and permissions in Owners components 2024-04-11 13:23:59 +01:00
Simon Larsen
df0783e4d4 Refactor filter handling and permissions in ModelTable components 2024-04-11 13:04:58 +01:00
Simon Larsen
a9a14b1253 Add filters to IncidentSeverity, PrivateUser, SMSSubscribers, and Announcements components 2024-04-11 11:42:42 +01:00
Simon Larsen
dc13aacb13 Refactor filter handling and permissions in WebhookSubscribers, IncidentState, StateTimeline, and MonitorOwners components 2024-04-11 11:30:05 +01:00
Simon Larsen
7f7d4c7388 Refactor filter handling and permissions in Projects, Probes, and Users components 2024-04-11 10:54:24 +01:00
Simon Larsen
f6642d5582 Refactor filter handling and permissions in EmailLog and IncidentNoteTemplates components 2024-04-10 21:31:12 +01:00
Simon Larsen
e85f216b9e Refactor filter handling and permissions in MonitorGroupResources, IncidentDelete, and MonitorProbes components 2024-04-10 21:27:59 +01:00
Simon Larsen
04289df987 Refactor filter handling and permissions in NewIncidents, ProjectInvitations, Domains, and Billing components 2024-04-10 21:19:28 +01:00
Simon Larsen
5e0bb766d6 Refactor filter handling and permissions in Owners, Labels, and TeamView components 2024-04-10 20:34:19 +01:00
Simon Larsen
e23c979911 Refactor filter handling and permissions in BaseModelTable component 2024-04-10 20:30:44 +01:00
Simon Larsen
cf43a3fb4e Refactor filter handling and permissions in BaseModelTable component 2024-04-10 20:12:44 +01:00
Simon Larsen
5d809015ee Refactor filter handling and permissions in BaseModelTable component 2024-04-10 18:30:32 +01:00
Simon Larsen
6b30e9aa2f Refactor BaseModelTable component to improve filter handling and permissions 2024-04-10 17:33:26 +01:00
Simon Larsen
29c4a43968 Refactor Table and ModelTable components to remove unused imports and properties 2024-04-10 17:20:46 +01:00
447 changed files with 21748 additions and 9019 deletions

View File

@@ -113,6 +113,7 @@
"no-console": "error",
"no-undef": "error",
"no-empty": "error",
"no-control-regex": "off",
"prefer-arrow-callback": "error",
"constructor-super": "error",
"no-case-declarations": "error",

View File

@@ -72,6 +72,22 @@ jobs:
- name: build docker image
run: sudo docker build -f ./App/Dockerfile .
docker-build-e2e:
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 ./E2E/Dockerfile .
docker-build-admin-dashboard:
runs-on: ubuntu-latest
env:

View File

@@ -174,6 +174,7 @@ jobs:
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: cd Common && npm install
- run: cd E2E && npm install && npm run compile && npm run dep-check
compile-probe:

View File

@@ -151,6 +151,66 @@ jobs:
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
e2e-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/e2e
ghcr.io/oneuptime/e2e
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 e2e.
- 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: ./E2E/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}}
isolated-vm-docker-image-deploy:
needs: generate-build-number
runs-on: ubuntu-latest
@@ -978,4 +1038,6 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}

View File

@@ -80,6 +80,68 @@ jobs:
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
e2e-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/e2e
ghcr.io/oneuptime/e2e
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 e2e.
- 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: ./E2E/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-server-docker-image-deploy:
needs: generate-build-number
runs-on: ubuntu-latest
@@ -880,7 +942,7 @@ jobs:
test-helm-chart:
runs-on: ubuntu-latest
needs: [ infrastructure-agent-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]
needs: [ infrastructure-agent-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:

62
.github/workflows/test.e2e.yaml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: E2E Tests
on:
pull_request:
push:
branches-ignore:
- 'hotfix-*' # excludes hotfix branches
- 'release'
jobs:
test:
runs-on: ubuntu-latest
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
- run: npm run dev
- 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/playwright-report
./E2E/test-results
# 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

5
.gitignore vendored
View File

@@ -107,4 +107,7 @@ InfrastructureAgent/build/*
InfrastructureAgent/err.log
InfrastructureAgent/out.log
InfrastructureAgent/daemon.pid
InfrastructureAgent/daemon.pid
App/greenlock/.greenlockrc
App/greenlock/greenlock.d/config.json
App/greenlock/greenlock.d/config.json.bak

View File

@@ -10,7 +10,17 @@ const app: ExpressApplication = Express.getExpressApp();
const init: PromiseVoidFunction = async (): Promise<void> => {
try {
// init the app
await App(APP_NAME, undefined, true);
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);

View File

@@ -1,4 +1,4 @@
{
"watch": ["webpack.config.js"],
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'HOST=localhost' >> /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3003 --mode=development"
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3003 --mode=development"
}

View File

@@ -11,14 +11,14 @@
"Common": "file:../Common",
"CommonServer": "file:../CommonServer",
"CommonUI": "file:../CommonUI",
"css-loader": "^6.10.0",
"css-loader": "^6.11.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"file-loader": "^6.2.0",
"Model": "file:../Model",
"react": "^18.2.0",
"react-dom": "^18.1.0",
"react-router-dom": "^6.22.2",
"react-router-dom": "^6.22.3",
"sass-loader": "^13.3.3",
"style-loader": "^3.3.4",
"ts-loader": "^9.5.1",
@@ -35,19 +35,19 @@
}
},
"../Common": {
"name": "common",
"name": "@oneuptime/common",
"version": "1.0.0",
"license": "MIT",
"license": "Apache-2.0",
"dependencies": {
"@types/crypto-js": "^4.2.2",
"@types/uuid": "^8.3.4",
"axios": "^1.6.7",
"axios": "^1.6.8",
"crypto-js": "^4.1.1",
"json5": "^2.2.3",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"posthog-js": "^1.104.4",
"reflect-metadata": "^0.2.1",
"posthog-js": "^1.116.6",
"reflect-metadata": "^0.2.2",
"slugify": "^1.6.5",
"typeorm": "^0.3.20",
"uuid": "^8.3.2"
@@ -61,49 +61,46 @@
}
},
"../CommonServer": {
"name": "common-server",
"name": "@oneuptime/common-server",
"version": "1.0.0",
"license": "MIT",
"license": "Apache-2.0",
"dependencies": {
"@clickhouse/client": "^0.2.7",
"@elastic/elasticsearch": "^8.11.0",
"@clickhouse/client": "^0.2.10",
"@elastic/elasticsearch": "^8.12.1",
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/api-logs": "^0.48.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.48.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.48.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.48.0",
"@opentelemetry/api-logs": "^0.49.1",
"@opentelemetry/auto-instrumentations-node": "^0.43.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.49.1",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.49.1",
"@opentelemetry/exporter-trace-otlp-proto": "^0.49.1",
"@opentelemetry/id-generator-aws-xray": "^1.2.1",
"@opentelemetry/instrumentation-express": "^0.35.0",
"@opentelemetry/instrumentation-http": "^0.48.0",
"@opentelemetry/sdk-logs": "^0.48.0",
"@opentelemetry/sdk-logs": "^0.49.1",
"@opentelemetry/sdk-metrics": "^1.21.0",
"@opentelemetry/sdk-node": "^0.48.0",
"@opentelemetry/sdk-trace-node": "^1.21.0",
"@socket.io/redis-adapter": "^8.2.1",
"acme-client": "^5.3.0",
"airtable": "^0.12.2",
"axios": "^1.6.4",
"bullmq": "^5.3.3",
"Common": "file:../Common",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"cron-parser": "^4.8.1",
"dotenv": "^16.4.1",
"ejs": "^3.1.8",
"express": "^4.17.3",
"dotenv": "^16.4.4",
"ejs": "^3.1.10",
"express": "^4.19.2",
"ioredis": "^5.3.2",
"json2csv": "^5.0.7",
"jsonwebtoken": "^9.0.0",
"markdown-it": "^13.0.1",
"Model": "file:../Model",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.9",
"nodemailer": "^6.9.10",
"pg": "^8.7.3",
"socket.io": "^4.7.2",
"socket.io": "^4.7.4",
"stripe": "^10.17.0",
"twilio": "^4.19.3",
"twilio": "^4.22.0",
"typeorm": "^0.3.20",
"typeorm-extension": "^2.2.13",
"vm2": "^3.9.14"
"typeorm-extension": "^2.2.13"
},
"devDependencies": {
"@faker-js/faker": "^6.3.1",
@@ -124,43 +121,48 @@
}
},
"../CommonUI": {
"name": "common-ui",
"name": "@oneuptime/common-ui",
"version": "1.0.0",
"license": "MIT",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.24.1",
"@monaco-editor/react": "^4.4.6",
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/context-zone": "^1.21.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.48.0",
"@opentelemetry/instrumentation": "^0.48.0",
"@opentelemetry/instrumentation-fetch": "^0.48.0",
"@opentelemetry/instrumentation-xml-http-request": "^0.48.0",
"@nivo/core": "^0.85.1",
"@nivo/line": "^0.85.1",
"@opentelemetry/api": "^1.8.0",
"@opentelemetry/context-zone": "^1.22.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.49.1",
"@opentelemetry/instrumentation": "^0.49.1",
"@opentelemetry/instrumentation-fetch": "^0.49.1",
"@opentelemetry/instrumentation-xml-http-request": "^0.49.1",
"@opentelemetry/resources": "^1.21.0",
"@opentelemetry/sdk-trace-web": "^1.21.0",
"@opentelemetry/semantic-conventions": "^1.21.0",
"@tippyjs/react": "^4.2.6",
"@types/react-highlight": "^0.12.8",
"Common": "file:../Common",
"formik": "^2.2.9",
"history": "^5.3.0",
"lodash": "^4.17.21",
"Model": "file:../Model",
"moment-timezone": "^0.5.44",
"moment-timezone": "^0.5.45",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-big-calendar": "^1.8.7",
"react-big-calendar": "^1.11.2",
"react-color": "^2.19.3",
"react-dom": "^18.1.0",
"react-dropzone": "^14.2.2",
"react-error-boundary": "^4.0.12",
"react-error-boundary": "^4.0.13",
"react-highlight": "^0.15.0",
"react-markdown": "^8.0.3",
"react-router-dom": "^6.21.3",
"react-router-dom": "^6.22.3",
"react-select": "^5.4.0",
"react-spinners": "^0.13.6",
"react-toggle": "^4.1.3",
"reactflow": "^11.10.3",
"reactflow": "^11.10.4",
"remark-gfm": "^3.0.1",
"socket.io-client": "^4.7.4",
"socket.io-client": "^4.7.5",
"tippy.js": "^6.3.7",
"universal-cookie": "^4.0.4",
"use-async-effect": "^2.2.6"
@@ -186,9 +188,9 @@
}
},
"../Model": {
"name": "model",
"name": "@oneuptime/model",
"version": "1.0.0",
"license": "ISC",
"license": "Apache-2.0",
"dependencies": {
"Common": "file:../Common",
"typeorm": "^0.3.20"
@@ -265,9 +267,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz",
"integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==",
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
"integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==",
"engines": {
"node": ">=14.0.0"
}
@@ -944,15 +946,15 @@
"dev": true
},
"node_modules/css-loader": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz",
"integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==",
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz",
"integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==",
"dependencies": {
"icss-utils": "^5.1.0",
"postcss": "^8.4.33",
"postcss-modules-extract-imports": "^3.0.0",
"postcss-modules-local-by-default": "^4.0.4",
"postcss-modules-scope": "^3.1.1",
"postcss-modules-extract-imports": "^3.1.0",
"postcss-modules-local-by-default": "^4.0.5",
"postcss-modules-scope": "^3.2.0",
"postcss-modules-values": "^4.0.0",
"postcss-value-parser": "^4.2.0",
"semver": "^7.5.4"
@@ -1920,9 +1922,9 @@
}
},
"node_modules/postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz",
"integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==",
"engines": {
"node": "^10 || ^12 || >= 14"
},
@@ -1931,9 +1933,9 @@
}
},
"node_modules/postcss-modules-local-by-default": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz",
"integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz",
"integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==",
"dependencies": {
"icss-utils": "^5.0.0",
"postcss-selector-parser": "^6.0.2",
@@ -1947,9 +1949,9 @@
}
},
"node_modules/postcss-modules-scope": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz",
"integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz",
"integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==",
"dependencies": {
"postcss-selector-parser": "^6.0.4"
},
@@ -1975,9 +1977,9 @@
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"version": "6.0.16",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -2085,11 +2087,11 @@
}
},
"node_modules/react-router": {
"version": "6.22.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz",
"integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==",
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz",
"integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==",
"dependencies": {
"@remix-run/router": "1.15.2"
"@remix-run/router": "1.15.3"
},
"engines": {
"node": ">=14.0.0"
@@ -2099,12 +2101,12 @@
}
},
"node_modules/react-router-dom": {
"version": "6.22.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz",
"integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==",
"version": "6.22.3",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz",
"integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==",
"dependencies": {
"@remix-run/router": "1.15.2",
"react-router": "6.22.2"
"@remix-run/router": "1.15.3",
"react-router": "6.22.3"
},
"engines": {
"node": ">=14.0.0"

View File

@@ -29,7 +29,7 @@
"Common": "file:../Common",
"CommonServer": "file:../CommonServer",
"CommonUI": "file:../CommonUI",
"css-loader": "^6.10.0",
"css-loader": "^6.11.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"file-loader": "^6.2.0",

View File

@@ -8,12 +8,13 @@ import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
import { LOGIN_API_URL } from '../Utils/ApiPaths';
import URL from 'Common/Types/API/URL';
import { JSONObject } from 'Common/Types/JSON';
import LoginUtil from '../Utils/Login';
import LoginUtil from 'CommonUI/src/Utils/Login';
import UserUtil from 'CommonUI/src/Utils/User';
import Navigation from 'CommonUI/src/Utils/Navigation';
import { DASHBOARD_URL } from 'CommonUI/src/Config';
import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert';
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
import useAsyncEffect from 'use-async-effect';
const LoginPage: () => JSX.Element = () => {
const apiUrl: URL = LOGIN_API_URL;
@@ -28,6 +29,16 @@ const LoginPage: () => JSX.Element = () => {
const [showSsoTip, setShowSSOTip] = useState<boolean>(false);
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="">
@@ -66,9 +77,14 @@ const LoginPage: () => JSX.Element = () => {
field: {
email: true,
},
title: 'Email',
fieldType: FormFieldSchemaType.Email,
placeholder: 'jeff@example.com',
required: true,
disabled: Boolean(
initialValues && initialValues['email']
),
title: 'Email',
dataTestId: 'email',
},
{
field: {
@@ -85,6 +101,7 @@ const LoginPage: () => JSX.Element = () => {
url: new Route('/accounts/forgot-password'),
openLinkInNewTab: false,
},
dataTestId: 'password',
},
]}
createOrUpdateApiUrl={apiUrl}
@@ -106,7 +123,7 @@ const LoginPage: () => JSX.Element = () => {
}}
maxPrimaryButtonWidth={true}
footer={
<div className="actions pointer text-center mt-4 hover:underline fw-semibold">
<div className="actions text-center mt-4 hover:underline fw-semibold">
<div>
{!showSsoTip && (
<div
@@ -121,11 +138,9 @@ const LoginPage: () => JSX.Element = () => {
{showSsoTip && (
<div className="text-gray-500 text-sm">
Please sign in with your username
and password. Once you have signed
in, you&apos;ll be able to sign in
via SSO that&apos;s configured for
your project.
Please sign in with your SSO
provider like Okta, Auth0, Entra ID
or any other SAML 2.0 provider.
</div>
)}
</div>

View File

@@ -5,7 +5,7 @@ import Link from 'CommonUI/src/Components/Link/Link';
import Route from 'Common/Types/API/Route';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
import LoginUtil from '../Utils/Login';
import LoginUtil from 'CommonUI/src/Utils/Login';
import { JSONObject } from 'Common/Types/JSON';
import UserUtil from 'CommonUI/src/Utils/User';
import Navigation from 'CommonUI/src/Utils/Navigation';
@@ -104,6 +104,7 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
disabled: Boolean(initialValues && initialValues['email']),
title: 'Email',
dataTestId: 'email',
},
{
field: {
@@ -113,6 +114,7 @@ const RegisterPage: () => JSX.Element = () => {
placeholder: 'Jeff Smith',
required: true,
title: 'Full Name',
dataTestId: 'name',
},
];
@@ -126,6 +128,7 @@ const RegisterPage: () => JSX.Element = () => {
placeholder: 'Acme, Inc.',
required: true,
title: 'Company Name',
dataTestId: 'companyName',
},
]);
@@ -139,6 +142,7 @@ const RegisterPage: () => JSX.Element = () => {
required: true,
placeholder: '+11234567890',
title: 'Phone Number',
dataTestId: 'companyPhoneNumber',
});
}
}
@@ -155,6 +159,7 @@ const RegisterPage: () => JSX.Element = () => {
placeholder: 'Password',
title: 'Password',
required: true,
dataTestId: 'password',
},
{
field: {
@@ -170,6 +175,7 @@ const RegisterPage: () => JSX.Element = () => {
overrideFieldKey: 'confirmPassword',
required: true,
showEvenIfPermissionDoesNotExist: true,
dataTestId: 'confirmPassword',
},
]);

View File

@@ -10,7 +10,18 @@ const app: ExpressApplication = Express.getExpressApp();
const init: PromiseVoidFunction = async (): Promise<void> => {
try {
// init the app
await App(APP_NAME, undefined, true);
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);

View File

@@ -1,4 +1,4 @@
{
"watch": ["webpack.config.js"],
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'HOST=localhost' >> /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3158 --mode=development"
"exec": "export DEBUG=express:* && printenv > /usr/src/app/dev-env/.env && echo 'USE_HTTPS=false' >> /usr/src/app/dev-env/.env && webpack-dev-server --port=3158 --mode=development"
}

View File

@@ -222,16 +222,30 @@ const Projects: FunctionComponent = (): ReactElement => {
noItemsMessage={'No projects found.'}
formFields={fields}
showRefreshButton={true}
showFilterButton={true}
viewPageRoute={Navigation.getCurrentRoute()}
columns={[
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,
isFilterable: true,
},
{
field: {
@@ -239,7 +253,6 @@ const Projects: FunctionComponent = (): ReactElement => {
},
title: 'Created At',
type: FieldType.DateTime,
isFilterable: true,
},
]}
/>

View File

@@ -12,7 +12,6 @@ import FieldType from 'CommonUI/src/Components/Types/FieldType';
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import { JSONObject } from 'Common/Types/JSON';
import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
import Pill from 'CommonUI/src/Components/Pill/Pill';
import { Green, Red } from 'Common/Types/BrandColors';
@@ -324,7 +323,7 @@ const Settings: FunctionComponent = (): ReactElement => {
)}
{emailServerType === EmailServerType.Sendgrid ? (
<CardModelDetail
<CardModelDetail<GlobalConfig>
name="Sendgrid Settings"
cardProps={{
title: 'Sendgrid Settings',
@@ -376,7 +375,7 @@ const Settings: FunctionComponent = (): ReactElement => {
},
title: '',
placeholder: 'None',
getElement: (item: JSONObject) => {
getElement: (item: GlobalConfig) => {
if (
item['sendgridApiKey'] &&
item['sendgridFromEmail'] &&

View File

@@ -7,7 +7,6 @@ import DashboardSideMenu from '../SideMenu';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import Probe from 'Model/Models/Probe';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { JSONObject } from 'Common/Types/JSON';
import OneUptimeDate from 'Common/Types/Date';
import { Green, Red } from 'Common/Types/BrandColors';
import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble';
@@ -23,7 +22,7 @@ import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes';
const Settings: FunctionComponent = (): ReactElement => {
const [showKeyModal, setShowKeyModal] = useState<boolean>(false);
const [currentProbe, setCurrentProbe] = useState<JSONObject | null>(null);
const [currentProbe, setCurrentProbe] = useState<Probe | null>(null);
return (
<Page
@@ -78,7 +77,6 @@ const Settings: FunctionComponent = (): ReactElement => {
modelAPI={AdminModelAPI}
noItemsMessage={'No probes found.'}
showRefreshButton={true}
showFilterButton={true}
onBeforeCreate={(item: Probe) => {
item.isGlobalProbe = true;
return Promise.resolve(item);
@@ -127,7 +125,7 @@ const Settings: FunctionComponent = (): ReactElement => {
title: 'Show ID and Key',
buttonStyleType: ButtonStyleType.NORMAL,
onClick: async (
item: JSONObject,
item: Probe,
onCompleteAction: VoidFunction,
onError: ErrorFunction
) => {
@@ -143,6 +141,22 @@ const Settings: FunctionComponent = (): ReactElement => {
},
},
]}
filters={[
{
field: {
name: true,
},
title: 'Name',
type: FieldType.Text,
},
{
field: {
description: true,
},
title: 'Description',
type: FieldType.Text,
},
]}
columns={[
{
field: {
@@ -150,8 +164,8 @@ const Settings: FunctionComponent = (): ReactElement => {
},
title: 'Name',
type: FieldType.Text,
isFilterable: true,
getElement: (item: JSONObject): ReactElement => {
getElement: (item: Probe): ReactElement => {
return <ProbeElement probe={item} />;
},
},
@@ -161,7 +175,6 @@ const Settings: FunctionComponent = (): ReactElement => {
},
title: 'Description',
type: FieldType.Text,
isFilterable: true,
},
{
field: {
@@ -169,15 +182,12 @@ const Settings: FunctionComponent = (): ReactElement => {
},
title: 'Status',
type: FieldType.Text,
isFilterable: false,
getElement: (item: JSONObject): ReactElement => {
getElement: (item: Probe): ReactElement => {
if (
item &&
item['lastAlive'] &&
OneUptimeDate.getNumberOfMinutesBetweenDates(
OneUptimeDate.fromString(
item['lastAlive'] as string
),
OneUptimeDate.fromString(item['lastAlive']),
OneUptimeDate.getCurrentDate()
) < 5
) {

View File

@@ -72,16 +72,37 @@ const Users: FunctionComponent = (): ReactElement => {
},
]}
showRefreshButton={true}
showFilterButton={true}
viewPageRoute={Navigation.getCurrentRoute()}
columns={[
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,
isFilterable: true,
},
{
field: {
@@ -89,7 +110,6 @@ const Users: FunctionComponent = (): ReactElement => {
},
title: 'Email',
type: FieldType.Email,
isFilterable: true,
},
{
field: {
@@ -97,7 +117,6 @@ const Users: FunctionComponent = (): ReactElement => {
},
title: 'Created At',
type: FieldType.DateTime,
isFilterable: true,
},
]}
/>

View File

@@ -16,58 +16,74 @@ import StatusServiceHandler from './Service/Status';
import DataTypeServiceHandler from './Service/DataType';
import Dictionary from 'Common/Types/Dictionary';
import { StaticPath } from './Utils/Config';
import FeatureSet from 'CommonServer/Types/FeatureSet';
const ResourceDictionary: Dictionary<ModelDocumentation> =
ResourceUtil.getResourceDictionaryByPath();
const APIReferenceFeatureSet: FeatureSet = {
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;

File diff suppressed because it is too large Load Diff

View File

@@ -10,73 +10,84 @@ import DocsNav, { NavGroup, NavLink } from './Utils/Nav';
import LocalFile from 'CommonServer/Utils/LocalFile';
import DocsRender from './Utils/Render';
import logger from 'CommonServer/Utils/Logger';
import FeatureSet from 'CommonServer/Types/FeatureSet';
const app: ExpressApplication = Express.getExpressApp();
const DocsFeatureSet: FeatureSet = {
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);
});
if (!currentCategory || !currrentNavLink) {
// render not found.
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,
});
}
);
const currrentNavLink: NavLink | undefined =
currentCategory?.links.find((link: NavLink) => {
return link.url.toLocaleLowerCase().includes(fullPath);
});
if (!currentCategory || !currrentNavLink) {
// render not found.
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,
});
}
}
);
app.use('/docs/static', ExpressStatic(StaticPath));
},
};
app.use('/docs/static', ExpressStatic(StaticPath));
export default DocsFeatureSet;

View File

@@ -5,18 +5,18 @@
<%- include('./Partials/Head.ejs') %>
</head>
<body class="flex min-h-full bg-white dark:bg-slate-900">
<body class="flex min-h-full bg-white ">
<div class="flex w-full flex-col">
<%- include('./Partials/Header.ejs') %>
<div class="relative mx-auto flex w-full max-w-8xl flex-auto justify-center sm:px-2 lg:px-8 xl:px-12">
<div class="hidden lg:relative lg:block lg:flex-none">
<div class="absolute inset-y-0 right-0 w-[50vw] bg-slate-50 dark:hidden"></div>
<div class="absolute inset-y-0 right-0 w-[50vw] bg-slate-50 "></div>
<div
class="absolute bottom-0 right-0 top-16 hidden h-12 w-px bg-gradient-to-t from-slate-800 dark:block">
class="absolute bottom-0 right-0 top-16 hidden h-12 w-px bg-gradient-to-t from-slate-800 ">
</div>
<div class="absolute bottom-0 right-0 top-28 hidden w-px bg-slate-800 dark:block"></div>
<div class="absolute bottom-0 right-0 top-28 hidden w-px bg-slate-800 "></div>
<div
class="sticky top-[4.75rem] -ml-0.5 h-[calc(100vh-4.75rem)] w-64 overflow-y-auto overflow-x-hidden py-16 pl-0.5 pr-8 xl:w-72 xl:pr-16">
<%- include('./Partials/Nav.ejs') %>

View File

@@ -1,11 +1,11 @@
<article>
<header class="mb-9 space-y-1">
<p class="text-base font-bold text-sky-500"><%- category.title %></p>
<h1 class="font-bold text-3xl tracking-tight text-slate-900 dark:text-white"><%- link.title %>
<h1 class="font-bold text-3xl tracking-tight text-slate-900 "><%- link.title %>
</h1>
</header>
<div
class="prose prose-slate max-w-none dark:prose-invert dark:text-slate-400 prose-headings:scroll-mt-28 prose-headings:font-display prose-headings:font-normal lg:prose-headings:scroll-mt-[8.5rem] prose-lead:text-slate-500 dark:prose-lead:text-slate-400 prose-a:font-semibold dark:prose-a:text-sky-400 prose-a:no-underline prose-a:shadow-[inset_0_-2px_0_0_var(--tw-prose-background,#fff),inset_0_calc(-1*(var(--tw-prose-underline-size,4px)+2px))_0_0_var(--tw-prose-underline,theme(colors.sky.300))] hover:prose-a:[--tw-prose-underline-size:6px] dark:[--tw-prose-background:theme(colors.slate.900)] dark:prose-a:shadow-[inset_0_calc(-1*var(--tw-prose-underline-size,2px))_0_0_var(--tw-prose-underline,theme(colors.sky.800))] dark:hover:prose-a:[--tw-prose-underline-size:6px] prose-pre:rounded-xl prose-pre:bg-slate-900 prose-pre:shadow-lg dark:prose-pre:bg-slate-800/60 dark:prose-pre:shadow-none dark:prose-pre:ring-1 dark:prose-pre:ring-slate-300/10 dark:prose-hr:border-slate-800">
class="prose prose-slate max-w-none prose-headings:scroll-mt-28 prose-headings:font-display prose-headings:font-normal lg:prose-headings:scroll-mt-[8.5rem] prose-lead:text-slate-500 prose-a:font-semibold prose-a:no-underline prose-a:shadow-[inset_0_-2px_0_0_var(--tw-prose-background,#fff),inset_0_calc(-1*(var(--tw-prose-underline-size,4px)+2px))_0_0_var(--tw-prose-underline,theme(colors.sky.300))] hover:prose-a:[--tw-prose-underline-size:6px] prose-pre:rounded-xl prose-pre:bg-slate-900 prose-pre:shadow-lg ">
<%- content %>
</div>
</article>

View File

@@ -1,5 +1,5 @@
<header
class="sticky top-0 z-50 flex flex-none flex-wrap items-center justify-between bg-white px-4 py-5 shadow-md shadow-slate-900/5 transition duration-500 sm:px-6 lg:px-8 dark:shadow-none dark:bg-slate-900/95 dark:backdrop-blur dark:[@supports(backdrop-filter:blur(0))]:bg-slate-900/75">
class="sticky top-0 z-50 flex flex-none flex-wrap items-center justify-between bg-white px-4 py-5 shadow-md shadow-slate-900/5 transition duration-500 sm:px-6 lg:px-8 ">
<div class="relative flex flex-grow basis-0 items-center"><a aria-label="Home page" href="/">
<img class="h-8 w-auto" src="/img/3-transparent.svg" alt="">
@@ -8,7 +8,7 @@
<div class="relative flex basis-0 justify-end gap-6 sm:gap-8 md:flex-grow">
<a class="group" aria-label="GitHub" target="_blank" href="https://github.com/oneuptime/oneuptime"><svg
aria-hidden="true" viewBox="0 0 16 16"
class="h-6 w-6 fill-slate-400 group-hover:fill-slate-500 dark:group-hover:fill-slate-300">
class="h-6 w-6 fill-slate-400 group-hover:fill-slate-500 ">
<path
d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z">
</path>

View File

@@ -2,11 +2,11 @@
<ul role="list" class="space-y-9">
<% for(var i=0; i<nav.length; i++) {%>
<li>
<h2 class="font-display font-medium text-slate-900 dark:text-white"><%- nav[i].title -%>
<h2 class="font-display font-medium text-slate-900 "><%- nav[i].title -%>
</h2>
<ul role="list"
class="mt-2 space-y-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-4 lg:border-slate-200 dark:border-slate-800">
class="mt-2 space-y-2 border-l-2 border-slate-100 lg:mt-4 lg:space-y-4 lg:border-slate-200 ">
<% if(nav[i].links.length> 0) { %>
<% for(var j=0; j<nav[i].links.length; j++) {%>
<% if(link.url===nav[i].links[j].url) { %>
@@ -17,7 +17,7 @@
</li>
<% } else { %>
<li class="relative"><a
class="block w-full pl-3.5 before:pointer-events-none before:absolute before:-left-1 before:top-1/2 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block dark:text-slate-400 dark:before:bg-slate-700 dark:hover:text-slate-300"
class="block w-full pl-3.5 before:pointer-events-none before:absolute before:-left-1 before:top-1/2 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full text-slate-500 before:hidden before:bg-slate-300 hover:text-slate-600 hover:before:block "
href="<%- nav[i].links[j].url -%>"><%-
nav[i].links[j].title -%></a></li>

View File

@@ -1,8 +1,8 @@
<dl class="mt-12 flex border-t border-slate-200 pt-6 dark:border-slate-800">
<dl class="mt-12 flex border-t border-slate-200 pt-6 ">
<div class="ml-auto text-right">
<dt class="font-display text-sm font-medium text-slate-900 dark:text-white">Next</dt>
<dt class="font-display text-sm font-medium text-slate-900 ">Next</dt>
<dd class="mt-1"><a
class="flex items-center gap-x-1 text-base font-semibold text-slate-500 hover:text-slate-600 dark:text-slate-400 dark:hover:text-slate-300"
class="flex items-center gap-x-1 text-base font-semibold text-slate-500 hover:text-slate-600 "
href="/docs/installation">Installation<svg viewBox="0 0 16 16" aria-hidden="true"
class="h-4 w-4 flex-none fill-current">
<path

File diff suppressed because it is too large Load Diff

View File

@@ -474,7 +474,7 @@
</div>
<input id="default-range" type="range" value="1" max="100" min="0" step="1"
onchange="updateMonitorPrice(this.value)" oninput="updateMonitorPrice(this.value)"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700">
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer ">
<div>
<p id="more-than-100-monitors-message" style="display: none;" class="text-base text-gray-600">If you have
@@ -564,7 +564,7 @@
</div>
<input id="default-range" type="range" value="1" max="1000" min="0" step="1"
onchange="updateTelemetryPrice(this.value, null)" oninput="updateTelemetryPrice(this.value, null)"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700">
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer ">
<div>
<p id="more-than-10-tb-ingested-message" style="display: none;" class="text-base text-gray-600">If you are
ingesting more than 1 TB of data every month, contact sales@oneuptime.com for a discount.</p>
@@ -578,7 +578,7 @@
</div>
<input id="default-range" type="range" value="15" max="180" min="0" step="1"
onchange="updateTelemetryPrice(null, this.value)" oninput="updateTelemetryPrice(null,this.value)"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700">
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer ">
<div>
<p id="more-than-6-months-rentention" style="display: none;" class="text-base text-gray-600">If you're
looking for data rention of more than 6 months. contact sales@oneuptime.com for a discount. </p>

View File

@@ -182,10 +182,18 @@ router.post(
savedUser.id!
);
const token: string = JSONWebToken.sign(
savedUser,
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
);
const token: string = JSONWebToken.signUserLoginToken({
tokenData: {
userId: savedUser.id!,
email: savedUser.email!,
name: savedUser.name!,
isMasterAdmin: savedUser.isMasterAdmin!,
isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. 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(), token, {
@@ -275,7 +283,7 @@ router.post(
logger.error(err);
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
return Response.sendErrorResponse(
@@ -390,7 +398,7 @@ router.post(
logger.error(err);
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
@@ -482,7 +490,7 @@ router.post(
logger.error(err);
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
@@ -499,7 +507,7 @@ router.post(
try {
CookieUtil.removeAllCookies(req, res);
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
@@ -573,10 +581,18 @@ router.post(
alreadySavedUser.password.toString() ===
user.password!.toString()
) {
const token: string = JSONWebToken.sign(
alreadySavedUser,
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
);
const token: string = JSONWebToken.signUserLoginToken({
tokenData: {
userId: alreadySavedUser.id!,
email: alreadySavedUser.email!,
name: alreadySavedUser.name!,
isMasterAdmin: alreadySavedUser.isMasterAdmin!,
isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. 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(

View File

@@ -62,10 +62,10 @@ router.post(
// if found then generate a token and return it.
const token: string = JSONWebToken.sign(
{ resellerId: resellerId },
OneUptimeDate.getDayInSeconds(365)
);
const token: string = JSONWebToken.sign({
data: { resellerId: resellerId },
expiresInSeconds: OneUptimeDate.getDayInSeconds(365),
});
return Response.sendJsonObjectResponse(req, res, {
access: token,

View File

@@ -32,6 +32,7 @@ import Hostname from 'Common/Types/API/Hostname';
import Protocol from 'Common/Types/API/Protocol';
import DatabaseConfig from 'CommonServer/DatabaseConfig';
import CookieUtil from 'CommonServer/Utils/Cookie';
import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig';
const router: ExpressRouter = Express.getRouter();
@@ -67,7 +68,10 @@ router.get(
isEnabled: true,
},
select: {
_id: true,
signOnURL: true,
issuerURL: true,
projectId: true,
},
props: {
isRoot: true,
@@ -92,310 +96,374 @@ router.get(
);
}
return Response.redirect(req, res, projectSSO.signOnURL);
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> => {
try {
const samlResponseBase64: string = req.body.SAMLResponse;
return await loginUserWithSso(req, res);
}
);
const samlResponse: string = Buffer.from(
samlResponseBase64,
'base64'
).toString();
type LoginUserWithSsoFunction = (
req: ExpressRequest,
res: ExpressResponse
) => Promise<void>;
const response: JSONObject = await xml2js.parseStringPromise(
samlResponse
const loginUserWithSso: LoginUserWithSsoFunction = async (
req: ExpressRequest,
res: ExpressResponse
): Promise<void> => {
try {
const samlResponseBase64: string = req.body.SAMLResponse;
if (!samlResponseBase64) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SAMLResponse not found')
);
}
let issuerUrl: string = '';
let email: Email | null = null;
const samlResponse: string = Buffer.from(
samlResponseBase64,
'base64'
).toString();
if (!req.params['projectId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Project ID not found')
);
}
const response: JSONObject = await xml2js.parseStringPromise(
samlResponse
);
if (!req.params['projectSsoId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Project SSO ID not found')
);
}
let issuerUrl: string = '';
let email: Email | null = null;
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 (!req.params['projectId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Project ID not found')
);
}
if (!projectSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SSO Config not found')
);
}
if (!req.params['projectSsoId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Project SSO ID 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) {
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,
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,
},
});
let isNewUser: boolean = false;
isNewUser = true;
}
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.
// 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!);
/// Create a user.
alreadySavedUser = await UserService.createByEmail(email, {
isRoot: true,
});
isNewUser = true;
}
// If he does not then add him to teams that he should belong and log in.
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,
},
});
}
}
if (isNewUser) {
return Response.render(
req,
res,
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
{
title: 'You have not signed up so far.',
message:
'You need to sign up for an account on OneUptime with this email:' +
email.toString() +
'. Once you have signed up, you can use SSO to log in to your project.',
}
);
}
const projectId: ObjectID = new ObjectID(
req.params['projectId'] as string
);
const token: string = JSONWebToken.sign(
{
userId: alreadySavedUser.id!,
projectId: projectId,
email: email,
isMasterAdmin: false,
},
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
);
// Refresh Permissions for this user here.
await AccessTokenService.refreshUserAllPermissions(
alreadySavedUser.id!
);
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol =
await DatabaseConfig.getHttpProtocol();
CookieUtil.setCookie(
res,
CookieUtil.getUserSSOKey(projectId),
token,
{
maxAge: OneUptimeDate.getMillisecondsInDays(
new PositiveNumber(30)
),
httpOnly: true,
}
);
return Response.redirect(
return Response.render(
req,
res,
new URL(
httpProtocol,
host,
new Route(DashboardRoute.toString()).addRoute(
'/' + req.params['projectId']
),
'sso_token=' + token
)
'/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.',
}
);
} catch (err) {
logger.error(err);
Response.sendErrorResponse(req, res, new ServerException());
}
// 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();
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;

View File

@@ -51,7 +51,7 @@ router.post(
CookieUtil.getUserTokenKey(statusPageId)
); // remove the cookie.
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
@@ -178,7 +178,7 @@ router.post(
logger.error(err);
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
throw new BadDataException(
@@ -320,7 +320,7 @@ router.post(
logger.error(err);
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
@@ -389,10 +389,12 @@ router.post(
});
if (alreadySavedUser) {
const token: string = JSONWebToken.sign(
alreadySavedUser,
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
);
const token: string = JSONWebToken.sign({
data: alreadySavedUser,
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30)
),
});
CookieUtil.setCookie(
res,

View File

@@ -25,6 +25,7 @@ import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivat
import HashedString from 'Common/Types/HashedString';
import StatusPageService from 'CommonServer/Services/StatusPageService';
import CookieUtil from 'CommonServer/Utils/Cookie';
import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig';
const router: ExpressRouter = Express.getRouter();
@@ -65,6 +66,8 @@ router.get(
},
select: {
signOnURL: true,
statusPageId: true,
_id: true,
},
props: {
isRoot: true,
@@ -89,7 +92,17 @@ router.get(
);
}
return Response.redirect(req, res, statusPageSSO.signOnURL);
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);
}
@@ -270,10 +283,12 @@ router.post(
});
}
const token: string = JSONWebToken.sign(
alreadySavedUser,
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
);
const token: string = JSONWebToken.sign({
data: alreadySavedUser,
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30)
),
});
CookieUtil.setCookie(
res,

View File

@@ -5,20 +5,27 @@ import SsoAPI from './API/SSO';
import ResellerAPI from './API/Reseller';
import StatusPageSsoAPI from './API/StatusPageSSO';
import StatusPageAuthenticationAPI from './API/StatusPageAuthentication';
import FeatureSet from 'CommonServer/Types/FeatureSet';
const app: ExpressApplication = Express.getExpressApp();
const IdentityFeatureSet: FeatureSet = {
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;

View File

@@ -4,8 +4,34 @@ import Email from 'Common/Types/Email';
import xmldom from 'xmldom';
import xmlCrypto, { FileKeyInfo } from 'xml-crypto';
import logger from 'CommonServer/Utils/Logger';
import OneUptimeDate from 'Common/Types/Date';
import Text from 'Common/Types/Text';
import URL from 'Common/Types/API/URL';
import zlib from 'zlib';
export default class SSOUtil {
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 deflated: Buffer = zlib.deflateRawSync(samlRequest);
const base64Encoded: string = deflated.toString('base64');
return URL.fromString(signOnUrl.toString()).addQueryParam(
'SAMLRequest',
base64Encoded,
true
);
}
public static isPayloadValid(payload: JSONObject): void {
if (
!payload['saml2p:Response'] &&
@@ -18,11 +44,13 @@ export default class SSOUtil {
payload =
(payload['saml2p:Response'] as JSONObject) ||
(payload['samlp: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['saml:Issuer'] as JSONArray) ||
(payload['Issuer'] as JSONArray);
if (issuers.length === 0) {
throw new BadRequestException('Issuers not found');
@@ -47,7 +75,8 @@ export default class SSOUtil {
const samlAssertion: JSONArray =
(payload['saml2:Assertion'] as JSONArray) ||
(payload['saml:Assertion'] as JSONArray);
(payload['saml:Assertion'] as JSONArray) ||
(payload['Assertion'] as JSONArray);
if (!samlAssertion || samlAssertion.length === 0) {
throw new BadRequestException('SAML Assertion not found');
@@ -55,7 +84,8 @@ export default class SSOUtil {
const samlSubject: JSONArray =
((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) ||
((samlAssertion[0] as JSONObject)['saml: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');
@@ -63,7 +93,8 @@ export default class SSOUtil {
const samlNameId: JSONArray =
((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) ||
((samlSubject[0] as JSONObject)['saml: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');
@@ -120,11 +151,13 @@ export default class SSOUtil {
payload =
(payload['saml2p:Response'] as JSONObject) ||
(payload['samlp: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['saml:Assertion'] as JSONArray) ||
(payload['Assertion'] as JSONArray);
if (!samlAssertion || samlAssertion.length === 0) {
throw new BadRequestException('SAML Assertion not found');
@@ -132,7 +165,8 @@ export default class SSOUtil {
const samlSubject: JSONArray =
((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) ||
((samlAssertion[0] as JSONObject)['saml: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');
@@ -140,7 +174,8 @@ export default class SSOUtil {
const samlNameId: JSONArray =
((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) ||
((samlSubject[0] as JSONObject)['saml: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');
@@ -160,11 +195,13 @@ export default class SSOUtil {
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['saml:Issuer'] as JSONArray) ||
(payload['Issuer'] as JSONArray);
if (issuers.length === 0) {
throw new BadRequestException('Issuers not found');

View File

@@ -32,7 +32,7 @@ router.post(
customTwilioConfig: body['customTwilioConfig'] as any,
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);
@@ -137,7 +137,7 @@ router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
);
}
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
});
export default router;

View File

@@ -44,7 +44,7 @@ router.post(
(body['userOnCallLogTimelineId'] as ObjectID) || undefined,
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);

View File

@@ -35,7 +35,7 @@ router.post(
}
);
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);
@@ -135,7 +135,7 @@ router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
);
}
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
});
export default router;

View File

@@ -98,7 +98,7 @@ router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
);
}
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
});
export default router;

View File

@@ -7,11 +7,18 @@ import SmsAPI from './API/SMS';
import CallAPI from './API/Call';
import SMTPConfigAPI from './API/SMTPConfig';
import './Utils/Handlebars';
import FeatureSet from 'CommonServer/Types/FeatureSet';
const APP_NAME: string = 'api/notification';
const app: ExpressApplication = Express.getExpressApp();
const NotificationFeatureSet: FeatureSet = {
init: async (): Promise<void> => {
const APP_NAME: string = 'api/notification';
const app: ExpressApplication = Express.getExpressApp();
app.use([`/${APP_NAME}/email`, '/email'], MailAPI);
app.use([`/${APP_NAME}/sms`, '/sms'], SmsAPI);
app.use([`/${APP_NAME}/call`, '/call'], CallAPI);
app.use([`/${APP_NAME}/smtp-config`, '/smtp-config'], SMTPConfigAPI);
app.use([`/${APP_NAME}/email`, '/email'], MailAPI);
app.use([`/${APP_NAME}/sms`, '/sms'], SmsAPI);
app.use([`/${APP_NAME}/call`, '/call'], CallAPI);
app.use([`/${APP_NAME}/smtp-config`, '/smtp-config'], SMTPConfigAPI);
},
};
export default NotificationFeatureSet;

View File

@@ -13,7 +13,8 @@
{{> DetailBoxField title="Current State: " text=currentState }}
{{> DetailBoxField title="Resources Affected: " text=resourcesAffected }}
{{> DetailBoxField title="Severity: " text=incidentSeverity }}
{{> DetailBoxField title="Root Cause: " text=rootCause }}
{{> DetailBoxField title="Root Cause: " text="" }}
{{> DetailBoxField title="" text=rootCause }}
{{> DetailBoxField title="Description: " text="" }}
{{> DetailBoxField title="" text=incidentDescription }}
{{> DetailBoxEnd this }}

View File

@@ -13,7 +13,8 @@
{{> DetailBoxField title="Current State: " text=currentState }}
{{> DetailBoxField title="Resources Affected: " text=resourcesAffected }}
{{> DetailBoxField title="Severity: " text=incidentSeverity }}
{{> DetailBoxField title="Root Cause: " text=rootCause }}
{{> DetailBoxField title="Root Cause: " text="" }}
{{> DetailBoxField title="" text=rootCause }}
{{> DetailBoxField title="Description: " text="" }}
{{> DetailBoxField title="" text=incidentDescription }}
{{> DetailBoxEnd this }}

View File

@@ -11,7 +11,8 @@
{{> DetailBoxStart this }}
{{> DetailBoxField title="Monitor Name:" text=monitorName }}
{{> DetailBoxField title="New Status: " text=currentStatus }}
{{> DetailBoxField title="Root Cause: " text=rootCause }}
{{> DetailBoxField title="Root Cause: " text="" }}
{{> DetailBoxField title="" text=rootCause }}
{{> DetailBoxField title="Status changed at: " text="" }}
{{> DetailBoxField title="" text=statusChangedAt }}
{{> DetailBoxField title="Description: " text="" }}

View File

@@ -1 +0,0 @@
{"manager":"/usr/src/app/FeatureSet/Workers/Utils/Greenlock/Manager.ts","configDir":"./greenlock.d"}

View File

@@ -0,0 +1,50 @@
import DataMigrationBase from './DataMigrationBase';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import TelemetryService from 'Model/Models/TelemetryService';
import TelemetryServiceService from 'CommonServer/Services/TelemetryServiceService';
import { BrightColors } from 'Common/Types/BrandColors';
import ArrayUtil from 'Common/Types/ArrayUtil';
export default class AddTelemetryServiceColor extends DataMigrationBase {
public constructor() {
super('AddTelemetryServiceColor');
}
public override async migrate(): Promise<void> {
// get all the users with email isVerified true.
const services: Array<TelemetryService> =
await TelemetryServiceService.findBy({
query: {},
select: {
_id: true,
serviceColor: true,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
});
for (const service of services) {
if (!service.serviceColor) {
(service.serviceColor =
ArrayUtil.selectItemByRandom(BrightColors)),
await TelemetryServiceService.updateOneById({
id: service.id!,
data: {
serviceColor: service.serviceColor,
},
props: {
isRoot: true,
},
});
}
}
}
public override async rollback(): Promise<void> {
return;
}
}

View File

@@ -0,0 +1,43 @@
import DataMigrationBase from './DataMigrationBase';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService';
import logger from 'CommonServer/Utils/Logger';
import StatusPageDomain from 'Model/Models/StatusPageDomain';
export default class GenerateNewCertsForStatusPage extends DataMigrationBase {
public constructor() {
super('GenerateNewCertsForStatusPage');
}
public override async migrate(): Promise<void> {
// get all domains in greenlock certs.
const statusPageDomains: Array<StatusPageDomain> =
await StatusPageDomainService.findBy({
query: {},
props: {
isRoot: true,
},
limit: LIMIT_MAX,
skip: 0,
select: {
_id: true,
},
});
// now order these domains
for (const statusPageDomain of statusPageDomains) {
// get status page domain.
try {
await StatusPageDomainService.orderCert(statusPageDomain);
} catch (e) {
logger.error(e);
}
}
}
public override async rollback(): Promise<void> {
return;
}
}

View File

@@ -21,6 +21,9 @@ import MigrateToMeteredSubscription from './MigrateToMeteredSubscription';
import MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage from './MoveEnableSubscribersToEnableEmailSubscribersOnStatusPage';
import UpdateActiveMonitorCountToBillingProvider from './UpdateActiveMonitorCountToBillingProvider';
import UpdateGlobalConfigFromEnv from './UpdateGlobalCongfigFromEnv';
import AddTelemetryServiceColor from './AddTelemetryServiceColor';
import MoveGreenlockCertsToAcmeCerts from './MoveGreenlockCertsToAcmeCerts';
import GenerateNewCertsForStatusPage from './GenerateNewCertsForStatusPage';
// This is the order in which the migrations will be run. Add new migrations to the end of the array.
@@ -47,6 +50,9 @@ const DataMigrations: Array<DataMigrationBase> = [
new ChangeLogSeverityColumnTypeFromTextToNumber(),
new AddAttributeColumnToSpanAndLog(),
new AddSecretKeyToIncomingRequestMonitor(),
new AddTelemetryServiceColor(),
new MoveGreenlockCertsToAcmeCerts(),
new GenerateNewCertsForStatusPage(),
];
export default DataMigrations;

View File

@@ -0,0 +1,77 @@
import GreenlockCertificateService from 'CommonServer/Services/GreenlockCertificateService';
import DataMigrationBase from './DataMigrationBase';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService';
import logger from 'CommonServer/Utils/Logger';
import GreenlockCertificate from 'Model/Models/GreenlockCertificate';
import StatusPageDomain from 'Model/Models/StatusPageDomain';
export default class MoveGreenlockCertsToAcmeCerts extends DataMigrationBase {
public constructor() {
super('MoveGreenlockCertsToAcmeCerts');
}
public override async migrate(): Promise<void> {
const allDomains: Array<string> = [];
// get all domains in greenlock certs.
const greenlockCerts: GreenlockCertificate[] =
await GreenlockCertificateService.findBy({
query: {},
select: {
key: true,
},
skip: 0,
limit: LIMIT_MAX,
props: {
isRoot: true,
},
});
for (const greenlockCert of greenlockCerts) {
if (!greenlockCert.key) {
continue;
}
if (allDomains.includes(greenlockCert.key!)) {
// this domain already exists in acme certs.
continue;
}
allDomains.push(greenlockCert.key!);
}
// now order these domains
for (const domain of allDomains) {
// get status page domain.
const statusPageDomain: StatusPageDomain | null =
await StatusPageDomainService.findOneBy({
query: {
fullDomain: domain,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (!statusPageDomain) {
continue;
}
try {
await StatusPageDomainService.orderCert(statusPageDomain);
} catch (e) {
logger.error(e);
}
}
}
public override async rollback(): Promise<void> {
return;
}
}

View File

@@ -34,10 +34,8 @@ import './Jobs/TelemetryService/DeleteOldData';
import './Jobs/ScheduledMaintenancePublicNote/SendNotificationToSubscribers';
// Certs Routers
import StatusPageCerts from './Jobs/StatusPageCerts/StatusPageCerts';
import './Jobs/StatusPageCerts/StatusPageCerts';
// Express
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
import JobDictionary from './Utils/JobDictionary';
// Monitor Owners
@@ -84,13 +82,6 @@ import './Jobs/MonitorMetrics/MonitorMetricsByMinute';
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
const APP_NAME: string = 'api/workers';
const app: ExpressApplication = Express.getExpressApp();
//cert routes.
app.use(`/${APP_NAME.toLocaleLowerCase()}`, StatusPageCerts);
const WorkersFeatureSet: FeatureSet = {
init: async (): Promise<void> => {
try {

View File

@@ -1,456 +1,51 @@
import { EVERY_HOUR, EVERY_MINUTE } from 'Common/Utils/CronTime';
import { EVERY_FIFTEEN_MINUTE } from 'Common/Utils/CronTime';
import RunCron from '../../Utils/Cron';
import { IsDevelopment } from 'CommonServer/EnvironmentConfig';
import StatusPageDomain from 'Model/Models/StatusPageDomain';
import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService';
// @ts-ignore
import Greenlock from 'greenlock';
import logger from 'CommonServer/Utils/Logger';
import BadDataException from 'Common/Types/Exception/BadDataException';
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from 'CommonServer/Utils/Express';
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
import { JSONObject } from 'Common/Types/JSON';
import Response from 'CommonServer/Utils/Response';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import axios, { AxiosResponse } from 'axios';
const router: ExpressRouter = Express.getRouter();
const greenlock: any = Greenlock.create({
configFile: '/usr/src/app/FeatureSet/Workers/greenlockrc',
packageRoot: `/usr/src/app`,
manager: '/usr/src/app/FeatureSet/Workers/Utils/Greenlock/Manager.ts',
approveDomains: async (opts: any) => {
const domain: StatusPageDomain | null =
await StatusPageDomainService.findOneBy({
query: {
fullDomain: opts.domain,
},
select: {
_id: true,
fullDomain: true,
},
props: {
isRoot: true,
},
});
if (!domain) {
throw new BadDataException(
`Domain ${opts.domain} does not exist in StatusPageDomain`
);
}
return opts; // or Promise.resolve(opts);
},
store: {
module: '/usr/src/app/FeatureSet/Workers/Utils/Greenlock/Store.ts',
},
// Staging for testing environments
// staging: IsDevelopment,
// This should be the contact who receives critical bug and security notifications
// Optionally, you may receive other (very few) updates, such as important new features
maintainerEmail: 'lets-encrypt@oneuptime.com',
// for an RFC 8555 / RFC 7231 ACME client user agent
packageAgent: 'oneuptime/1.0.0',
notify: function (event: string, details: any) {
if ('error' === event) {
logger.error('Greenlock Notify: ' + event);
logger.error(details);
}
logger.info('Greenlock Notify: ' + event);
logger.info(details);
},
agreeToTerms: true,
challenges: {
'http-01': {
module: '/usr/src/app/FeatureSet/Workers/Utils/Greenlock/HttpChallenge.ts',
},
},
});
// Delete
router.delete(
`/certs`,
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const body: JSONObject = req.body;
if (!body['domain']) {
throw new BadDataException('Domain is required');
}
await greenlock.remove({
subject: body['domain'],
});
return Response.sendEmptyResponse(req, res);
} catch (err) {
next(err);
}
}
);
// Create
router.post(
`/certs`,
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const body: JSONObject = req.body;
if (!body['domain']) {
throw new BadDataException('Domain is required');
}
await greenlock.add({
subject: body['domain'],
altnames: [body['domain']],
});
return Response.sendEmptyResponse(req, res);
} catch (err) {
next(err);
}
}
);
// Create
router.get(
`/certs`,
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const body: JSONObject = req.body;
if (!body['domain']) {
throw new BadDataException('Domain is required');
}
const site: JSONObject = await greenlock.get({
servername: body['domain'] as string,
});
return Response.sendJsonObjectResponse(req, res, site);
} catch (err) {
next(err);
}
}
);
RunCron(
'StatusPageCerts:RenewCerts',
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
{
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE,
runOnStartup: true,
},
async () => {
logger.info('Renewing Certs...');
await greenlock.renew();
await StatusPageDomainService.renewCertsWhichAreExpiringSoon();
logger.info('Renew Completed...');
}
);
RunCron(
'StatusPageCerts:OrderCerts',
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
async () => {
// Fetch all domains where certs are added to greenlock.
const domains: Array<StatusPageDomain> =
await StatusPageDomainService.findBy({
query: {
isAddedToGreenlock: true,
isSslProvisioned: false,
},
select: {
_id: true,
greenlockConfig: true,
fullDomain: true,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
});
logger.info(`Certificates to Order: ${domains.length}`);
for (const domain of domains) {
logger.info(
`StatusPageCerts:OrderCerts - Ordering Certificate ${domain.fullDomain}`
);
greenlock.order(domain.greenlockConfig).catch((err: any) => {
logger.error(
`StatusPageCerts:OrderCerts - Failed for domain ${domain.fullDomain}`
);
logger.error(err);
});
}
}
);
RunCron(
'StatusPageCerts:AddCerts',
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
async () => {
const domains: Array<StatusPageDomain> =
await StatusPageDomainService.findBy({
query: {
isAddedToGreenlock: false,
},
select: {
_id: true,
fullDomain: true,
cnameVerificationToken: true,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
});
for (const domain of domains) {
logger.info(
`StatusPageCerts:AddCerts - Checking CNAME ${domain.fullDomain}`
);
// Check CNAME validation and if that fails. Remove certs from Greenlock.
const isValid: boolean = await checkCnameValidation(
domain.fullDomain!,
domain.cnameVerificationToken!
);
if (isValid) {
logger.info(
`StatusPageCerts:AddCerts - CNAME for ${domain.fullDomain} is valid. Adding domain to greenlock.`
);
await StatusPageDomainService.updateOneById({
id: domain.id!,
data: {
isCnameVerified: true,
},
props: {
isRoot: true,
},
});
await greenlock.add({
subject: domain.fullDomain,
altnames: [domain.fullDomain],
});
await StatusPageDomainService.updateOneById({
id: domain.id!,
data: {
isAddedToGreenlock: true,
},
props: {
isRoot: true,
},
});
logger.info(
`StatusPageCerts:AddCerts - ${domain.fullDomain} added to greenlock.`
);
} else {
logger.info(
`StatusPageCerts:AddCerts - CNAME for ${domain.fullDomain} is invalid. Removing cert`
);
}
}
}
);
RunCron(
'StatusPageCerts:RemoveCerts',
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
async () => {
// Fetch all domains where certs are added to greenlock.
const domains: Array<StatusPageDomain> =
await StatusPageDomainService.findBy({
query: {
isAddedToGreenlock: true,
},
select: {
_id: true,
fullDomain: true,
cnameVerificationToken: true,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
});
for (const domain of domains) {
logger.info(
`StatusPageCerts:RemoveCerts - Checking CNAME ${domain.fullDomain}`
);
// Check CNAME validation and if that fails. Remove certs from Greenlock.
const isValid: boolean = await checkCnameValidation(
domain.fullDomain!,
domain.cnameVerificationToken!
);
if (!isValid) {
logger.info(
`StatusPageCerts:RemoveCerts - CNAME for ${domain.fullDomain} is invalid. Removing domain from greenlock.`
);
await greenlock.remove({
subject: domain.fullDomain,
});
await StatusPageDomainService.updateOneById({
id: domain.id!,
data: {
isAddedToGreenlock: false,
isCnameVerified: false,
},
props: {
isRoot: true,
},
});
logger.info(
`StatusPageCerts:RemoveCerts - ${domain.fullDomain} removed from greenlock.`
);
} else {
logger.info(
`StatusPageCerts:RemoveCerts - CNAME for ${domain.fullDomain} is valid`
);
}
}
}
);
RunCron(
'StatusPageCerts:CheckSslProvisioningStatus',
{ schedule: IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, runOnStartup: true },
{
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE,
runOnStartup: true,
},
async () => {
// Fetch all domains where certs are added to greenlock.
const domains: Array<StatusPageDomain> =
await StatusPageDomainService.findBy({
query: {
isAddedToGreenlock: true,
},
select: {
_id: true,
fullDomain: true,
cnameVerificationToken: true,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
});
for (const domain of domains) {
logger.info(
`StatusPageCerts:RemoveCerts - Checking CNAME ${domain.fullDomain}`
);
// Check CNAME validation and if that fails. Remove certs from Greenlock.
const isValid: boolean = await isSslProvisioned(
domain.fullDomain!,
domain.cnameVerificationToken!
);
if (!isValid) {
await StatusPageDomainService.updateOneById({
id: domain.id!,
data: {
isSslProvisioned: false,
},
props: {
isRoot: true,
},
});
} else {
await StatusPageDomainService.updateOneById({
id: domain.id!,
data: {
isSslProvisioned: true,
},
props: {
isRoot: true,
},
});
}
}
await StatusPageDomainService.updateSslProvisioningStatusForAllDomains();
}
);
type CheckCnameValidationFunction = (
fulldomain: string,
token: string
) => Promise<boolean>;
const checkCnameValidation: CheckCnameValidationFunction = async (
fulldomain: string,
token: string
): Promise<boolean> => {
try {
const result: AxiosResponse = await axios.get(
'http://' +
fulldomain +
'/status-page-api/cname-verification/' +
token
);
if (result.status === 200) {
return true;
}
return false;
} catch (err) {
logger.info('Failed checking for CNAME ' + fulldomain);
logger.info('Token: ' + token);
logger.info(err);
return false;
RunCron(
'StatusPageCerts:OrderSSL',
{
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE,
runOnStartup: true,
},
async () => {
await StatusPageDomainService.orderSSLForDomainsWhichAreNotOrderedYet();
}
};
);
type IsSSLProvisionedFunction = (
fulldomain: string,
token: string
) => Promise<boolean>;
const isSslProvisioned: IsSSLProvisionedFunction = async (
fulldomain: string,
token: string
): Promise<boolean> => {
try {
const result: AxiosResponse = await axios.get(
'https://' +
fulldomain +
'/status-page-api/cname-verification/' +
token
);
if (result.status === 200) {
return true;
}
return false;
} catch (err) {
return false;
RunCron(
'StatusPageCerts:VerifyCnameWhoseCnameisNotVerified',
{
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_FIFTEEN_MINUTE,
runOnStartup: true,
},
async () => {
await StatusPageDomainService.verifyCnameWhoseCnameisNotVerified();
}
};
export default router;
);

View File

@@ -1,112 +0,0 @@
import GreenlockChallenge from 'Model/Models/GreenlockChallenge';
import GreenlockChallengeService from 'CommonServer/Services/GreenlockChallengeService';
import logger from 'CommonServer/Utils/Logger';
// because greenlock package expects module.exports.
module.exports = {
create: (_opts: any) => {
return {
init: async (): Promise<null> => {
logger.info('Greenlock HTTP Challenge Init');
return Promise.resolve(null);
},
set: async (data: any): Promise<null> => {
logger.info('Greenlock HTTP Challenge Set');
logger.info(data);
const ch: any = data.challenge;
const key: string = ch.identifier.value + '#' + ch.token;
const token: string = ch.token;
let challenge: GreenlockChallenge | null =
await GreenlockChallengeService.findOneBy({
query: {
key: key,
},
select: {
_id: true,
},
props: {
isRoot: true,
ignoreHooks: true,
},
});
if (!challenge) {
challenge = new GreenlockChallenge();
challenge.key = key;
challenge.token = token;
challenge.challenge = ch.keyAuthorization;
await GreenlockChallengeService.create({
data: challenge,
props: {
isRoot: true,
},
});
} else {
challenge.challenge = ch.keyAuthorization;
challenge.token = token;
await GreenlockChallengeService.updateOneById({
id: challenge.id!,
data: challenge,
props: {
isRoot: true,
},
});
}
//
return null;
},
get: async (data: any): Promise<null | any> => {
logger.info('Greenlock HTTP Challenge Get');
logger.info(data);
const ch: any = data.challenge;
const key: string = ch.identifier.value + '#' + ch.token;
const challenge: GreenlockChallenge | null =
await GreenlockChallengeService.findOneBy({
query: {
key: key,
},
select: {
_id: true,
},
props: {
isRoot: true,
ignoreHooks: true,
},
});
if (!challenge) {
return null;
}
return { keyAuthorization: challenge.challenge };
},
remove: async (data: any): Promise<null> => {
logger.info('Greenlock HTTP Challenge Remove');
logger.info(data);
const ch: any = data.challenge;
const key: string = ch.identifier.value + '#' + ch.token;
await GreenlockChallengeService.deleteOneBy({
query: {
key: key,
},
props: {
isRoot: true,
ignoreHooks: true,
},
});
return null;
},
};
},
};

View File

@@ -1,190 +0,0 @@
// Docs: https://git.rootprojects.org/root/greenlock-manager.js
import StatusPageDomainService from 'CommonServer/Services/StatusPageDomainService';
import StatusPageDomain from 'Model/Models/StatusPageDomain';
import logger from 'CommonServer/Utils/Logger';
import GreenlockCertificate from 'Model/Models/GreenlockCertificate';
import GreenlockCertificateService from 'CommonServer/Services/GreenlockCertificateService';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import { JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
// because greenlock package expects module.exports.
module.exports = {
create: () => {
return {
// Get
get: async ({
servername,
}: {
servername: string;
}): Promise<JSONObject | undefined> => {
// Required: find the certificate with the subject of `servername`
// Optional (multi-domain certs support): find a certificate with `servername` as an altname
// Optional (wildcard support): find a certificate with `wildname` as an altname
// { subject, altnames, renewAt, deletedAt, challenges, ... }
logger.info('Greenlock Manager Get');
logger.info(servername);
const domain: StatusPageDomain | null =
await StatusPageDomainService.findOneBy({
query: {
fullDomain: servername,
},
props: {
isRoot: true,
ignoreHooks: true,
},
select: {
_id: true,
greenlockConfig: true,
},
});
if (!domain || !domain.greenlockConfig) {
logger.info(
'Greenlock Manager GET ' +
servername +
' - No domain found.'
);
return undefined;
}
logger.info('Greenlock Manager GET ' + servername + ' RESULT');
logger.info(domain.greenlockConfig);
return domain.greenlockConfig;
},
find: async function (args: any) {
logger.info('Manager Find: ');
logger.info(JSON.stringify(args, null, 2));
// { subject, servernames, altnames, renewBefore }
// i.e. find certs more than 30 days old
args.issuedBefore = Date.now() - 30 * 24 * 60 * 60 * 1000;
// i.e. find certs more that will expire in less than 45 days
//args.expiresBefore = Date.now() + 45 * 24 * 60 * 60 * 1000;
const issuedBefore: any = args.issuedBefore || Infinity;
const expiresBefore: any = args.expiresBefore || Infinity; //Date.now() + 21 * 24 * 60 * 60 * 1000;
const renewBefore: any = args.renewBefore || Infinity; //Date.now() + 21 * 24 * 60 * 60 * 1000;
// if there's anything to match, only return matches
// if there's nothing to match, return everything
const nameKeys: Array<string> = ['subject', 'altnames'];
const matchAll: boolean = !nameKeys.some((k: string) => {
return k in args;
});
const querynames: string = (args.altnames || []).slice(0);
const greenlockCertificates: Array<GreenlockCertificate> =
await GreenlockCertificateService.findBy({
query: {
isKeyPair: false,
},
limit: LIMIT_MAX,
skip: 0,
select: {
blob: true,
key: true,
},
props: {
isRoot: true,
},
});
const sites: Array<JSONObject> = greenlockCertificates
.filter((i: GreenlockCertificate) => {
return i.blob;
})
.map((i: GreenlockCertificate) => {
return JSONFunctions.parseJSONObject(i.blob!);
})
.filter((site: any) => {
logger.info('Filter Site: ');
logger.info(site);
if (site.deletedAt) {
logger.info('Filter Site: DeletedAt');
return false;
}
if (site.expiresAt >= expiresBefore) {
logger.info('Filter Site: expiresAt');
return false;
}
if (site.issuedAt >= issuedBefore) {
logger.info('Filter Site: issuedAt');
return false;
}
if (site.renewAt >= renewBefore) {
logger.info('Filter Site: renewAt');
return false;
}
// after attribute filtering, before cert filtering
if (matchAll) {
logger.info('Filter Site: MatchAll');
return true;
}
// if subject is specified, don't return anything else
if (site.subject === args.subject) {
logger.info('Filter Site: Subject');
return true;
}
// altnames, servername, and wildname all get rolled into one
return site.altnames.some((altname: any) => {
return querynames.includes(altname);
});
});
logger.info('Sites: ');
logger.info(sites);
return sites;
},
// Set
set: async (opts: any) => {
logger.info('Greenlock Manager Set');
logger.info(opts);
// { subject, altnames, renewAt, deletedAt }
// Required: updated `renewAt` and `deletedAt` for certificate matching `subject`
if (!opts.subject) {
return;
}
const domain: StatusPageDomain | null =
await StatusPageDomainService.findOneBy({
query: {
fullDomain: opts['subject'] as string,
},
props: {
isRoot: true,
},
select: {
_id: true,
greenlockConfig: true,
},
});
if (!domain) {
return;
}
await StatusPageDomainService.updateOneById({
id: domain.id!,
data: {
greenlockConfig: opts,
},
props: {
isRoot: true,
ignoreHooks: true,
},
});
},
};
},
};

View File

@@ -1,220 +0,0 @@
/// https://git.rootprojects.org/root/greenlock-store-memory.js/src/branch/master/index.js
import GreenlockCertificate from 'Model/Models/GreenlockCertificate';
import GreenlockCertificateService from 'CommonServer/Services/GreenlockCertificateService';
import logger from 'CommonServer/Utils/Logger';
import JSONFunctions from 'Common/Types/JSONFunctions';
type SaveCertificateFunction = (
id: string,
blob: string,
isKeyPair: boolean
) => Promise<null>;
type GetCertificateFunction = (
id: string,
isKeyPair: boolean
) => Promise<null | string>;
module.exports = {
create: (_opts: any) => {
const saveCertificate: SaveCertificateFunction = async (
id: string,
blob: string,
isKeyPair: boolean
): Promise<null> => {
logger.info('Save Certificates: ' + id);
let cert: GreenlockCertificate | null =
await GreenlockCertificateService.findOneBy({
query: {
key: id,
isKeyPair: isKeyPair,
},
select: {
_id: true,
isKeyPair: isKeyPair,
},
props: {
isRoot: true,
ignoreHooks: true,
},
});
if (!cert) {
cert = new GreenlockCertificate();
cert.key = id;
cert.blob = blob;
cert.isKeyPair = isKeyPair;
await GreenlockCertificateService.create({
data: cert,
props: {
isRoot: true,
},
});
} else {
cert.blob = blob;
cert.isKeyPair = isKeyPair;
await GreenlockCertificateService.updateOneById({
id: cert.id!,
data: cert,
props: {
isRoot: true,
},
});
}
//
return null;
};
const getCertificate: GetCertificateFunction = async (
id: string,
isKeyPair: boolean
): Promise<null | string> => {
logger.info('Get Certificate - ' + id);
const cert: GreenlockCertificate | null =
await GreenlockCertificateService.findOneBy({
query: {
key: id,
isKeyPair: isKeyPair,
},
select: {
_id: true,
blob: true,
},
props: {
isRoot: true,
ignoreHooks: true,
},
});
if (!cert || !cert.blob) {
logger.info('Certificate not found');
return null;
}
logger.info('Certificate found');
return cert.blob;
};
type SaveKeyPairFunction = (id: string, blob: string) => Promise<null>;
const saveKeypair: SaveKeyPairFunction = async (
id: string,
blob: string
): Promise<null> => {
logger.info('Save Keypair: ' + id);
return await saveCertificate(id, blob, true);
};
type GetKeypairFunction = (id: string) => Promise<null | string>;
const getKeypair: GetKeypairFunction = async (
id: string
): Promise<null | string> => {
logger.info('Get Keypair: ' + id);
return await getCertificate(id, true);
};
return {
accounts: {
// Whenever a new keypair is used to successfully create an account, we need to save its keypair
setKeypair: async (opts: any): Promise<null> => {
logger.info('Accounts Set Keypair: ');
logger.info(JSON.stringify(opts, null, 2));
const id: string =
opts.account.id || opts.email || 'default';
const keypair: any = opts.keypair;
return await saveKeypair(id, JSON.stringify(keypair)); // Must return or Promise `null` instead of `undefined`
},
// We need a way to retrieve a prior account's keypair for renewals and additional ACME certificate "orders"
checkKeypair: async (opts: any): Promise<any | null> => {
logger.info('Accounts Check Keypair: ');
logger.info(JSON.stringify(opts, null, 2));
const id: string =
opts.account.id || opts.email || 'default';
const keyblob: any = await getKeypair(id);
if (!keyblob) {
return null;
}
return JSONFunctions.parse(keyblob);
},
},
certificates: {
setKeypair: async (opts: any): Promise<null> => {
logger.info('Certificates Set Keypair: ');
logger.info(JSON.stringify(opts, null, 2));
// The ID is a string that doesn't clash between accounts and certificates.
// That's all you need to know... unless you're doing something special (in which case you're on your own).
const id: string =
opts.certificate.kid ||
opts.certificate.id ||
opts.subject;
const keypair: any = opts.keypair;
return await saveKeypair(id, JSON.stringify(keypair)); // Must return or Promise `null` instead of `undefined`
// Side Note: you can use the "keypairs" package to convert between
// public and private for jwk and pem, as well as convert JWK <-> PEM
},
// You won't be able to use a certificate without it's private key, gotta save it
checkKeypair: async (opts: any): Promise<any | null> => {
logger.info('Certificates Check Keypair: ');
logger.info(JSON.stringify(opts, null, 2));
const id: string =
opts.certificate.kid ||
opts.certificate.id ||
opts.subject;
const keyblob: any = await getKeypair(id);
if (!keyblob) {
return null;
}
return JSONFunctions.parse(keyblob);
},
// And you'll also need to save certificates. You may find the metadata useful to save
// (perhaps to delete expired keys), but the same information can also be redireved from
// the key using the "cert-info" package.
set: async (opts: any): Promise<null> => {
logger.info('Certificates Set: ');
logger.info(JSON.stringify(opts, null, 2));
const id: string = opts.certificate.id || opts.subject;
const pems: any = opts.pems;
return await saveCertificate(
id,
JSON.stringify(pems),
false
); // Must return or Promise `null` instead of `undefined`
},
// This is actually the first thing to be called after approveDomins(),
// but it's easiest to implement last since it's not useful until there
// are certs that can actually be loaded from storage.
check: async (opts: any): Promise<null | any> => {
logger.info('Certificates Check: ');
logger.info(JSON.stringify(opts, null, 2));
const id: string = opts.certificate?.id || opts.subject;
const certblob: any = await getCertificate(id, false);
if (!certblob) {
return null;
}
return JSONFunctions.parse(certblob);
},
},
options: {},
};
},
};

View File

@@ -13,11 +13,14 @@ export default class ComponentCodeAPI {
public constructor() {
this.router = Express.getRouter();
}
public init(): void {
// init all component code.
/// Get all the components.
for (const key in Components) {
const ComponentCode: ComponentCode | undefined = Components[key];
if (ComponentCode instanceof TriggerCode) {
const instance: TriggerCode = ComponentCode;
instance

View File

@@ -5,7 +5,7 @@ import Express, {
} from 'CommonServer/Utils/Express';
import logger from 'CommonServer/Utils/Logger';
import ManualAPI from './API/Manual';
import ComponentCode from './API/ComponentCode';
import ComponentCodeAPI from './API/ComponentCode';
import { QueueJob, QueueName } from 'CommonServer/Infrastructure/Queue';
import QueueWorker from 'CommonServer/Infrastructure/QueueWorker';
import RunWorkflow from './Services/RunWorkflow';
@@ -16,27 +16,30 @@ import FeatureSet from 'CommonServer/Types/FeatureSet';
const APP_NAME: string = 'api/workflow';
const app: ExpressApplication = Express.getExpressApp();
app.use(`/${APP_NAME}/manual`, new ManualAPI().router);
app.use(`/${APP_NAME}`, new WorkflowAPI().router);
app.get(
`/${APP_NAME}/docs/:componentName`,
(req: ExpressRequest, res: ExpressResponse) => {
res.sendFile(
'/usr/src/app/FeatureSet/Workflow/Docs/ComponentDocumentation/' +
req.params['componentName']
);
}
);
app.use(`/${APP_NAME}`, new ComponentCode().router);
const WorkflowFeatureSet: FeatureSet = {
init: async (): Promise<void> => {
try {
const app: ExpressApplication = Express.getExpressApp();
app.use(`/${APP_NAME}/manual`, new ManualAPI().router);
app.use(`/${APP_NAME}`, new WorkflowAPI().router);
app.get(
`/${APP_NAME}/docs/:componentName`,
(req: ExpressRequest, res: ExpressResponse) => {
res.sendFile(
'/usr/src/app/FeatureSet/Workflow/Docs/ComponentDocumentation/' +
req.params['componentName']
);
}
);
const componentCodeAPI: ComponentCodeAPI = new ComponentCodeAPI();
componentCodeAPI.init();
app.use(`/${APP_NAME}`, componentCodeAPI.router);
// Job process.
QueueWorker.getWorker(
QueueName.Workflow,

View File

@@ -9,23 +9,37 @@ import { PostgresAppInstance } from 'CommonServer/Infrastructure/PostgresDatabas
import { ClickhouseAppInstance } from 'CommonServer/Infrastructure/ClickhouseDatabase';
import Realtime from 'CommonServer/Utils/Realtime';
// import featuresets.
import './FeatureSet/Identity/Index';
import './FeatureSet/Notification/Index';
import './FeatureSet/Docs/Index';
import './FeatureSet/BaseAPI/Index';
import './FeatureSet/ApiReference/Index';
// import FeatureSets.
import IdentityRoutes from './FeatureSet/Identity/Index';
import NotificationRoutes from './FeatureSet/Notification/Index';
import DocsRoutes from './FeatureSet/Docs/Index';
import BaseAPIRoutes from './FeatureSet/BaseAPI/Index';
import APIReferenceRoutes from './FeatureSet/ApiReference/Index';
import Workers from './FeatureSet/Workers/Index';
import Workflow from './FeatureSet/Workflow/Index';
import HomeRoutes from './FeatureSet/Home/Index';
import InfrastructureStatus from 'CommonServer/Infrastructure/Status';
// home should be in the end.
import './FeatureSet/Home/Index';
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
const init: PromiseVoidFunction = async (): Promise<void> => {
try {
const statusCheck: PromiseVoidFunction = async (): Promise<void> => {
return await InfrastructureStatus.checkStatus({
checkClickhouseStatus: true,
checkPostgresStatus: true,
checkRedisStatus: true,
});
};
// init the app
await App(process.env['SERVICE_NAME'] || 'app');
await App.init({
appName: process.env['SERVICE_NAME'] || 'app',
statusOptions: {
liveCheck: statusCheck,
readyCheck: statusCheck,
},
});
// connect to the database.
await PostgresAppInstance.connect(
@@ -39,13 +53,22 @@ const init: PromiseVoidFunction = async (): Promise<void> => {
ClickhouseAppInstance.getDatasourceOptions()
);
Realtime.init();
await Realtime.init();
// init workers
// init featuresets
await IdentityRoutes.init();
await NotificationRoutes.init();
await DocsRoutes.init();
await BaseAPIRoutes.init();
await APIReferenceRoutes.init();
await Workers.init();
// init workflow
await Workflow.init();
// home should be in the end because it has the catch all route.
await HomeRoutes.init();
// add default routes
await App.addDefaultRoutes();
} catch (err) {
logger.error('App Init Failed:');
logger.error(err);

1
App/greenlock/README.md Normal file
View File

@@ -0,0 +1 @@
# This directory is for the .greenlockrc file

231
App/package-lock.json generated
View File

@@ -1,19 +1,18 @@
{
"name": "app",
"name": "@oneuptime/app",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "app",
"name": "@oneuptime/app",
"version": "1.0.0",
"license": "ISC",
"license": "Apache-2.0",
"dependencies": {
"@sendgrid/mail": "^8.1.0",
"Common": "file:../Common",
"CommonServer": "file:../CommonServer",
"ejs": "^3.1.9",
"greenlock": "^4.0.4",
"handlebars": "^4.7.8",
"marked": "^12.0.0",
"Model": "file:../Model",
@@ -36,19 +35,19 @@
}
},
"../Common": {
"name": "common",
"name": "@oneuptime/common",
"version": "1.0.0",
"license": "MIT",
"license": "Apache-2.0",
"dependencies": {
"@types/crypto-js": "^4.2.2",
"@types/uuid": "^8.3.4",
"axios": "^1.6.7",
"axios": "^1.6.8",
"crypto-js": "^4.1.1",
"json5": "^2.2.3",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"posthog-js": "^1.105.4",
"reflect-metadata": "^0.2.1",
"posthog-js": "^1.116.6",
"reflect-metadata": "^0.2.2",
"slugify": "^1.6.5",
"typeorm": "^0.3.20",
"uuid": "^8.3.2"
@@ -62,49 +61,46 @@
}
},
"../CommonServer": {
"name": "common-server",
"name": "@oneuptime/common-server",
"version": "1.0.0",
"license": "MIT",
"license": "Apache-2.0",
"dependencies": {
"@clickhouse/client": "^0.2.9",
"@elastic/elasticsearch": "^8.11.0",
"@clickhouse/client": "^0.2.10",
"@elastic/elasticsearch": "^8.12.1",
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/api-logs": "^0.48.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.48.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.48.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.48.0",
"@opentelemetry/api-logs": "^0.49.1",
"@opentelemetry/auto-instrumentations-node": "^0.43.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.49.1",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.49.1",
"@opentelemetry/exporter-trace-otlp-proto": "^0.49.1",
"@opentelemetry/id-generator-aws-xray": "^1.2.1",
"@opentelemetry/instrumentation-express": "^0.35.0",
"@opentelemetry/instrumentation-http": "^0.48.0",
"@opentelemetry/sdk-logs": "^0.48.0",
"@opentelemetry/sdk-logs": "^0.49.1",
"@opentelemetry/sdk-metrics": "^1.21.0",
"@opentelemetry/sdk-node": "^0.48.0",
"@opentelemetry/sdk-trace-node": "^1.21.0",
"@socket.io/redis-adapter": "^8.2.1",
"acme-client": "^5.3.0",
"airtable": "^0.12.2",
"axios": "^1.6.7",
"bullmq": "^5.3.3",
"Common": "file:../Common",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"cron-parser": "^4.8.1",
"dotenv": "^16.4.1",
"ejs": "^3.1.8",
"express": "^4.17.3",
"dotenv": "^16.4.4",
"ejs": "^3.1.10",
"express": "^4.19.2",
"ioredis": "^5.3.2",
"json2csv": "^5.0.7",
"jsonwebtoken": "^9.0.0",
"markdown-it": "^13.0.1",
"Model": "file:../Model",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.9",
"nodemailer": "^6.9.10",
"pg": "^8.7.3",
"socket.io": "^4.7.4",
"stripe": "^10.17.0",
"twilio": "^4.21.0",
"twilio": "^4.22.0",
"typeorm": "^0.3.20",
"typeorm-extension": "^2.2.13",
"vm2": "^3.9.14"
"typeorm-extension": "^2.2.13"
},
"devDependencies": {
"@faker-js/faker": "^6.3.1",
@@ -125,9 +121,9 @@
}
},
"../Model": {
"name": "model",
"name": "@oneuptime/model",
"version": "1.0.0",
"license": "ISC",
"license": "Apache-2.0",
"dependencies": {
"Common": "file:../Common",
"typeorm": "^0.3.20"
@@ -785,14 +781,6 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@greenlock/manager": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@greenlock/manager/-/manager-3.1.0.tgz",
"integrity": "sha512-PBy5CMK+j4oD7sj7hF5qE+xKEOSiiuL2hHd5X5ttEbtnTSDKjNeqbrR5k2ZddwVNdjOVeBIeuqlm81IFZ+Ftew==",
"dependencies": {
"greenlock-manager-fs": "^3.1.0"
}
},
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -1187,117 +1175,6 @@
"node": ">=8.0"
}
},
"node_modules/@root/acme": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.1.0.tgz",
"integrity": "sha512-GAyaW63cpSYd2KvVp5lHLbCWeEhJPKZK9nsJvZJOKsD9Uv88KEttn4FpDZEJ+2q3Jsey0DWpuQ2I4ft0JV9p2w==",
"hasInstallScript": true,
"dependencies": {
"@root/csr": "^0.8.1",
"@root/encoding": "^1.0.1",
"@root/keypairs": "^0.10.0",
"@root/pem": "^1.0.4",
"@root/request": "^1.6.1",
"@root/x509": "^0.7.2"
}
},
"node_modules/@root/acme/node_modules/@root/keypairs": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.10.3.tgz",
"integrity": "sha512-hbmjVbk/G99Z1XlUzJM4VOAaR8jm4vMnfwpZL301FA24ubJ/bOjj6enCrz9gKsQvBO6RY4/R4MgUuWpXeqeZZQ==",
"dependencies": {
"@root/encoding": "^1.0.1",
"@root/pem": "^1.0.4",
"@root/x509": "^0.7.2"
}
},
"node_modules/@root/asn1": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@root/asn1/-/asn1-1.0.0.tgz",
"integrity": "sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig==",
"dependencies": {
"@root/encoding": "^1.0.1"
}
},
"node_modules/@root/csr": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz",
"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==",
"dependencies": {
"@root/asn1": "^1.0.0",
"@root/pem": "^1.0.4",
"@root/x509": "^0.7.2"
}
},
"node_modules/@root/encoding": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz",
"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ=="
},
"node_modules/@root/greenlock": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@root/greenlock/-/greenlock-4.0.5.tgz",
"integrity": "sha512-KR9w3mYE9aH33FCibI8oSYBQV+f7lc3MVPdZ9nxY2tqRLmJp05cMOMz340mtG14VnWDuznLj4TbBj3sHIuoQPQ==",
"dependencies": {
"@greenlock/manager": "^3.1.0",
"@root/acme": "^3.1.0",
"@root/csr": "^0.8.1",
"@root/keypairs": "^0.10.0",
"@root/mkdirp": "^1.0.0",
"@root/request": "^1.6.1",
"acme-http-01-standalone": "^3.0.5",
"cert-info": "^1.5.1",
"greenlock-store-fs": "^3.2.2",
"safe-replace": "^1.1.0"
},
"bin": {
"greenlock": "bin/greenlock.js"
}
},
"node_modules/@root/greenlock/node_modules/@root/keypairs": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.10.3.tgz",
"integrity": "sha512-hbmjVbk/G99Z1XlUzJM4VOAaR8jm4vMnfwpZL301FA24ubJ/bOjj6enCrz9gKsQvBO6RY4/R4MgUuWpXeqeZZQ==",
"dependencies": {
"@root/encoding": "^1.0.1",
"@root/pem": "^1.0.4",
"@root/x509": "^0.7.2"
}
},
"node_modules/@root/keypairs": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz",
"integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==",
"dependencies": {
"@root/encoding": "^1.0.1",
"@root/pem": "^1.0.4",
"@root/x509": "^0.7.2"
}
},
"node_modules/@root/mkdirp": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz",
"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA=="
},
"node_modules/@root/pem": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.4.tgz",
"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA=="
},
"node_modules/@root/request": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.9.2.tgz",
"integrity": "sha512-wVaL9yVV9oDR9UNbPZa20qgY+4Ch6YN8JUkaE4el/uuS5dmhD8Lusm/ku8qJVNtmQA56XLzEDCRS6/vfpiHK2A=="
},
"node_modules/@root/x509": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/@root/x509/-/x509-0.7.2.tgz",
"integrity": "sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ==",
"dependencies": {
"@root/asn1": "^1.0.0",
"@root/encoding": "^1.0.1"
}
},
"node_modules/@sendgrid/client": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.0.tgz",
@@ -1705,11 +1582,6 @@
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"node_modules/acme-http-01-standalone": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/acme-http-01-standalone/-/acme-http-01-standalone-3.0.5.tgz",
"integrity": "sha512-W4GfK+39GZ+u0mvxRVUcVFCG6gposfzEnSBF20T/NUwWAKG59wQT1dUbS1NixRIAsRuhpGc4Jx659cErFQH0Pg=="
},
"node_modules/acorn": {
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
@@ -2053,14 +1925,6 @@
}
]
},
"node_modules/cert-info": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz",
"integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==",
"bin": {
"cert-info": "bin/cert-info.js"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -2681,42 +2545,6 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"node_modules/greenlock": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/greenlock/-/greenlock-4.0.4.tgz",
"integrity": "sha512-f96lHXk0DIZCyem4IpMea/ME4LnWUHHSJr79r6UxF//gBRFy9toSq7ndiSO9hL1A2WDZSv+j28kFcbf+/az9Ew==",
"dependencies": {
"@greenlock/manager": "^3.1.0",
"@root/acme": "^3.0.9",
"@root/csr": "^0.8.1",
"@root/greenlock": "^4.0.4",
"@root/keypairs": "^0.9.0",
"@root/mkdirp": "^1.0.0",
"@root/request": "^1.4.2",
"acme-http-01-standalone": "^3.0.5",
"cert-info": "^1.5.1",
"greenlock-store-fs": "^3.2.2",
"safe-replace": "^1.1.0"
}
},
"node_modules/greenlock-manager-fs": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.1.1.tgz",
"integrity": "sha512-np6qdnPIOZx40PAcSQcqK1eMPWjTKxsxcgRd/OVg0ai49WC1Ds74CTrwmB84pq2n53ikbnDBQFmKEQ4AC0DK8w==",
"dependencies": {
"@root/mkdirp": "^1.0.0",
"safe-replace": "^1.1.0"
}
},
"node_modules/greenlock-store-fs": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.2.2.tgz",
"integrity": "sha512-92ejLB4DyV4qv/2b6VLGF2nKfYQeIfg3o+e/1cIoYLjlIaUFdbBXkzLTRozFlHsQPZt2ALi5qYrpC9IwH7GK8A==",
"dependencies": {
"@root/mkdirp": "^1.0.0",
"safe-replace": "^1.1.0"
}
},
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
@@ -4469,11 +4297,6 @@
}
]
},
"node_modules/safe-replace": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz",
"integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw=="
},
"node_modules/sax": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",

View File

@@ -20,7 +20,6 @@
"Common": "file:../Common",
"CommonServer": "file:../CommonServer",
"ejs": "^3.1.9",
"greenlock": "^4.0.4",
"handlebars": "^4.7.8",
"marked": "^12.0.0",
"Model": "file:../Model",

View File

@@ -18,6 +18,7 @@ import Dictionary from '../Types/Dictionary';
import ModelPermission from '../Types/BaseDatabase/ModelPermission';
import Permission, { UserTenantAccessPermission } from '../Types/Permission';
import { PlanSelect } from '../Types/Billing/SubscriptionPlan';
import { JSONValue } from '../Types/JSON';
export type AnalyticsBaseModelType = { new (): AnalyticsBaseModel };
@@ -259,6 +260,17 @@ export default class AnalyticsBaseModel extends CommonModel {
return column.isDefaultValueColumn;
}
public getDefaultValueForColumn(columnName: string): JSONValue {
const column: AnalyticsTableColumn | null =
this.getTableColumn(columnName);
if (!column) {
throw new BadDataException('Column ' + columnName + ' not found');
}
return column.defaultValue;
}
public getColumnBillingAccessControl(
columnName: string
): ColumnBillingAccessControl | null {

View File

@@ -78,12 +78,27 @@ export default class CommonModel {
typeof value === 'string'
) {
try {
value = JSON.parse(value);
value = JSONFunctions.parse(value);
} catch (e) {
value = {};
}
}
if (
column.type === TableColumnType.JSONArray &&
typeof value === 'string'
) {
try {
value = JSONFunctions.parseJSONArray(value);
if (!Array.isArray(value)) {
throw new BadDataException('Not an array');
}
} catch (e) {
value = [];
}
}
if (
column.type === TableColumnType.Number &&
typeof value === 'string'
@@ -204,6 +219,8 @@ export default class CommonModel {
recordValue as Array<CommonModel>,
column.nestedModelType
);
} else {
json[column.key] = recordValue;
}
return;
@@ -221,16 +238,16 @@ export default class CommonModel {
): Array<TBaseModel> {
const models: Array<CommonModel> = [];
jsonArray.forEach((json: JSONObject | CommonModel) => {
for (const json of jsonArray) {
if (json instanceof CommonModel) {
models.push(json);
return;
continue;
}
const model: CommonModel = new modelType();
model.fromJSON(json);
models.push(model);
});
}
return models as Array<TBaseModel>;
}

View File

@@ -371,6 +371,10 @@ export default class BaseModel extends BaseEntity {
}
}
public isTableColumn(columnName: string): boolean {
return Boolean(getTableColumn(this, columnName));
}
public isEntityColumn(columnName: string): boolean {
const tableColumnType: TableColumnMetadata = getTableColumn(
this,

View File

@@ -2,20 +2,20 @@ import Color from '../../Types/Color';
import {
Black,
White,
slate,
Purple,
Pink,
Red,
Orange,
Yellow,
Green,
Teal,
Cyan,
VeryLightGrey,
Grey,
LightGrey,
Moroon,
Blue,
Slate500,
Purple500,
Pink500,
Red500,
Orange500,
Yellow500,
Green500,
Teal500,
Cyan500,
VeryLightGray,
Gray500,
LightGray,
Moroon500,
Blue500,
} from '../../Types/BrandColors';
describe('Color', () => {
@@ -41,100 +41,100 @@ describe('Color', () => {
});
describe('slate', () => {
it('should be an instance with the hex code of slate', () => {
const color: Color = slate;
expect(slate).toBe(color);
expect(slate.color).toBe('#564ab1');
const color: Color = Slate500;
expect(Slate500).toBe(color);
expect(Slate500.color).toBe('#64748b');
});
});
describe('Purple', () => {
it('should be an instance with the hex code of Purple', () => {
const color: Color = Purple;
expect(Purple).toBe(color);
expect(Purple.color).toBe('#6f42c1');
describe('Purple500', () => {
it('should be an instance with the hex code of Purple500', () => {
const color: Color = Purple500;
expect(Purple500).toBe(color);
expect(Purple500.color).toBe('#a855f7');
});
});
describe('Pink', () => {
it('should be an instance with the hex code of Pink', () => {
const color: Color = Pink;
expect(Pink).toBe(color);
expect(Pink.color).toBe('#e83e8c');
describe('Pink500', () => {
it('should be an instance with the hex code of Pink500', () => {
const color: Color = Pink500;
expect(Pink500).toBe(color);
expect(Pink500.color).toBe('#ec4899');
});
});
describe('Red', () => {
it('should be an instance with the hex code of Red', () => {
const color: Color = Red;
expect(Red).toBe(color);
expect(Red.color).toBe('#fd625e');
describe('Red500', () => {
it('should be an instance with the hex code of Red500', () => {
const color: Color = Red500;
expect(Red500).toBe(color);
expect(Red500.color).toBe('#ef4444');
});
});
describe('Orange', () => {
it('should be an instance with the hex code of Orange', () => {
const color: Color = Orange;
expect(Orange).toBe(color);
expect(Orange.color).toBe('#f1734f');
describe('Orange500', () => {
it('should be an instance with the hex code of Orange500', () => {
const color: Color = Orange500;
expect(Orange500).toBe(color);
expect(Orange500.color).toBe('#f97316');
});
});
describe('Yellow', () => {
it('should be an instance with the hex code of Yellow', () => {
const color: Color = Yellow;
expect(Yellow).toBe(color);
expect(Yellow.color).toBe('#ffbf53');
describe('Yellow500', () => {
it('should be an instance with the hex code of Yellow500', () => {
const color: Color = Yellow500;
expect(Yellow500).toBe(color);
expect(Yellow500.color).toBe('#ffbf53');
});
});
describe('Green', () => {
it('should be an instance with the hex code of Green', () => {
const color: Color = Green;
expect(Green).toBe(color);
expect(Green.color).toBe('#2ab57d');
describe('Green500', () => {
it('should be an instance with the hex code of Green500', () => {
const color: Color = Green500;
expect(Green500).toBe(color);
expect(Green500.color).toBe('#22c55e');
});
});
describe('Teal', () => {
it('should be an instance with the hex code of Teal', () => {
const color: Color = Teal;
expect(Teal).toBe(color);
expect(Teal.color).toBe('#050505');
describe('Teal500', () => {
it('should be an instance with the hex code of Teal500', () => {
const color: Color = Teal500;
expect(Teal500).toBe(color);
expect(Teal500.color).toBe('#14b8a6');
});
});
describe('Cyan', () => {
it('should be an instance with the hex code of Cyan', () => {
const color: Color = Cyan;
expect(Cyan).toBe(color);
expect(Cyan.color).toBe('#4ba6ef');
describe('Cyan500', () => {
it('should be an instance with the hex code of Cyan500', () => {
const color: Color = Cyan500;
expect(Cyan500).toBe(color);
expect(Cyan500.color).toBe('#06b6d4');
});
});
describe('VeryLightGrey', () => {
it('should be an instance with the hex code of VeryLightGrey', () => {
const color: Color = VeryLightGrey;
expect(VeryLightGrey).toBe(color);
expect(VeryLightGrey.color).toBe('#c2c2c2');
describe('VeryLightGray', () => {
it('should be an instance with the hex code of VeryLightGray', () => {
const color: Color = VeryLightGray;
expect(VeryLightGray).toBe(color);
expect(VeryLightGray.color).toBe('#c2c2c2');
});
});
describe('Grey', () => {
it('should be an instance with the hex code of Grey', () => {
const color: Color = Grey;
expect(Grey).toBe(color);
expect(Grey.color).toBe('#575757');
describe('Gray500', () => {
it('should be an instance with the hex code of Gray500', () => {
const color: Color = Gray500;
expect(Gray500).toBe(color);
expect(Gray500.color).toBe('#6b7280');
});
});
describe('LightGrey', () => {
it('should be an instance with the hex code of LightGrey', () => {
const color: Color = LightGrey;
expect(LightGrey).toBe(color);
expect(LightGrey.color).toBe('#908B8B');
describe('LightGray', () => {
it('should be an instance with the hex code of LightGray', () => {
const color: Color = LightGray;
expect(LightGray).toBe(color);
expect(LightGray.color).toBe('#908B8B');
});
});
describe('Moroon', () => {
it('should be an instance with the hex code of Moroon', () => {
const color: Color = Moroon;
expect(Moroon).toBe(color);
expect(Moroon.color).toBe('#b70400');
describe('Moroon500', () => {
it('should be an instance with the hex code of Moroon500', () => {
const color: Color = Moroon500;
expect(Moroon500).toBe(color);
expect(Moroon500.color).toBe('#b70400');
});
});
describe('Blue', () => {
it('should be an instance with the hex code of Blue', () => {
const color: Color = Blue;
expect(Blue).toBe(color);
expect(Blue.color).toBe('#3686be');
describe('Blue500', () => {
it('should be an instance with the hex code of Blue500', () => {
const color: Color = Blue500;
expect(Blue500).toBe(color);
expect(Blue500.color).toBe('#3b82f6');
});
});
});

View File

@@ -8,6 +8,6 @@ describe('DatabaseNotConnectedException', () => {
});
test('should return 3 as the code for DatabaseNotConnectedException', () => {
expect(new DatabaseNotConnectedException().code).toBe(3);
expect(new DatabaseNotConnectedException().code).toBe(500);
});
});

View File

@@ -23,6 +23,8 @@ export default class Route extends DatabaseProperty {
route = route.toString();
}
route = route?.replace(/\/+/g, '/'); // remove multiple slashes from route and replace with single slash
if (route) {
this.route = route;
}

View File

@@ -6,6 +6,7 @@ enum ColumnType {
Text = 'Text',
NestedModel = 'Nested Model',
JSON = 'JSON',
JSONArray = 'JSON Array',
Decimal = 'Decimal',
ArrayNumber = 'Array of Numbers',
ArrayText = 'Array of Text',

View File

@@ -33,6 +33,10 @@ export default class ArrayUtil {
};
}
public static selectItemByRandom<T>(array: Array<T>): T {
return array[Math.floor(Math.random() * array.length)]!;
}
public static distinctByFieldName(
array: Array<any>,
fieldName: string

View File

@@ -1,5 +1,6 @@
import Color from './Color';
// Standard Colors
export const Black: Color = Color.fromString('#000000');
export const White: Color = Color.fromString('#ffffff');
export const slate: Color = Color.fromString('#564ab1');
@@ -11,37 +12,56 @@ export const Yellow: Color = Color.fromString('#ffbf53');
export const Green: Color = Color.fromString('#2ab57d');
export const Teal: Color = Color.fromString('#050505');
export const Cyan: Color = Color.fromString('#4ba6ef');
export const VeryLightGrey: Color = Color.fromString('#c2c2c2');
export const VeryLightGray: Color = Color.fromString('#c2c2c2');
export const Grey: Color = Color.fromString('#575757');
export const LightGrey: Color = Color.fromString('#908B8B');
export const LightGray: Color = Color.fromString('#908B8B');
export const Moroon: Color = Color.fromString('#b70400');
export const Blue: Color = Color.fromString('#3686be');
// these are *-500 colors from tailwindcss
export const Zinc500: Color = Color.fromString('#71717a');
export const Neutra500l: Color = Color.fromString('#737373');
export const Stone500: Color = Color.fromString('#78716c');
export const Slate500: Color = Color.fromString('#64748b');
export const Purple500: Color = Color.fromString('#a855f7');
export const Pink500: Color = Color.fromString('#ec4899');
export const Red500: Color = Color.fromString('#ef4444');
export const Orange500: Color = Color.fromString('#f97316');
export const Yellow500: Color = Color.fromString('#ffbf53');
export const Green500: Color = Color.fromString('#22c55e');
export const Teal500: Color = Color.fromString('#14b8a6');
export const Cyan500: Color = Color.fromString('#06b6d4');
export const Gray500: Color = Color.fromString('#6b7280');
export const Moroon500: Color = Color.fromString('#b70400');
export const Blue500: Color = Color.fromString('#3b82f6');
export const Indigo500: Color = Color.fromString('#6366f1');
export const Amber500: Color = Color.fromString('#f59e0b');
export const Lime500: Color = Color.fromString('#84cc16');
export const Emerald500: Color = Color.fromString('#10b981');
export const Sky500: Color = Color.fromString('#0ea5e9');
export const Violet500: Color = Color.fromString('#8b5cf6');
export const Fuchsia500: Color = Color.fromString('#d946ef');
export const Rose500: Color = Color.fromString('#f43f5e');
export const ChartColors: Color[] = [
export const BrightColors: Color[] = [
Black,
Indigo500,
Green,
Red,
Cyan,
Pink,
Orange,
Purple,
Yellow,
];
export const EventColorList: Color[] = [
Color.fromString('#d50000'),
Color.fromString('#e67c73'),
Color.fromString('#f4511e'),
Color.fromString('#f6bf26'),
Color.fromString('#33b679'),
Color.fromString('#0b8043'),
Color.fromString('#039be5'),
Color.fromString('#3f51b5'),
Color.fromString('#65a30d'),
Color.fromString('#8e24aa'),
Color.fromString('#616161'),
Green500,
Red500,
Cyan500,
Pink500,
Orange500,
Purple500,
Yellow500,
Teal500,
Gray500,
Moroon500,
Blue500,
Rose500,
Fuchsia500,
Violet500,
Sky500,
Emerald500,
Lime500,
Amber500,
];

View File

@@ -849,6 +849,38 @@ export default class OneUptimeDate {
return minutes;
}
public static convertMinutesToDaysHoursAndMinutes(minutes: number): string {
// should output 2 days, 3 hours and 4 minutes. If the days are 0, it should not show the days. If the hours are 0, it should not show the hours. If the minutes are 0, it should not show the minutes.
const days: number = Math.floor(minutes / (24 * 60));
const hours: number = Math.floor((minutes % (24 * 60)) / 60);
const mins: number = minutes % 60;
let formattedString: string = '';
if (days > 0) {
formattedString += days + ' days';
}
if (hours > 0) {
if (formattedString.length > 0) {
formattedString += ', ';
}
formattedString += hours + ' hours';
}
if (mins >= 0) {
if (formattedString.length > 0) {
formattedString += ', ';
}
formattedString += mins + ' minutes';
}
return formattedString;
}
public static getDateAsFormattedArrayInMultipleTimezones(
date: string | Date,
onlyShowDate?: boolean

View File

@@ -53,7 +53,10 @@ export default class Email extends DatabaseProperty {
}
public static isValid(value: string): boolean {
const re: RegExp = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,10}$/i;
// from https://datatracker.ietf.org/doc/html/rfc5322
const re: RegExp =
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/i;
const isValid: boolean = re.test(value);
if (!isValid) {
return false;
@@ -68,6 +71,10 @@ export default class Email extends DatabaseProperty {
};
}
public static fromString(value: string): Email {
return new Email(value);
}
public static override fromJSON(json: JSONObject): Email {
if (json['_type'] === ObjectType.Email) {
return new Email((json['value'] as string) || '');

View File

@@ -2,7 +2,7 @@ enum ExceptionCode {
NotImplementedException = 0,
GeneralException = 1,
APIException = 2,
DatabaseNotConnectedException = 3,
DatabaseNotConnectedException = 500,
BadOperationException = 5,
WebRequestException = 6,
BadDataException = 400,

View File

@@ -8,6 +8,64 @@ import SerializableObjectDictionary from './SerializableObjectDictionary';
import JSON5 from 'json5';
export default class JSONFunctions {
public static nestJson(obj: JSONObject): JSONObject {
// obj could be in this format:
/**
* {
"http.url.protocol": "http",
"http.url.hostname": "localhost",
"http.host": "localhost",
*/
// we want to convert it to this format:
/**
* {
*
* "http": {
* "url": {
* "protocol": "http",
* "hostname": "localhost"
* },
* "host": "localhost",
* "method": "POST",
* "scheme": "http",
* "client_ip": "
* ...
*
* },
*/
const result: JSONObject = {};
for (const key in obj) {
const keys: Array<string> = key.split('.');
let currentObj: JSONObject = result;
for (let i: number = 0; i < keys.length; i++) {
const k: string | undefined = keys[i];
if (!k) {
continue;
}
if (i === keys.length - 1) {
currentObj[k] = obj[key];
} else {
if (!currentObj[k]) {
currentObj[k] = {};
}
currentObj = currentObj[k] as JSONObject;
}
}
}
return result;
}
public static isEmptyObject(
obj: JSONObject | BaseModel | null | undefined
): boolean {
@@ -294,6 +352,10 @@ export default class JSONFunctions {
return newVal;
}
public static toFormattedString(val: JSONValue): string {
return JSON.stringify(val, null, 4);
}
public static anyObjectToJSONObject(val: any): JSONObject {
return JSON.parse(JSON.stringify(val));
}
@@ -320,4 +382,14 @@ export default class JSONFunctions {
return returnObj;
}
public static flattenArray(val: JSONArray): JSONArray {
const returnArr: JSONArray = [];
for (const obj of val) {
returnArr.push(this.flattenObject(obj as JSONObject));
}
return returnArr;
}
}

View File

@@ -10,4 +10,5 @@ export default interface JSONWebTokenData extends JSONObject {
isMasterAdmin: boolean;
statusPageId?: ObjectID | undefined; // for status page logins.
projectId?: ObjectID | undefined; // for SSO logins.
isGlobalLogin: boolean; // If this is OneUptime username and password login. This is true, if this is SSO login. Then, this is false.
}

View File

@@ -2,7 +2,7 @@ enum ProductType {
Logs = 'Logs',
Traces = 'Traces',
Metrics = 'Metrics',
ActiveMonitoring = 'ActiveMonitoring',
ActiveMonitoring = 'Active Monitoring',
}
export default ProductType;

View File

@@ -23,6 +23,7 @@ export default class MonitorCriteria extends DatabaseProperty {
public static getDefaultMonitorCriteria(arg: {
monitorType: MonitorType;
monitorName: string;
onlineMonitorStatusId: ObjectID;
offlineMonitorStatusId: ObjectID;
defaultIncidentSeverityId: ObjectID;
@@ -33,12 +34,14 @@ export default class MonitorCriteria extends DatabaseProperty {
monitorType: arg.monitorType,
monitorStatusId: arg.offlineMonitorStatusId,
incidentSeverityId: arg.defaultIncidentSeverityId,
monitorName: arg.monitorName,
});
const onlineCriteria: MonitorCriteriaInstance | null =
MonitorCriteriaInstance.getDefaultOnlineMonitorCriteriaInstance({
monitorType: arg.monitorType,
monitorStatusId: arg.onlineMonitorStatusId,
monitorName: arg.monitorName,
});
monitorCriteria.data = {

View File

@@ -53,6 +53,7 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
public static getDefaultOnlineMonitorCriteriaInstance(arg: {
monitorType: MonitorType;
monitorStatusId: ObjectID;
monitorName: string;
}): MonitorCriteriaInstance | null {
if (arg.monitorType === MonitorType.IncomingRequest) {
const monitorCriteriaInstance: MonitorCriteriaInstance =
@@ -72,8 +73,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
incidents: [],
changeMonitorStatus: true,
createIncidents: false,
name: 'Check if online',
description: `This criteria checks if the ${arg.monitorType} is online`,
name: `Check if ${arg.monitorName} is online`,
description: `This criteria checks if the ${arg.monitorName} is online`,
};
return monitorCriteriaInstance;
@@ -97,8 +98,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
incidents: [],
changeMonitorStatus: true,
createIncidents: false,
name: 'Check if online',
description: `This criteria checks if the ${arg.monitorType} is online`,
name: `Check if ${arg.monitorName} is online`,
description: `This criteria checks if the ${arg.monitorName} is online`,
};
return monitorCriteriaInstance;
@@ -122,8 +123,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
incidents: [],
changeMonitorStatus: true,
createIncidents: false,
name: 'Check if online',
description: `This criteria checks if the ${arg.monitorType} is online`,
name: `Check if ${arg.monitorName} is online`,
description: `This criteria checks if the ${arg.monitorName} is online`,
};
return monitorCriteriaInstance;
@@ -153,8 +154,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
incidents: [],
changeMonitorStatus: true,
createIncidents: false,
name: 'Check if online',
description: `This criteria checks if the ${arg.monitorType} is online`,
name: `Check if ${arg.monitorName} is online`,
description: `This criteria checks if the ${arg.monitorName} is online`,
};
if (
@@ -178,6 +179,7 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
monitorType: MonitorType;
monitorStatusId: ObjectID;
incidentSeverityId: ObjectID;
monitorName: string;
}): MonitorCriteriaInstance {
const monitorCriteriaInstance: MonitorCriteriaInstance =
new MonitorCriteriaInstance();
@@ -201,8 +203,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
],
incidents: [
{
title: `${arg.monitorType} is offline`,
description: `${arg.monitorType} is currently offline.`,
title: `${arg.monitorName} is offline`,
description: `${arg.monitorName} is currently offline.`,
incidentSeverityId: arg.incidentSeverityId,
autoResolveIncident: true,
id: ObjectID.generate().toString(),
@@ -211,8 +213,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
],
changeMonitorStatus: true,
createIncidents: true,
name: 'Check if offline',
description: `This criteria checks if the ${arg.monitorType} is offline`,
name: `Check if ${arg.monitorName} is offline`,
description: `This criteria checks if the ${arg.monitorName} is offline`,
};
}
@@ -238,8 +240,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
],
incidents: [
{
title: `${arg.monitorType} is offline`,
description: `${arg.monitorType} is currently offline.`,
title: `${arg.monitorName} is offline`,
description: `${arg.monitorName} is currently offline.`,
incidentSeverityId: arg.incidentSeverityId,
autoResolveIncident: true,
id: ObjectID.generate().toString(),
@@ -248,8 +250,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
],
changeMonitorStatus: true,
createIncidents: true,
name: 'Check if offline',
description: `This criteria checks if the ${arg.monitorType} is offline`,
name: `Check if ${arg.monitorName} is offline`,
description: `This criteria checks if the ${arg.monitorName} is offline`,
};
}
@@ -267,8 +269,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
],
incidents: [
{
title: `${arg.monitorType} is offline`,
description: `${arg.monitorType} is currently offline.`,
title: `${arg.monitorName} is offline`,
description: `${arg.monitorName} is currently offline.`,
incidentSeverityId: arg.incidentSeverityId,
autoResolveIncident: true,
id: ObjectID.generate().toString(),
@@ -277,8 +279,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
],
changeMonitorStatus: true,
createIncidents: true,
name: 'Check if offline',
description: `This criteria checks if the ${arg.monitorType} is offline`,
name: `Check if ${arg.monitorName} is offline`,
description: `This criteria checks if the ${arg.monitorName} is offline`,
};
}
@@ -296,8 +298,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
],
incidents: [
{
title: `${arg.monitorType} is offline`,
description: `${arg.monitorType} is currently offline.`,
title: `${arg.monitorName} is offline`,
description: `${arg.monitorName} is currently offline.`,
incidentSeverityId: arg.incidentSeverityId,
autoResolveIncident: true,
id: ObjectID.generate().toString(),
@@ -306,8 +308,8 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
],
changeMonitorStatus: true,
createIncidents: true,
name: 'Check if offline',
description: `This criteria checks if the ${arg.monitorType} is offline`,
name: `Check if ${arg.monitorName} is offline`,
description: `This criteria checks if the ${arg.monitorName} is offline`,
};
}

View File

@@ -41,6 +41,7 @@ export default class MonitorStep extends DatabaseProperty {
}
public static getDefaultMonitorStep(arg: {
monitorName: string;
monitorType: MonitorType;
onlineMonitorStatusId: ObjectID;
offlineMonitorStatusId: ObjectID;

View File

@@ -36,6 +36,7 @@ export default class MonitorSteps extends DatabaseProperty {
public static getDefaultMonitorSteps(arg: {
defaultMonitorStatusId: ObjectID;
monitorType: MonitorType;
monitorName: string;
onlineMonitorStatusId: ObjectID;
offlineMonitorStatusId: ObjectID;
defaultIncidentSeverityId: ObjectID;

View File

@@ -118,4 +118,36 @@ export class MonitorTypeHelper {
return monitorTypeProps[0].title;
}
public static isProbableMonitors(monitorType: MonitorType): boolean {
const isProbeableMonitor: boolean =
monitorType === MonitorType.API ||
monitorType === MonitorType.Website ||
monitorType === MonitorType.IP ||
monitorType === MonitorType.Ping ||
monitorType === MonitorType.Port ||
monitorType === MonitorType.SSLCertificate;
return isProbeableMonitor;
}
public static doesMonitorTypeHaveDocumentation(
monitorType: MonitorType
): boolean {
return (
monitorType === MonitorType.IncomingRequest ||
monitorType === MonitorType.Server
);
}
public static doesMonitorTypeHaveInterval(
monitorType: MonitorType
): boolean {
return this.isProbableMonitors(monitorType);
}
public static doesMonitorTypeHaveCriteria(
monitorType: MonitorType
): boolean {
return monitorType !== MonitorType.Manual;
}
}

View File

@@ -4,7 +4,7 @@ enum WorkflowStatus {
Success = 'Success',
Error = 'Error',
Timeout = 'Timeout',
WorkflowCountExceeded = 'WorkflowCountExceeded',
WorkflowCountExceeded = 'Workflow Count Exceeded',
}
export default WorkflowStatus;

View File

@@ -343,6 +343,9 @@ export default class API {
);
}
throw new APIException('Endpoint is not available');
// get url from error
const url: string = error?.config?.url || '';
throw new APIException(`URL ${url ? url + ' ' : ''}is not available.`);
}
}

View File

@@ -4,3 +4,4 @@ export const EVERY_HOUR: string = '1 * * * *';
export const EVERY_FIVE_MINUTE: string = '*/5 * * * *';
export const EVERY_FIVE_SECONDS: string = '*/5 * * * * *';
export const EVERY_WEEK: string = '0 0 * * 0';
export const EVERY_FIFTEEN_MINUTE: string = '*/15 * * * *';

View File

@@ -11,13 +11,13 @@
"dependencies": {
"@types/crypto-js": "^4.2.2",
"@types/uuid": "^8.3.4",
"axios": "^1.6.7",
"axios": "^1.6.8",
"crypto-js": "^4.1.1",
"json5": "^2.2.3",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"posthog-js": "^1.111.0",
"reflect-metadata": "^0.2.1",
"posthog-js": "^1.126.0",
"reflect-metadata": "^0.2.2",
"slugify": "^1.6.5",
"typeorm": "^0.3.20",
"uuid": "^8.3.2"
@@ -1382,11 +1382,11 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"dependencies": {
"follow-redirects": "^1.15.4",
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@@ -2146,9 +2146,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
@@ -3845,9 +3845,10 @@
}
},
"node_modules/posthog-js": {
"version": "1.111.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.111.0.tgz",
"integrity": "sha512-0Bf1hnclW3WLV7UMrIOMk6kVeEWJMNxe8NhkWLraus1uB63p05pPUB8QnpU6JLbx4h800DvtOxAfXbiJN+ozWw==",
"version": "1.126.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.126.0.tgz",
"integrity": "sha512-8qCdPE9RZkyXI3kKCnkXWxK0jn2mLZg6g5a6KezDPqH7mHTG66v7ANU31hcwzQGV5F5UW1GXw0xL0PaC3HkA6g==",
"deprecated": "This version of posthog-js is deprecated, please update posthog-js, and do not use this version! Check out our JS docs at https://posthog.com/docs/libraries/js",
"dependencies": {
"fflate": "^0.4.8",
"preact": "^10.19.3"
@@ -3934,9 +3935,9 @@
"dev": true
},
"node_modules/reflect-metadata": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz",
"integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw=="
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="
},
"node_modules/require-directory": {
"version": "2.1.1",

View File

@@ -27,8 +27,8 @@
"json5": "^2.2.3",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"posthog-js": "^1.116.2",
"reflect-metadata": "^0.2.1",
"posthog-js": "^1.126.0",
"reflect-metadata": "^0.2.2",
"slugify": "^1.6.5",
"typeorm": "^0.3.20",
"uuid": "^8.3.2"

View File

@@ -26,6 +26,7 @@ import {
import PartialEntity from 'Common/Types/Database/PartialEntity';
import { UserPermission } from 'Common/Types/Permission';
import CommonAPI from './CommonAPI';
import GroupBy from '../Types/Database/GroupBy';
export default class BaseAPI<
TBaseModel extends BaseModel,
@@ -237,6 +238,7 @@ export default class BaseAPI<
let query: Query<BaseModel> = {};
let select: Select<BaseModel> = {};
let sort: Sort<BaseModel> = {};
let groupBy: GroupBy<BaseModel> | undefined;
if (req.body) {
query = JSONFunctions.deserialize(
@@ -250,6 +252,10 @@ export default class BaseAPI<
sort = JSONFunctions.deserialize(
req.body['sort']
) as Sort<BaseModel>;
groupBy = JSONFunctions.deserialize(
req.body['groupBy']
) as GroupBy<BaseModel>;
}
const databaseProps: DatabaseCommonInteractionProps =
@@ -260,6 +266,7 @@ export default class BaseAPI<
select,
skip: skip,
limit: limit,
groupBy: groupBy,
sort: sort,
props: databaseProps,
});
@@ -342,7 +349,7 @@ export default class BaseAPI<
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
public async updateItem(
@@ -370,7 +377,7 @@ export default class BaseAPI<
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
public async createItem(

View File

@@ -25,6 +25,7 @@ import { UserPermission } from 'Common/Types/Permission';
import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel';
import AnalyticsDatabaseService from '../Services/AnalyticsDatabaseService';
import CommonAPI from './CommonAPI';
import GroupBy from '../Types/AnalyticsDatabase/GroupBy';
export default class BaseAnalyticsAPI<
TAnalyticsDataModel extends AnalyticsDataModel,
@@ -235,6 +236,7 @@ export default class BaseAnalyticsAPI<
let query: Query<AnalyticsDataModel> = {};
let select: Select<AnalyticsDataModel> = {};
let sort: Sort<AnalyticsDataModel> = {};
let groupBy: GroupBy<AnalyticsDataModel> = {};
if (req.body) {
query = JSONFunctions.deserialize(
@@ -248,6 +250,10 @@ export default class BaseAnalyticsAPI<
sort = JSONFunctions.deserialize(
req.body['sort']
) as Sort<AnalyticsDataModel>;
groupBy = JSONFunctions.deserialize(
req.body['groupBy']
) as GroupBy<AnalyticsDataModel>;
}
const databaseProps: DatabaseCommonInteractionProps =
@@ -259,11 +265,13 @@ export default class BaseAnalyticsAPI<
skip: skip,
limit: limit,
sort: sort,
groupBy: groupBy,
props: databaseProps,
});
const count: PositiveNumber = await this.service.countBy({
query,
groupBy: groupBy,
props: databaseProps,
});
@@ -340,7 +348,7 @@ export default class BaseAnalyticsAPI<
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
public async updateItem(
@@ -370,7 +378,7 @@ export default class BaseAnalyticsAPI<
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
public async createItem(

View File

@@ -166,7 +166,7 @@ export default class UserAPI extends BaseAPI<
},
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
next(err);
}

View File

@@ -1,14 +1,19 @@
import version from './VersionAPI';
import status from './StatusAPI';
import StatusAPI, { StatusAPIOptions } from './StatusAPI';
import Express, { ExpressApplication } from '../Utils/Express';
const app: ExpressApplication = Express.getExpressApp();
type InitFunction = (appName: string) => void;
export interface InitOptions {
appName: string;
statusOptions: StatusAPIOptions;
}
const init: InitFunction = (appName: string): void => {
app.use([`/${appName}`, '/'], version);
app.use([`/${appName}`, '/'], status);
type InitFunction = (data: InitOptions) => void;
const init: InitFunction = (data: InitOptions): void => {
app.use([`/${data.appName}`, '/'], version);
app.use([`/${data.appName}`, '/'], StatusAPI.init(data.statusOptions));
};
export default init;

View File

@@ -111,7 +111,7 @@ router.post(
return Response.sendErrorResponse(req, res, err as Exception);
}
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);

View File

@@ -4,26 +4,78 @@ import Express, {
ExpressRouter,
} from '../Utils/Express';
import LocalCache from '../Infrastructure/LocalCache';
import Response from '../Utils/Response';
import ServerException from 'Common/Types/Exception/ServerException';
import logger from '../Utils/Logger';
import Exception from 'Common/Types/Exception/Exception';
const router: ExpressRouter = Express.getRouter();
export interface StatusAPIOptions {
readyCheck: () => Promise<void>;
liveCheck: () => Promise<void>;
}
router.get('/app-name', (_req: ExpressRequest, res: ExpressResponse) => {
res.send({ app: LocalCache.getString('app', 'name') });
});
export default class StatusAPI {
public static init(options: StatusAPIOptions): ExpressRouter {
const router: ExpressRouter = Express.getRouter();
// General status
router.get('/status', (_req: ExpressRequest, res: ExpressResponse) => {
res.send({ status: 'ok' });
});
router.get(
'/app-name',
(_req: ExpressRequest, res: ExpressResponse) => {
res.send({ app: LocalCache.getString('app', 'name') });
}
);
//Healthy probe
router.get('/status/healthy', (_req: ExpressRequest, res: ExpressResponse) => {
res.send({ status: 'healthy' });
});
// General status
router.get('/status', (req: ExpressRequest, res: ExpressResponse) => {
Response.sendJsonObjectResponse(req, res, {
status: 'ok',
});
});
//Liveness probe
router.get('/status/live', (_req: ExpressRequest, res: ExpressResponse) => {
res.send({ status: 'live' });
});
//Healthy probe
router.get(
'/status/ready',
async (req: ExpressRequest, res: ExpressResponse) => {
try {
logger.info('Ready check');
await options.readyCheck();
Response.sendJsonObjectResponse(req, res, {
status: 'ok',
});
} catch (e) {
Response.sendErrorResponse(
req,
res,
e instanceof Exception
? e
: new ServerException('Server is not ready')
);
}
}
);
export default router;
//Liveness probe
router.get(
'/status/live',
async (req: ExpressRequest, res: ExpressResponse) => {
try {
logger.info('Live check');
await options.readyCheck();
Response.sendJsonObjectResponse(req, res, {
status: 'ok',
});
} catch (e) {
Response.sendErrorResponse(
req,
res,
e instanceof Exception
? e
: new ServerException('Server is not ready')
);
}
}
);
return router;
}
}

View File

@@ -15,10 +15,7 @@ import Response from '../Utils/Response';
import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException';
import BadDataException from 'Common/Types/Exception/BadDataException';
import StatusPageFooterLinkService from '../Services/StatusPageFooterLinkService';
import {
LIMIT_INFINITY,
LIMIT_PER_PROJECT,
} from 'Common/Types/Database/LimitMax';
import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import StatusPageFooterLink from 'Model/Models/StatusPageFooterLink';
import StatusPageHeaderLinkService from '../Services/StatusPageHeaderLinkService';
import StatusPageHeaderLink from 'Model/Models/StatusPageHeaderLink';
@@ -53,8 +50,6 @@ import ScheduledMaintenanceStateTimelineService from '../Services/ScheduledMaint
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
import Query from '../Types/Database/Query';
import JSONFunctions from 'Common/Types/JSONFunctions';
import GreenlockChallenge from 'Model/Models/GreenlockChallenge';
import GreenlockChallengeService from '../Services/GreenlockChallengeService';
import NotFoundException from 'Common/Types/Exception/NotFoundException';
import logger from '../Utils/Logger';
import Email from 'Common/Types/Email';
@@ -77,6 +72,8 @@ import CommonAPI from './CommonAPI';
import Phone from 'Common/Types/Phone';
import StatusPageHistoryChartBarColorRule from 'Model/Models/StatusPageHistoryChartBarColorRule';
import StatusPageHistoryChartBarColorRuleService from '../Services/StatusPageHistoryChartBarColorRuleService';
import AcmeChallenge from 'Model/Models/AcmeChallenge';
import AcmeChallengeService from '../Services/AcmeChallengeService';
export default class StatusPageAPI extends BaseAPI<
StatusPage,
@@ -125,7 +122,7 @@ export default class StatusPageAPI extends BaseAPI<
);
}
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);
@@ -135,8 +132,8 @@ export default class StatusPageAPI extends BaseAPI<
.getCrudApiPath()
?.toString()}/.well-known/acme-challenge/:token`,
async (req: ExpressRequest, res: ExpressResponse) => {
const challenge: GreenlockChallenge | null =
await GreenlockChallengeService.findOneBy({
const challenge: AcmeChallenge | null =
await AcmeChallengeService.findOneBy({
query: {
token: req.params['token'] as string,
},
@@ -748,6 +745,7 @@ export default class StatusPageAPI extends BaseAPI<
monitorId: true,
createdAt: true,
endsAt: true,
startsAt: true,
monitorStatus: {
name: true,
color: true,
@@ -755,10 +753,10 @@ export default class StatusPageAPI extends BaseAPI<
} as any,
},
sort: {
createdAt: SortOrder.Ascending,
createdAt: SortOrder.Descending,
},
skip: 0,
limit: LIMIT_INFINITY, // This can be optimized.
limit: LIMIT_MAX, // This can be optimized.
props: {
isRoot: true,
},
@@ -776,6 +774,7 @@ export default class StatusPageAPI extends BaseAPI<
monitorId: true,
createdAt: true,
endsAt: true,
startsAt: true,
monitorStatus: {
name: true,
color: true,
@@ -783,15 +782,32 @@ export default class StatusPageAPI extends BaseAPI<
} as any,
},
sort: {
createdAt: SortOrder.Ascending,
createdAt: SortOrder.Descending,
},
skip: 0,
limit: LIMIT_INFINITY, // This can be optimized.
limit: LIMIT_MAX, // This can be optimized.
props: {
isRoot: true,
},
})
);
// sort monitorStatusTimelines by createdAt.
monitorStatusTimelines = monitorStatusTimelines.sort(
(
a: MonitorStatusTimeline,
b: MonitorStatusTimeline
) => {
if (!a.createdAt || !b.createdAt) {
return 0;
}
return (
b.createdAt!.getTime() -
a.createdAt!.getTime()
);
}
);
}
// check if status page has active incident.
@@ -1002,9 +1018,8 @@ export default class StatusPageAPI extends BaseAPI<
},
select: scheduledEventsSelect,
sort: {
createdAt: SortOrder.Ascending,
startsAt: SortOrder.Ascending,
},
skip: 0,
limit: LIMIT_PER_PROJECT,
props: {
@@ -1023,9 +1038,8 @@ export default class StatusPageAPI extends BaseAPI<
},
select: scheduledEventsSelect,
sort: {
createdAt: SortOrder.Ascending,
startsAt: SortOrder.Ascending,
},
skip: 0,
limit: LIMIT_PER_PROJECT,
props: {
@@ -1219,7 +1233,7 @@ export default class StatusPageAPI extends BaseAPI<
) => {
try {
await this.subscribeToStatusPage(req);
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
next(err);
}
@@ -1265,7 +1279,7 @@ export default class StatusPageAPI extends BaseAPI<
try {
await this.subscribeToStatusPage(req);
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
next(err);
}
@@ -2049,7 +2063,13 @@ export default class StatusPageAPI extends BaseAPI<
await StatusPageSubscriberService.updateOneById({
id: statusPageSubscriber.id!,
data: statusPageSubscriber,
data: {
statusPageResources:
statusPageSubscriber.statusPageResources!,
isSubscribedToAllResources:
statusPageSubscriber.isSubscribedToAllResources!,
isUnsubscribed: statusPageSubscriber.isUnsubscribed,
} as any,
props: {
isRoot: true,
},

View File

@@ -0,0 +1,259 @@
import StatusPageDomain from 'Model/Models/StatusPageDomain';
import BaseAPI from './BaseAPI';
import StatusPageDomainService, {
Service as StatusPageDomainServiceType,
} from '../Services/StatusPageDomainService';
import {
ExpressRequest,
ExpressResponse,
NextFunction,
} from '../Utils/Express';
import BadDataException from 'Common/Types/Exception/BadDataException';
import Response from '../Utils/Response';
import ObjectID from 'Common/Types/ObjectID';
import UserMiddleware from '../Middleware/UserAuthorization';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
import CommonAPI from './CommonAPI';
import logger from '../Utils/Logger';
import PositiveNumber from 'Common/Types/PositiveNumber';
import { StatusPageCNameRecord } from '../EnvironmentConfig';
export default class StatusPageDomainAPI extends BaseAPI<
StatusPageDomain,
StatusPageDomainServiceType
> {
public constructor() {
super(StatusPageDomain, StatusPageDomainService);
// CNAME verification api. THis API will be used from the dashboard to validate the CNAME MANUALLY.
this.router.get(
`${new this.entityType()
.getCrudApiPath()
?.toString()}/verify-cname/:id`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
if (!StatusPageCNameRecord) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
`Custom Domains not enabled for this
OneUptime installation. Please contact
your server admin to enable this
feature.`
)
);
}
const databaseProps: DatabaseCommonInteractionProps =
await CommonAPI.getDatabaseCommonInteractionProps(req);
const id: ObjectID = new ObjectID(
req.params['id'] as string
);
// check if the user can read the domain.
const domainCount: PositiveNumber =
await StatusPageDomainService.countBy({
query: {
_id: id.toString(),
},
props: databaseProps,
});
if (domainCount.toNumber() === 0) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'The domain does not exist or user does not have access to it.'
)
);
}
const domain: StatusPageDomain | null =
await StatusPageDomainService.findOneBy({
query: {
_id: id.toString(),
},
select: {
_id: true,
fullDomain: true,
},
props: {
isRoot: true,
},
});
if (!domain) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid token.')
);
}
if (!domain.fullDomain) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid domain.')
);
}
const isValid: boolean =
await StatusPageDomainService.isCnameValid(
domain.fullDomain!
);
if (!isValid) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'CNAME is not verified. Please make sure you have the correct record and please verify CNAME again. If you are sure that the record is correct, please wait for some time for the DNS to propagate.'
)
);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (e) {
next(e);
}
}
);
// Provision SSL API. THis API will be used from the dashboard to validate the CNAME MANUALLY.
this.router.get(
`${new this.entityType()
.getCrudApiPath()
?.toString()}/order-ssl/:id`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
if (!StatusPageCNameRecord) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
`Custom Domains not enabled for this
OneUptime installation. Please contact
your server admin to enable this
feature.`
)
);
}
const databaseProps: DatabaseCommonInteractionProps =
await CommonAPI.getDatabaseCommonInteractionProps(req);
const id: ObjectID = new ObjectID(
req.params['id'] as string
);
// check if the user can read the domain.
const domainCount: PositiveNumber =
await StatusPageDomainService.countBy({
query: {
_id: id.toString(),
},
props: databaseProps,
});
if (domainCount.toNumber() === 0) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'The domain does not exist or user does not have access to it.'
)
);
}
const domain: StatusPageDomain | null =
await StatusPageDomainService.findOneBy({
query: {
_id: id.toString(),
},
select: {
_id: true,
fullDomain: true,
cnameVerificationToken: true,
isCnameVerified: true,
isSslProvisioned: true,
},
props: {
isRoot: true,
},
});
if (!domain) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid token.')
);
}
if (!domain.cnameVerificationToken) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid token.')
);
}
if (!domain.isCnameVerified) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'CNAME is not verified. Please verify CNAME first before you provision SSL.'
)
);
}
if (domain.isSslProvisioned) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('SSL is already provisioned.')
);
}
if (!domain.fullDomain) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid domain.')
);
}
logger.info('Ordering SSL');
// provision SSL
await StatusPageDomainService.orderCert(domain);
logger.info(
'SSL Provisioned for domain - ' + domain.fullDomain
);
return Response.sendEmptySuccessResponse(req, res);
} catch (e) {
next(e);
}
}
);
}
}

View File

@@ -95,7 +95,7 @@ export default class UserCallAPI extends BaseAPI<
},
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);
@@ -115,7 +115,7 @@ export default class UserCallAPI extends BaseAPI<
await this.service.resendVerificationCode(req.body.itemId);
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);
}

View File

@@ -94,7 +94,7 @@ export default class UserEmailAPI extends BaseAPI<
},
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);
@@ -116,7 +116,7 @@ export default class UserEmailAPI extends BaseAPI<
await this.service.resendVerificationCode(req.body.itemId);
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);
}

View File

@@ -91,7 +91,7 @@ export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> {
},
});
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);
@@ -111,7 +111,7 @@ export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> {
await this.service.resendVerificationCode(req.body.itemId);
return Response.sendEmptyResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
}
);
}

View File

@@ -4,6 +4,8 @@ import Hostname from 'Common/Types/API/Hostname';
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
import { JSONObject } from 'Common/Types/JSON';
import BillingConfig from './BillingConfig';
import Email from 'Common/Types/Email';
import Protocol from 'Common/Types/API/Protocol';
export enum ConfigLogLevel {
INFO = 'INFO',
@@ -24,6 +26,14 @@ export const DatabaseHost: Hostname = Hostname.fromString(
process.env['DATABASE_HOST'] || 'postgres'
);
export const LetsEncryptNotificationEmail: Email = Email.fromString(
process.env['LETS_ENCRYPT_NOTIFICATION_EMAIL'] ||
'notifications@example.com'
);
export const LetsEncryptAccountKey: string =
process.env['LETS_ENCRYPT_ACCOUNT_KEY'] || '';
export const DatabasePort: Port = new Port(
process.env['DATABASE_PORT'] || '5432'
);
@@ -136,6 +146,9 @@ export const ClickhouseHost: Hostname = Hostname.fromString(
process.env['CLICKHOUSE_HOST'] || 'clickhouse'
);
export const StatusPageCNameRecord: string =
process.env['STATUS_PAGE_CNAME_RECORD'] || '';
export const ClickhousePort: Port = new Port(
process.env['CLICKHOUSE_PORT'] || '8123'
);
@@ -155,3 +168,8 @@ export const AppVersion: string = process.env['APP_VERSION'] || 'unknown';
export const LogLevel: ConfigLogLevel =
(process.env['LOG_LEVEL'] as ConfigLogLevel) || ConfigLogLevel.ERROR;
export const HttpProtocol: Protocol =
process.env['HTTP_PROTOCOL'] === 'https' ? Protocol.HTTPS : Protocol.HTTP;
export const Host: string = process.env['HOST'] || '';

View File

@@ -98,6 +98,32 @@ export default class ClickhouseDatabase {
this.dataSource = null;
}
}
public async checkConnnectionStatus(): Promise<boolean> {
// Ping clickhouse to check if the connection is still alive
try {
const result: PingResult | undefined =
await this.getDataSource()?.ping();
if (!result) {
throw new DatabaseNotConnectedException(
'Clickhouse Database is not connected'
);
}
if (result?.success === false) {
throw new DatabaseNotConnectedException(
'Clickhouse Database is not connected'
);
}
return true;
} catch (err) {
logger.error('Clickhouse Connection Lost');
logger.error(err);
return false;
}
}
}
export const ClickhouseAppInstance: ClickhouseDatabase =

View File

@@ -71,6 +71,26 @@ export default class Database {
this.dataSource = null;
}
}
public async checkConnnectionStatus(): Promise<boolean> {
// check popstgres connection to see if it is still alive
try {
const result: any = await this.dataSource?.query(
`SELECT COUNT(domain) FROM "AcmeChallenge"`
); // this is a dummy query to check if the connection is still alive
if (!result) {
return false;
}
return true;
} catch (err) {
logger.error('Postgres Connection Lost');
logger.error(err);
return false;
}
}
}
export const PostgresAppInstance: Database = new Database();

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