Compare commits

...

297 Commits

Author SHA1 Message Date
Simon Larsen
f09419ef50 refactor: Update failureCause message in PingMonitor.ts 2024-06-21 13:59:45 +01:00
Simon Larsen
4c90dc66a0 refactor: Update SendCreatedResourceNotification to convert rootCause to HTML 2024-06-21 13:56:23 +01:00
Simon Larsen
99c2e34ab5 refactor: Add support for IsOnline check in CriteriaFilterUtil 2024-06-21 13:43:29 +01:00
Simon Larsen
0fb0e9b047 refactor: Update SendCreatedResourceNotification to convert rootCause to HTML
This commit updates the SendCreatedResourceNotification class to convert the rootCause property to HTML using the Markdown.convertToHTML function. This ensures that the rootCause is properly formatted and displayed in the incident notification email. The rootCause is either the incident's rootCause or a default message if no rootCause is identified. This enhancement improves the readability and effectiveness of the incident notifications.
2024-06-21 13:34:00 +01:00
Simon Larsen
7ae6a3c5ea refactor: Update ProbeMonitorResponseService to include criteria details in rootCause
This commit updates the ProbeMonitorResponseService class to include the criteria details in the rootCause property of the ProbeApiIngestResponse. The criteria met ID, criteria name, criteria description, filter conditions met, and failure cause are now included in the rootCause. This enhancement improves the clarity and completeness of the rootCause information.
2024-06-21 13:08:05 +01:00
Simon Larsen
59f0526936 refactor: Update EvaluateOverTime method to handle boolean values for IsOnline metric
The EvaluateOverTime method in the EvaluateOverTime.ts file has been updated to handle boolean values for the IsOnline metric. This change allows the method to correctly evaluate and process the IsOnline metric data, improving the accuracy and reliability of the evaluation process.
2024-06-21 12:57:50 +01:00
Simon Larsen
bfcd5c1753 refactor: Improve clarity of CompareCriteria message generation
The CompareCriteria class has been refactored to improve the clarity of message generation. The code now checks the filter type before appending the values to the message, ensuring that the message is only generated when necessary. This change enhances the readability and maintainability of the code.
2024-06-21 12:31:43 +01:00
Simon Larsen
76df1a5889 refactor: Fix CriteriaFilterUtil to handle IsOnline check correctly
The CriteriaFilterUtil class has been updated to handle the IsOnline check correctly. The getEvaluateOverTimeTypeByCriteriaFilter method now includes the IsOnline check in the returned array of EvaluateOverTimeType values. This fix ensures that the CriteriaFilterUtil class functions properly and provides accurate results for the IsOnline check.
2024-06-21 12:24:06 +01:00
Simon Larsen
422ee6c192 refactor: Add support for IsOnline check in CriteriaFilterUtil
This commit adds support for the IsOnline check in the CriteriaFilterUtil class. The getEvaluateOverTimeTypeByCriteriaFilter method now includes the IsOnline check in the returned array of EvaluateOverTimeType values. This enhancement improves the functionality and flexibility of the CriteriaFilterUtil class.
2024-06-21 11:58:21 +01:00
Simon Larsen
808b3512f3 refactor: Improve clarity of declaredBy field assignment in SendCreatedResourceNotification.ts
This commit refactors the code in SendCreatedResourceNotification.ts to improve the clarity of how the "declaredBy" field is assigned. The code now checks if the incident has a createdByProbe and createdByUser, and assigns the appropriate value to "declaredBy" based on the available information. This change enhances the readability and maintainability of the code.
2024-06-21 11:44:19 +01:00
Simon Larsen
742796fd67 feat: Add declaredBy field to IncidentOwnerResourceCreated template and SendCreatedResourceNotification job
This commit adds the "declaredBy" field to the IncidentOwnerResourceCreated template in the Notification feature set and the SendCreatedResourceNotification job in the Workers feature set. The "declaredBy" field is used to display the name of the user or probe who declared the incident. This enhancement improves the clarity and completeness of the incident notifications sent to incident owners.
2024-06-21 11:39:32 +01:00
Simon Larsen
9027369e10 refactor: Add suffix to ProbeElement in Probe.tsx
This commit adds a new optional prop "suffix" to the ProbeElement component in Probe.tsx. The suffix is appended to the probe name displayed in the UI. This enhancement provides more flexibility in customizing the display of probe names.
2024-06-21 11:24:13 +01:00
Simon Larsen
a87f3ac896 refactor: Fix branch name generation in CopilotActionsBase.ts
The branch name generation in CopilotActionsBase.ts has been fixed to replace double hyphens with a single hyphen. This ensures that the branch name is correctly formatted and avoids any potential issues with branch creation and management.
2024-06-21 10:59:34 +01:00
Simon Larsen
db75cc1a5b Merge pull request #1499 from OneUptime/ai-copilot-main
Ai copilot main
2024-06-21 10:41:28 +01:00
Simon Larsen
2deeb32b78 refactor: Fix branch name generation in CopilotActionsBase.ts
The branch name generation in CopilotActionsBase.ts has been fixed to replace double hyphens with a single hyphen. This ensures that the branch name is correctly formatted and avoids any potential issues with branch creation and management.
2024-06-20 22:31:58 +01:00
Simon Larsen
bc8b8eb982 refactor: Update LlmType import paths in Copilot/Config.ts, Copilot/Service/LLM/Llama.ts, and Copilot/Service/LLM/LLM.ts
This commit updates the import paths for the LlmType module in the following files:
- Copilot/Config.ts
- Copilot/Service/LLM/Llama.ts
- Copilot/Service/LLM/LLM.ts

The import paths are corrected to match the actual file structure, ensuring that the LlmType module is properly imported and used throughout the codebase. This change improves the maintainability and readability of the code.
2024-06-20 21:37:24 +01:00
Simon Larsen
ef3fe66e72 Merge pull request #1498 from jackveney/patch-1
Update endpoint-status.sh to follow redirects
2024-06-20 21:10:32 +01:00
Jack Veney
17a0b65a4b Update endpoint-status.sh
Included the -L option in the curl command, ensuring that it will follow any 301 redirects until the final URL is reached.
2024-06-20 15:24:27 -04:00
Simon Larsen
01a1a0f69e Merge pull request #1496 from OneUptime/snyk-upgrade-99d49c023062d95b651963a884a19da7
[Snyk] Upgrade posthog-js from 1.135.2 to 1.136.1
2024-06-20 17:30:18 +01:00
Simon Larsen
1260e482cf refactor: Improve comments in code and update job function in app.py 2024-06-20 13:14:39 +01:00
Simon Larsen
6e5081a4e3 refactor: Remove unused CopilotEvent and CopilotEventService 2024-06-20 12:30:06 +01:00
Simon Larsen
dd93998296 refactor: Remove unused CopilotEvent and CopilotEventService
This commit removes the unused CopilotEvent enum and CopilotEventService class. These files are no longer needed in the codebase and can be safely deleted. Removing unused code improves the maintainability and reduces the complexity of the project.
2024-06-20 11:41:04 +01:00
Simon Larsen
94956b045a refactor: Improve comments in code and update job function in app.py
This commit refactors the code by improving the comments in the code and updating the job function in app.py. The comments are now focused on code that is hard to understand, and unnecessary comments are removed. The job function is converted to an async function to support asynchronous processing, which improves the performance and responsiveness of the application. These changes enhance the readability and efficiency of the codebase.
2024-06-20 10:00:07 +00:00
Simon Larsen
b2f650a865 refactor: Sanitize file path in CodeRepositoryUtil.getFileContent()
The file path in the CodeRepositoryUtil.getFileContent() method is now sanitized using the LocalFile.sanitizeFilePath() function before executing the command. This change ensures that the file path is properly formatted and prevents any potential security vulnerabilities or errors caused by invalid file paths.
2024-06-20 10:26:16 +01:00
Simon Larsen
4390a37184 fix prompt 2024-06-20 09:22:53 +00:00
Simon Larsen
9b08d1a9e4 refactor: Convert job function to async in app.py
The job function in app.py has been converted to an async function to support asynchronous processing. This change improves the performance and responsiveness of the application by allowing other tasks to run concurrently while the job function is processing the queue.
2024-06-19 21:05:36 +00:00
Simon Larsen
dbef1071e0 refactor: Add GPU support to Llama app in docker-compose.ai.yml 2024-06-19 20:58:08 +00:00
Simon Larsen
98fbbe301e refactor: Update variable types in Copilot/Init.ts and Copilot/Utils/Prompts.ts 2024-06-19 21:03:54 +01:00
snyk-bot
6805083725 fix: upgrade posthog-js from 1.135.2 to 1.136.1
Snyk has created this PR to upgrade posthog-js from 1.135.2 to 1.136.1.

See this package in npm:
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-06-19 19:07:13 +00:00
Simon Larsen
27213fa235 refactor: Add FixNumberOfCodeEventsInEachRun constant to Config.ts
This commit adds the FixNumberOfCodeEventsInEachRun constant to the Config.ts file. The constant is set to 5 and is used to determine the number of code events to fix in each run. This change improves the flexibility and configurability of the code events handling in the application.
2024-06-19 16:29:25 +01:00
Simon Larsen
baa98e333e refactor: Update LlmType export in Config.ts and LlmType.ts 2024-06-19 15:36:24 +01:00
Simon Larsen
ccdcf2c679 refactor: Update Llama app to use local model path instead of model ID 2024-06-19 15:06:36 +01:00
Simon Larsen
85edd12c2d refactor: Update Dockerfile.tpl to use huggingface/transformers-pytorch-gpu image 2024-06-19 13:20:34 +00:00
Simon Larsen
97cc28b182 refactor: Update Dockerfile.tpl to use huggingface/transformers-pytorch-gpu image
This commit updates the Dockerfile.tpl to use the huggingface/transformers-pytorch-gpu image instead of the continuumio/anaconda3 image. This change allows the Llama app to utilize GPU resources for improved performance in AI processing. Additionally, the unnecessary installation of the transformers and accelerate libraries is removed as they are already included in the huggingface/transformers-pytorch-gpu image.
2024-06-19 13:06:23 +00:00
Simon Larsen
b0041e6993 refactor: Update Dockerfile.tpl to expose port 8547 instead of port 80 2024-06-19 12:18:25 +00:00
Simon Larsen
a66d78743d Merge pull request #1495 from im-alfa/bugfix/add-aero-as-a-valid-tld
fix: add `.aero` as a valid TLD
2024-06-19 12:48:38 +01:00
Simon Larsen
bc78491478 ```text
refactor: Add appname label to probe.yaml

This commit adds the "appname" label to the probe.yaml file in order to provide additional information about the application. This label will help with identifying the specific application when managing and monitoring the Kubernetes resources.
2024-06-19 11:14:33 +01:00
alfa
7751870ccf fix: add .aero as a valid TLD 2024-06-19 05:42:23 +02:00
Simon Larsen
df6ffb15d4 refactor: Add GPU support to Llama app in docker-compose.ai.yml
This commit adds GPU support to the Llama app in the docker-compose.ai.yml file. It includes a new deploy section with reservations for GPU devices, specifying the driver, count, and capabilities. This change enables the Llama app to utilize GPU resources for improved performance in AI processing.
2024-06-18 22:11:53 +01:00
Simon Larsen
df20f343e9 refactor: Update Llama app to log prompt and output to console
This commit updates the Llama app to log the prompt and output to the console for debugging purposes. It adds print statements to log the prompt before processing and the generated output after processing. This change improves the development workflow by providing visibility into the input and output of the AI model.
2024-06-18 22:08:42 +01:00
Simon Larsen
e4ade513ce feat: Add start-ai, build-ai, and force-build-ai scripts for AI services
This commit adds three new scripts, start-ai, build-ai, and force-build-ai, to the package.json file. These scripts are used to start, build, and force-build the AI services respectively. They are configured to export environment variables, run the prerun script, and execute the necessary docker compose commands with the specified configuration file. This addition enhances the development workflow by providing convenient commands for managing the AI services.
2024-06-18 21:49:00 +01:00
Simon Larsen
3c2af1dc38 refactor: Remove unused Llama service from docker-compose.dev.yml 2024-06-18 21:47:40 +01:00
Simon Larsen
af5d714642 refactor: Add generate-build-number job as dependency in infrastructure-agent-deploy workflow 2024-06-18 21:41:57 +01:00
Simon Larsen
3f315be279 refactor: Update Llama app to use local model path instead of model ID
This commit updates the Llama app to use a local model path instead of a model ID. The model path is set to "/app/Models/Meta-Llama-3-8B-Instruct". This change improves the reliability and performance of the app by directly referencing the model file instead of relying on an external model ID.
2024-06-18 21:41:29 +01:00
Simon Larsen
095493cec9 Merge branch 'release' into ai-copilot-main 2024-06-18 19:29:03 +01:00
Simon Larsen
3bdf87f34f refactor: Update github-release workflow to include generate-build-number job
This commit modifies the github-release workflow to include the generate-build-number job as a dependency. Previously, the workflow only depended on the test-e2e-release-saas and test-e2e-release-self-hosted jobs. By adding the generate-build-number job, we ensure that the necessary build number is generated before the github-release job is executed. This change improves the overall release process by providing accurate build information for the release.
2024-06-18 19:02:46 +01:00
Simon Larsen
26bb6f1e74 refactor: Update Dockerfile.tpl to expose port 8547 instead of port 80
This commit modifies the Dockerfile.tpl file to update the EXPOSE directive. The port number is changed from 80 to 8547 to align with the port used by the Llama application. This change ensures that the Llama application is accessible from outside the container on the correct port.
2024-06-18 18:42:11 +01:00
Simon Larsen
20db81a5f6 refactor: Update secrets.yaml to add keys for postgres, redis, and clickhouse
This commit modifies the secrets.yaml file to add keys for postgres, redis, and clickhouse. These keys are used to store sensitive information such as passwords for external services. By including these keys, we ensure that the necessary secrets are available for the application to connect to these services securely.
2024-06-18 13:59:49 +01:00
Simon Larsen
f9b097a112 refactor: Improve page load performance by lazy loading images
This commit updates the Register.spec.ts file to improve the page load performance by implementing lazy loading for images. It ensures that the dashboard page is up before signing up the user and reloads the page if it fails to load. These changes enhance the user experience by optimizing the loading time of the registration page.
2024-06-18 13:45:51 +01:00
Simon Larsen
b70b70e27e Merge remote-tracking branch 'origin/snyk-upgrade-e85a20baed82dca9b23b1e6d43058393' 2024-06-18 13:35:13 +01:00
Simon Larsen
93df459662 Merge pull request #1487 from OneUptime/snyk-upgrade-07f6caf2da548bfa468d50536046a7d4
[Snyk] Upgrade playwright from 1.44.0 to 1.44.1
2024-06-18 13:34:00 +01:00
Simon Larsen
1aceb81c85 Merge pull request #1490 from OneUptime/snyk-upgrade-3e8efba0b61bba9ccdff4fbf1899abe8
[Snyk] Upgrade posthog-js from 1.133.0 to 1.135.2
2024-06-18 13:33:53 +01:00
Simon Larsen
023aea94c2 Merge pull request #1491 from OneUptime/snyk-upgrade-172a12f064e961ffc3354f68f2eb7cb7
[Snyk] Upgrade @babel/runtime from 7.24.5 to 7.24.6
2024-06-18 13:33:46 +01:00
Simon Larsen
d8dff468ab refactor: Update Cookie.ts to accept string as key parameter
This commit modifies the Cookie.ts file to update the setItem method signature. The key parameter now accepts both CookieName and string types, allowing for more flexibility when setting cookies. This change improves the usability and versatility of the Cookie utility class.
2024-06-18 13:33:09 +01:00
Simon Larsen
082d800c34 refactor: Update ProbeMonitorResponseService to include remediation notes
This commit updates the ProbeMonitorResponseService class to include a check for the presence of remediation notes in the criteriaIncident object. If remediation notes are provided, they are assigned to the incident object. This change enhances the functionality of the ProbeMonitorResponseService by allowing the storage and retrieval of remediation notes for incidents.
2024-06-18 13:08:56 +01:00
Simon Larsen
e7f4c962b8 refactor: Update incident form to include remediation notes
This commit updates the incident form to include a field for remediation notes. This allows users to provide specific steps that should be taken to resolve the incident. By adding this feature, we enhance the incident management process and improve collaboration among team members.
2024-06-18 13:07:59 +01:00
Simon Larsen
f6074fe8f4 refactor: Update MonitorCriteriaIncident component to remove unused imports 2024-06-18 13:00:10 +01:00
Simon Larsen
b45678f167 refactor: Update MonitorCriteriaIncidentForm UI with incident description and remediation notes
This commit updates the MonitorCriteriaIncidentForm component to include fields for incident description and remediation notes. The incident description field now supports markdown formatting and has a placeholder text for when no description is entered. Similarly, the remediation notes field also supports markdown formatting and has a placeholder text for when no notes are entered. These changes enhance the user experience by providing a more comprehensive incident form.
2024-06-18 12:59:22 +01:00
Simon Larsen
6d53678135 refactor: Add new migration for SchemaMigrations and update MonitorCriteriaIncidentForm UI 2024-06-18 12:56:12 +01:00
Simon Larsen
7b77dd4a53 refactor: Add new migration for SchemaMigrations and update MonitorCriteriaIncidentForm UI
This commit adds a new migration file for the SchemaMigrations table, introducing a new migration named MigrationName1718711669847. This migration is imported in the Index.ts file of the SchemaMigrations directory. Additionally, the MonitorCriteriaIncidentForm component is updated to change the title of the "Show Advanced Fields" button to "Add Remediation Notes for an engineer to help resolve this incident." The button is also given a left margin of -3 pixels to adjust its positioning. These changes improve the database schema management and enhance the user experience in the incident form.
2024-06-18 12:55:52 +01:00
Simon Larsen
51bb54e771 refactor: Add placeholder text for name and email fields in user profile form 2024-06-18 12:51:17 +01:00
Simon Larsen
48b095f548 refactor: Update import statements for TimezoneUtil in multiple files 2024-06-18 12:44:54 +01:00
Simon Larsen
786ec6ce7a refactor: Update import statements for TimezoneUtil in multiple files 2024-06-18 11:35:04 +01:00
Simon Larsen
401fbb58e3 refactor: Update import statements for TimezoneUtil in multiple files 2024-06-18 10:38:45 +01:00
Simon Larsen
5243ae6b8d Merge branch 'master' of github.com:OneUptime/oneuptime 2024-06-18 10:05:03 +01:00
Simon Larsen
a06a0f1d16 refactor: Update import statements for TimezoneUtil in multiple files 2024-06-17 21:01:44 +01:00
Simon Larsen
e0bc906484 Merge pull request #1489 from hg13bs/patch-1
Remove duplicate "Swift" entry
2024-06-17 19:29:01 +01:00
Simon Larsen
139ff2d638 refactor: Add user timezone support to login and user profile
This commit adds support for user timezones in the login and user profile functionality. It updates the LoginUtil and User classes to include methods for setting and retrieving the user's timezone. Additionally, the UserTimezoneInit component in the Dashboard module is updated to display and allow the user to update their timezone. This change improves the user experience by providing accurate timezone information and allows for better handling of time-related functionality throughout the application.
2024-06-17 17:27:34 +01:00
Simon Larsen
7be603ef08 refactor: Update import statements for TimezoneUtil in multiple files 2024-06-17 17:22:00 +01:00
Simon Larsen
d23548f984 refactor: Update error message in OneUptimeDate class
This commit updates the error message in the `asDateForDatabaseQuery` method of the `OneUptimeDate` class. It now includes the `date.toString()` value to provide more information about the invalid date. This change improves error handling and debugging capabilities.
2024-06-17 17:09:17 +01:00
Simon Larsen
3573e59634 refactor: Update getCurrentTimezoneString method in Date.ts
This commit refactors the getCurrentTimezoneString method in the Date.ts file. It replaces the usage of moment.tz.guess() with this.getCurrentTimezone() to improve code readability and maintainability. Additionally, it adds a check to prepend "GMT" to the zone abbreviation if it starts with a "+" or "-" sign. This change ensures consistent formatting of the timezone string returned by the method.
2024-06-17 16:23:49 +01:00
Simon Larsen
c4e4e7e488 refactor: Update import statements for TimezoneUtil in Monitor.ts
This commit updates the import statements in the Monitor.ts file. It changes the import statement for the TimezoneUtil module to import from "Common/Types/Timezone" instead of the previous import path. This change ensures that the correct module is imported, improving the accuracy and reliability of the code.
2024-06-17 16:14:08 +01:00
Simon Larsen
be3e8447ab refactor: Add data-testid attributes to Toast component
This commit adds data-testid attributes to the Toast component in order to improve testability. The "toast" element, "toast-icon" element, "title" element, "description" element, and "close-button" element now have corresponding data-testid attributes. This change allows for more precise targeting of elements in automated tests, enhancing the reliability and accuracy of the test suite.
2024-06-17 16:07:04 +01:00
Simon Larsen
2615e39c19 refactor: Update import statements for TimezoneUtil in multiple files 2024-06-17 15:58:30 +01:00
Simon Larsen
49b6550581 refactor: Update import statements for TimezoneUtil in UserProfile/Index.tsx 2024-06-17 15:57:32 +01:00
Simon Larsen
e870efc3c4 refactor: Remove unused code and update import statements 2024-06-17 15:33:47 +01:00
Simon Larsen
38862adf5a refactor: Remove unused code and update import statements
This commit removes unused code and updates import statements in multiple files. It removes the unused TimezoneCode enum from the Common/Types/TimezoneCode.ts file and updates the import statements in the affected files to import Timezone from Common/Types/Timezone instead of TimezoneCode. These changes improve the accuracy and reliability of the code.
2024-06-17 13:43:09 +01:00
Simon Larsen
06b92e2745 feat: Add subscriber timezones functionality to SubscriberSettings 2024-06-17 13:18:44 +01:00
Simon Larsen
1a8e0682c7 feat: Add subscriber timezones functionality to SubscriberSettings
This commit adds the functionality to select subscriber timezones in the SubscriberSettings component. It introduces a new field, "Subscriber Timezones", which allows subscribers to choose their preferred timezones for receiving notifications. The selected timezones are displayed when subscribers receive notifications. This feature enhances the user experience by providing localized time information.

Refactor import statements for TimezoneUtil in UserProfile/Index.tsx

This commit updates the import statements in the UserProfile/Index.tsx file to import TimezoneUtil from "Common/Types/Timezone" instead of "CommonUI/src/Utils/Timezone". This change ensures that the correct module is imported, improving the accuracy and reliability of the code.

Refactor import statements for TimezoneUtil in UserProfile/Index.tsx

This commit updates the import statements in the UserProfile/Index.tsx file to import TimezoneUtil from "Common/Types/Timezone" instead of "CommonUI/src/Utils/Timezone". This change ensures that the correct module is imported, improving the accuracy and reliability of the code.

Refactor import statements for TimezoneUtil in UserProfile/Index.tsx

This commit updates the import statements in the UserProfile/Index.tsx file to import TimezoneUtil from "Common/Types/Timezone" instead of "CommonUI/src/Utils/Timezone". This change ensures that the correct module is imported, improving the accuracy and reliability of the code.

Refactor import statements for TimezoneUtil in UserProfile/Index.tsx

This commit updates the import statements in the UserProfile/Index.tsx file to import TimezoneUtil from "Common/Types/Timezone" instead of "CommonUI/src/Utils/Timezone". This change ensures that the correct module is imported, improving the accuracy and reliability of the code.

refactor: Update retries value in playwright.config.ts

This commit updates the retries value in the playwright.config.ts file to a fixed value of 3. This change ensures that the tests are retried up to 3 times, improving the reliability of the test suite.

refactor: Update import statements for jest in test files

This commit updates the import statements for jest in multiple test files to use the correct module name "jest" instead of "globals". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.

refactor: Update docker-compose command in test.e2e.yaml

This commit updates the docker-compose command in the test.e2e.yaml file to remove the duplicate `e2e` service name. This change improves the clarity and efficiency of the script.

fix: job dependencies

Merge branch 'release' of github.com:OneUptime/oneuptime into release

refactor: Clear user data and remove cookies on authentication error
2024-06-17 11:03:37 +01:00
Simon Larsen
e0189356d5 refactor: Remove TimezoneCode enum and update import statements
This commit removes the TimezoneCode enum from the Common/Types/TimezoneCode.ts file. The enum is no longer needed and can be safely deleted. Additionally, it updates the import statements in the affected files to import Timezone from Common/Types/Timezone instead of TimezoneCode. This change ensures that the correct module is imported and improves the accuracy and reliability of the code.
2024-06-17 10:56:59 +01:00
Simon Larsen
dad07b9f80 refactor: Update import statements for TimezoneUtil in UserProfile/Index.tsx
This commit updates the import statements in the UserProfile/Index.tsx file. It changes the import statement for TimezoneUtil from "CommonUI/src/Utils/Timezone" to "Common/Types/Timezone". This change ensures that the correct module is imported, improving the accuracy and reliability of the code.
2024-06-17 10:47:23 +01:00
Simon Larsen
8d59710306 refactor: Update import statements for TimezoneUtil in UserProfile/Index.tsx
This commit updates the import statements in the UserProfile/Index.tsx file. It changes the import statement for TimezoneUtil from "CommonUI/src/Utils/Timezone" to "Common/Types/Timezone". This change ensures that the correct module is imported, improving the accuracy and reliability of the code.
2024-06-17 10:42:43 +01:00
Simon Larsen
5e9227eb4f refactor: Add TimezoneUtil to UserProfile/Index.tsx
This commit refactors the import statements in the UserProfile/Index.tsx file. It updates the import statement for TimezoneUtil from "CommonUI/src/Utils/Timezone" to "Common/Types/Timezone". This change ensures that the correct module is imported, improving the accuracy and reliability of the code.
2024-06-17 10:41:04 +01:00
snyk-bot
1e63173564 fix: upgrade @babel/runtime from 7.24.5 to 7.24.6
Snyk has created this PR to upgrade @babel/runtime from 7.24.5 to 7.24.6.

See this package in npm:
@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-06-15 06:14:37 +00:00
snyk-bot
76fded39ee fix: upgrade posthog-js from 1.133.0 to 1.135.2
Snyk has created this PR to upgrade posthog-js from 1.133.0 to 1.135.2.

See this package in npm:
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-06-15 06:12:15 +00:00
hg13
c82f3c1f71 Remove duplicate "Swift" entry 2024-06-15 02:06:24 +03:00
Simon Larsen
24d9219a62 refactor: Update retries value in playwright.config.ts
This commit updates the retries value in the playwright.config.ts file. The previous value was determined based on the environment variable "CI", but it has been changed to a fixed value of 3. This change ensures that the tests are retried up to 3 times, improving the reliability of the test suite.
2024-06-14 19:38:03 +01:00
Simon Larsen
6ed588d7aa refactor: Update import statements for jest in test files
This commit updates the import statements for jest in multiple test files. The previous import statements used "globals" as the module name, which is incorrect. The correct module name is "jest". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.
2024-06-14 18:54:53 +01:00
Simon Larsen
17c1862eac ```text
refactor: Update docker-compose command in test.e2e.yaml

This commit updates the docker-compose command in the test.e2e.yaml file. The previous command included the `e2e` service name twice, which is unnecessary. The updated command removes the duplicate service name, improving the clarity and efficiency of the script.
2024-06-14 18:34:11 +01:00
Simon Larsen
37b1846363 fix job dependencies 2024-06-14 18:21:51 +01:00
Simon Larsen
da3e6bc3af Merge branch 'release' of github.com:OneUptime/oneuptime into release 2024-06-14 18:02:55 +01:00
Simon Larsen
a35ea2ba66 refactor: Clear user data and remove cookies on authentication error 2024-06-14 18:02:36 +01:00
Simon Larsen
b2fa2e40f4 refactor: Clear user data and remove cookies on authentication error
This commit clears the user data and removes cookies when an authentication error occurs. This ensures that the user is logged out and any stored data is removed, preventing unauthorized access. The code change is made in the API.ts file.
2024-06-14 18:01:51 +01:00
Simon Larsen
babecb5170 chore: Update axios and posthog-js dependencies
This commit updates the axios and posthog-js dependencies in the package-lock.json file. The axios dependency is updated from version 1.7.1 to 1.7.2, and the posthog-js dependency is updated from version 1.131.4 to 1.133.0. This update ensures that the latest stable versions of these dependencies are used, improving the functionality and security of the application.
2024-06-14 17:35:33 +01:00
Simon Larsen
6e0e643337 refactor: Update import statements for jest in test files
This commit updates the import statements for jest in multiple test files. The previous import statements used "globals" as the module name, which is incorrect. The correct module name is "jest". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.
2024-06-14 17:01:06 +01:00
Simon Larsen
2a59faa6b4 refactor: Update import statements for jest in test files
This commit updates the import statements for jest in multiple test files. The previous import statements used "globals" as the module name, which is incorrect. The correct module name is "jest". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.
2024-06-14 16:54:53 +01:00
Simon Larsen
9d7d44afda refactor: Update import statements for jest in test files
This commit updates the import statements for jest in multiple test files. The previous import statements used "globals" as the module name, which is incorrect. The correct module name is "jest". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.
2024-06-14 16:42:55 +01:00
Simon Larsen
8f1e67da3a refactor: Remove ignore for all JavaScript files in eslint configuration 2024-06-14 15:44:31 +01:00
Simon Larsen
c09f863d58 refactor: Remove unused imports and update eslint configuration 2024-06-14 15:42:45 +01:00
Simon Larsen
3c8c2a3feb refactor: Update import statements for jest in test files
This commit updates the import statements for jest in multiple test files. The previous import statements used "globals" as the module name, which is incorrect. The correct module name is "jest". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.
2024-06-14 15:42:33 +01:00
Simon Larsen
4c4169e245 refactor: Update import statements for jest in test files 2024-06-14 14:24:45 +01:00
Simon Larsen
34c5cb1e94 refactor: Update import statements for jest in test files 2024-06-14 14:20:24 +01:00
Simon Larsen
f5fca46e5b refactor: Update import statements for jest in test files
This commit updates the import statements for jest in multiple test files. The previous import statements used "globals" as the module name, which is incorrect. The correct module name is "jest". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.
2024-06-14 14:16:56 +01:00
Simon Larsen
48e3c24c6e refactor: Update import statement for jest in setupTest.js
This commit updates the import statement for jest in the setupTest.js file. The previous import statement used "globals" as the module name, which is incorrect. The correct module name is "jest". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.
2024-06-14 14:11:32 +01:00
Simon Larsen
e9c94876c0 refactor: Update import statement for jest in setupTest.js
This commit updates the import statement for jest in the setupTest.js file. The previous import statement used "globals" as the module name, which is incorrect. The correct module name is "jest". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.
2024-06-14 14:06:41 +01:00
Simon Larsen
fcd6c8ea7d refactor: Remove unused imports and update eslint configuration 2024-06-14 13:50:52 +01:00
Simon Larsen
81726ce7b2 refactor: Update import statement for jest in setupTest.js 2024-06-14 13:40:33 +01:00
Simon Larsen
6349c60bdb refactor: Update import statement for jest in setupTest.js
This commit updates the import statement for jest in the setupTest.js file. The previous import statement used "globals" as the module name, which is incorrect. The correct module name is "jest". This change ensures that the jest module is imported correctly, improving the accuracy and reliability of the test setup.
2024-06-14 13:01:38 +01:00
Simon Larsen
74cf119444 refactor: Remove unused globals.jquery from eslint configuration 2024-06-14 12:36:11 +01:00
Simon Larsen
ba6ac2e32e refactor: Update symbol type to use lowercase 'symbol' in ColumnAccessControl files 2024-06-14 12:34:56 +01:00
Simon Larsen
1498656a43 refactor: Update symbol type to use lowercase 'symbol' in ColumnAccessControl files 2024-06-14 12:15:36 +01:00
Simon Larsen
70a2a3993b refactor: Update symbol type to use lowercase 'symbol' in ColumnAccessControl files 2024-06-14 12:09:53 +01:00
Simon Larsen
5152d5de12 refactor: Remove @trivago/prettier-plugin-sort-imports and update eslint configuration
This commit removes the "@trivago/prettier-plugin-sort-imports" plugin from the ".prettierrc.json" file and updates the eslint configuration in the "package.json" file. The plugin was removed because it is no longer needed and caused conflicts with the updated eslint configuration. This change improves the codebase by simplifying the prettier configuration and ensuring compatibility with the updated eslint rules.
2024-06-14 12:07:16 +01:00
Simon Larsen
2999e539b3 refactor: Update symbol type to use lowercase 'symbol' in ColumnAccessControl files 2024-06-14 12:01:14 +01:00
Simon Larsen
8ce432256c refactor: Update symbol type to use lowercase 'symbol' in ColumnAccessControl files
This commit updates the symbol type from 'Symbol' to 'symbol' in the ColumnAccessControl files. The use of 'symbol' aligns with the TypeScript convention for symbol types. This change improves code consistency and readability.
2024-06-14 11:53:27 +01:00
Simon Larsen
ddbfbac802 refactor: Remove unused files and update eslint configuration 2024-06-14 11:51:38 +01:00
Simon Larsen
1d6a7ee1fa feat: Add timezone dropdown to user profile page
This commit adds a new dropdown component for selecting the timezone on the user profile page. The dropdown options are populated using the `TimezoneCode` enum from the `Common/Types/TimezoneCode.ts` file. Users can now select their timezone, which will be used for all date and time related notifications sent to them. This enhancement improves the user experience by providing a convenient way to set the timezone preference.
2024-06-14 11:21:25 +01:00
Simon Larsen
0d97f0447a refactor: Improve error handling in API class
This commit refactors the API class in API.ts to improve error handling. The `getFriendlyErrorMessage` method now includes additional error cases and returns more specific error messages for network errors, timeouts, request aborts, cancellations, connection issues, and SSL certificate expiration. This change enhances the user experience by providing clearer and more informative error messages.
2024-06-14 10:57:19 +01:00
Simon Larsen
36a13b60fe refactor: Update notification subjects for incidents and announcements 2024-06-14 10:20:57 +01:00
Simon Larsen
64f4fcf829 refactor: Update notification subjects for incidents and announcements 2024-06-14 10:06:38 +01:00
Simon Larsen
1ff7c84d82 refactor: Update notification subjects for incidents and announcements
This commit updates the notification subjects for incidents and announcements in the SendNotificationToSubscribers.ts files. The subjects now include a prefix indicating the type of notification (e.g., [Incident], [Announcement]) followed by the status page name. This change improves the clarity and consistency of the notification emails and SMS messages sent to subscribers.
2024-06-14 10:05:48 +01:00
Simon Larsen
da623d4d34 increase timeout 2024-06-14 10:00:03 +01:00
snyk-bot
2efb630640 fix: upgrade playwright from 1.44.0 to 1.44.1
Snyk has created this PR to upgrade playwright from 1.44.0 to 1.44.1.

See this package in npm:
playwright

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/49c81d9c-12c2-4e8e-b9e8-72f98b1b595c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-14 05:19:10 +00:00
Simon Larsen
affa492ce3 refactor: Improve SSL certificate error handling
This commit refactors the API class in API.ts to improve the handling of SSL certificate errors. The `getFriendlyErrorMessage` method now includes specific error messages for different SSL certificate issues, such as expired certificates, certificates signed by unknown authorities, and self-signed certificates. This enhancement enhances the user experience by providing clearer and more informative error messages for SSL certificate errors.
2024-06-13 21:50:59 +01:00
Simon Larsen
5ae3a5b5ee refactor: Improve error handling in API class
The API class in API.ts has been updated to improve error handling. The getFriendlyErrorMessage method now includes additional error cases and returns more specific error messages for network errors, timeouts, request aborts, cancellations, connection issues, and SSL certificate expiration. This change enhances the user experience by providing clearer and more informative error messages.
2024-06-13 21:50:10 +01:00
Simon Larsen
5716ab2445 refactor: Update API error handling and display
The API class in API.ts has been updated to improve error handling and display. The getFriendlyErrorMessage method now includes additional error cases and returns more specific error messages for network errors, timeouts, request aborts, cancellations, connection issues, and SSL certificate expiration. This change enhances the user experience by providing clearer and more informative error messages.
2024-06-13 21:45:56 +01:00
Simon Larsen
a66a04456b refactor: Update endpoint URLs for status check script 2024-06-13 21:44:49 +01:00
Simon Larsen
e97a78eaeb Merge branch 'ai-copilot-main' 2024-06-13 21:12:48 +01:00
Simon Larsen
57f41a908a refactor: Improve login test to handle missing user credentials 2024-06-13 21:11:54 +01:00
Simon Larsen
f42291a428 refactor: Update status check URLs for Dashboard, Status Page, and Accounts
The status-check.sh script has been modified to update the endpoint URLs for checking the status of the Dashboard, Status Page, and Accounts services. The URLs now include "/status/ready" at the end, ensuring that the services are ready to handle requests. This change improves the accuracy and reliability of the status check script.
2024-06-13 21:05:37 +01:00
Simon Larsen
4fa9adfc7d refactor: Update login test to handle missing user credentials
The login test in Login.spec.ts has been updated to handle cases where the user credentials (email and password) are missing. Previously, the test would only check if the user is registered, but now it also checks if the registered user email and password are available. This change ensures that the test behaves correctly in all scenarios and improves the reliability of the login functionality.
2024-06-13 18:49:19 +01:00
Simon Larsen
3e2e581e52 ```text
refactor: Update artifact upload paths in release and test workflows

The artifact upload paths in the release.yml, test-release.yaml, and test.e2e.yaml workflows have been updated to include the entire E2E directory instead of specific subdirectories. This change simplifies the configuration and ensures that all necessary files are included in the artifacts.
2024-06-13 18:39:02 +01:00
Simon Larsen
2a3c34af95 refactor: Add endpoint to retrieve Copilot events by file
This commit adds a new endpoint to the CodeRepositoryAPI class in CodeRepositoryAPI.ts. The endpoint allows users to retrieve Copilot events for a specific file in the code repository. It takes in a secret key, file path, and service catalog ID as parameters and returns a list of Copilot events. This enhancement improves the functionality and usability of the API.
2024-06-13 14:55:31 +01:00
Simon Larsen
170b79e4cf refactor: Add CopilotEventStatus column to CopilotEvent model
This commit adds a new column, CopilotEventStatus, to the CopilotEvent model in the CopilotEvent.ts file. The CopilotEventStatus column represents the status of a Copilot event that was triggered for a file in the code repository. This enhancement improves the functionality and flexibility of the CopilotEvent model.
2024-06-13 14:39:26 +01:00
Simon Larsen
c15b8bb951 refactor: Update CodeRepositoryUtil to include serviceRepository parameter in createBranch and createOrCheckoutBranch 2024-06-13 14:11:09 +01:00
Simon Larsen
17c47f7d89 refactor: Update ServiceFileTypesUtil to include common directories and files to ignore 2024-06-13 13:32:16 +01:00
Simon Larsen
a406287215 refactor: Update branch name in Copilot/Index.ts and add GITHUB_USERNAME to .env.example
This commit updates the branch name in Copilot/Index.ts from 'test-branch-3' to 'test-branch-4'. Additionally, it adds the GITHUB_USERNAME variable to the .env.example file. These changes improve the accuracy and functionality of the code.
2024-06-13 13:10:14 +01:00
Simon Larsen
a73b050a4d refactor: Update CodeRepositoryUtil to include serviceRepository parameter in createBranch and createOrCheckoutBranch
This commit modifies the CodeRepositoryUtil class in CodeRepository.ts to include a new parameter, serviceRepository, in the createBranch and createOrCheckoutBranch methods. This change allows for better integration with the ServiceRepository class and improves the flexibility and maintainability of the code.
2024-06-13 12:47:42 +01:00
Simon Larsen
224fb2e887 Merge pull request #1479 from OneUptime/snyk-upgrade-fb48859c6d1e1f5c6c32c4f5abf203c1
[Snyk] Upgrade posthog-js from 1.132.0 to 1.133.0
2024-06-13 12:34:04 +01:00
Simon Larsen
96d4131614 refactor: Add createPullRequest method to HostedCodeRepository
The HostedCodeRepository class in HostedCodeRepository.ts has been updated to include a new method, createPullRequest. This method allows for the creation of pull requests in the code repository. This change enhances the functionality and flexibility of the HostedCodeRepository class.
2024-06-13 12:26:49 +01:00
snyk-bot
cda00d5238 fix: upgrade posthog-js from 1.132.0 to 1.133.0
Snyk has created this PR to upgrade posthog-js from 1.132.0 to 1.133.0.

See this package in npm:
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-06-13 00:27:42 +00:00
Simon Larsen
dea385ad44 refactor: Update ServiceFileTypesUtil to include common directories and files to ignore
The ServiceFileTypesUtil class in ServiceFileTypes.ts has been updated to include two new methods: getCommonDirectoriesToIgnore and getCommonFilesToIgnore. These methods return arrays of common directories and files that should be ignored in code repositories. This change improves the functionality and maintainability of the code by providing a centralized way to define and retrieve the directories and files to ignore.
2024-06-12 22:10:47 +01:00
Simon Larsen
21973401fb refactor: Improve file extension handling in CodeRepositoryUtil
The CodeRepositoryUtil class in CodeRepository.ts has been refactored to improve the handling of file extensions. The changes include:
- Simplifying the logic for skipping files with unsupported extensions
- Adding support for accepted file extensions in the getFilesInDirectory method

These improvements enhance the functionality and maintainability of the code.
2024-06-12 22:07:01 +01:00
Simon Larsen
d153ad9bf7 refactor: Update ServiceRepository to include serviceLanguage field
The ServiceRepository class in ServiceRepository.ts has been updated to include a new field, serviceLanguage. This field is of type ServiceLanguage and is required. This change allows for better organization and identification of code repositories based on their programming language.
2024-06-12 21:27:00 +01:00
Simon Larsen
2ce8ba6295 refactor: Sanitize file paths in CodeRepositoryUtil and LocalFile
This commit refactors the CodeRepositoryUtil and LocalFile classes to include a new method, sanitizeFilePath, which removes double slashes from file paths. This change ensures that file paths are properly formatted and improves the reliability and maintainability of the code.
2024-06-12 21:05:37 +01:00
Simon Larsen
572130d349 Merge branch 'master' of github.com:OneUptime/oneuptime 2024-06-12 20:21:07 +01:00
Simon Larsen
f0cb049266 refactor: Update CodeRepositoryView component in Index.tsx
The CodeRepositoryView component in Index.tsx has been updated to include a new field for the repository name. This change allows for better organization and identification of code repositories within the system.
2024-06-12 20:20:40 +01:00
Simon Larsen
cab5af6645 refactor: Add organizationName and repositoryName fields to CodeRepository
The CodeRepository entity has been updated to include two new fields: organizationName and repositoryName. These fields are of type character varying(100) and are required. This change allows for better organization and identification of code repositories within the system.
2024-06-12 20:18:30 +01:00
Simon Larsen
e77b923dc1 Merge pull request #1472 from OneUptime/ai-copilot-main
Copilot Main
2024-06-12 19:20:49 +01:00
Simon Larsen
8325c06ca3 refactor: Update status check URLs for Dashboard and Status Page
The status-check.sh script has been modified to update the endpoint URLs for checking the status of the Dashboard and Status Page services. The URLs now include "/status/ready" at the end, ensuring that the services are ready to handle requests. This change improves the accuracy and reliability of the status check script.
2024-06-12 17:27:22 +01:00
Simon Larsen
b66f56bc12 refactor: Update endpoint status check URLs to include "/ready"
The status-check.sh script has been updated to modify the endpoint URLs for checking the status of various services. The URLs now include "/ready" at the end, indicating that the services are ready to handle requests. This change ensures that the status check accurately reflects the readiness of the services and improves the reliability of the script.
2024-06-12 17:25:08 +01:00
Simon Larsen
2de2926d79 refactor: Add MigrationName1718203144945 to SchemaMigrations/Index.ts
The SchemaMigrations/Index.ts file has been updated to include the MigrationName1718203144945 migration. This change ensures that the new migration is included in the list of schema migrations, allowing for proper database versioning and management.
2024-06-12 15:39:55 +01:00
Simon Larsen
fd13e91aac refactor: Update ServiceRepository to use number type for limitNumberOfOpenPullRequestsCount
The ServiceRepository class in ServiceRepository.ts has been updated to use the number type for the limitNumberOfOpenPullRequestsCount property instead of the string type. This change ensures that the property is correctly typed and improves the accuracy and reliability of the code.
2024-06-12 15:38:54 +01:00
Simon Larsen
cf7d4c7720 refactor: Update GitHubUtil to use HostedCodeRepository/HostedCodeRepository 2024-06-12 15:11:50 +01:00
Simon Larsen
b1a76e97ce Merge pull request #1477 from OneUptime/snyk-upgrade-4dc39844f330c9a5f3d5be2668e24709
[Snyk] Upgrade axios from 1.7.0 to 1.7.2
2024-06-12 14:19:29 +01:00
Simon Larsen
7d8036e3eb refactor: Update GitHubUtil to use HostedCodeRepository/HostedCodeRepository
The GitHubUtil class in GitHub.ts has been updated to import the HostedCodeRepository class from the correct file path, "HostedCodeRepository/HostedCodeRepository". This change ensures that the correct class is imported and used, improving the functionality and maintainability of the code.
2024-06-12 13:18:20 +01:00
Simon Larsen
c7344a7624 refactor: Remove commented out code and unused imports
The CodeRepositoryType.ts file has been refactored to remove commented out code and unused imports. This change improves code cleanliness and reduces unnecessary clutter in the file.
2024-06-12 12:25:12 +01:00
Simon Larsen
42253e4e50 refactor: Update GitHub token handling in Config.ts 2024-06-12 11:59:22 +01:00
Simon Larsen
64acf372d6 refactor: Update GitHub token handling in Config.ts
The Config.ts file has been updated to handle the GitHub token more efficiently. The GetGitHubToken function now checks the repository type and throws an exception if a GitHub token is required but not provided. This change improves the security and reliability of the application by ensuring that the correct token is used for GitHub repositories.
2024-06-12 11:31:50 +01:00
snyk-bot
80f4238618 fix: upgrade axios from 1.7.0 to 1.7.2
Snyk has created this PR to upgrade axios from 1.7.0 to 1.7.2.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/49c81d9c-12c2-4e8e-b9e8-72f98b1b595c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-12 04:53:12 +00:00
Simon Larsen
2fe54c46c1 Merge pull request #1475 from OneUptime/snyk-upgrade-d4dca93f2a1a1e81fe41908a5a826632
[Snyk] Upgrade posthog-js from 1.131.4 to 1.132.0
2024-06-11 19:55:56 +01:00
Simon Larsen
266b046b9e Merge pull request #1474 from OneUptime/snyk-upgrade-8df461c2c34dd57552a05c7122331e04
[Snyk] Upgrade axios from 1.7.1 to 1.7.2
2024-06-11 19:55:45 +01:00
snyk-bot
f311841df2 fix: upgrade posthog-js from 1.131.4 to 1.132.0
Snyk has created this PR to upgrade posthog-js from 1.131.4 to 1.132.0.

See this package in npm:
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-06-11 18:09:12 +00:00
snyk-bot
26319ff3c8 fix: upgrade axios from 1.7.1 to 1.7.2
Snyk has created this PR to upgrade axios from 1.7.1 to 1.7.2.

See this package in npm:
axios

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-06-11 18:09:07 +00:00
Simon Larsen
e719bb3b70 refactor: Add doNotShowWhenEditing flag to ServiceRepositoryPage component
The ServiceRepositoryPage component has been updated to include a new flag, doNotShowWhenEditing, which controls whether a certain field should be displayed when editing a service. This change improves the user experience by allowing for more flexibility in customizing the form fields based on the editing mode.
2024-06-11 18:38:37 +01:00
Simon Larsen
d0255c1e33 feat: Enable editing of services in ServiceRepositoryPage 2024-06-11 18:37:49 +01:00
Simon Larsen
f66241d12f Merge pull request #1473 from OneUptime/master
Release
2024-06-11 18:36:34 +01:00
Simon Larsen
52f4c74908 refactor: Update noItemsMessage prop types to include ReactElement
The prop types for the noItemsMessage prop have been updated in multiple components to include the ReactElement type. This change allows for passing React elements as the no items message, providing more flexibility in customizing the message's content and appearance.
2024-06-11 18:34:25 +01:00
Simon Larsen
eeb6904c2d refactor: Update title of Service Catalog field in ServiceRepositoryPage
The title of the Service Catalog field in the ServiceRepositoryPage component has been updated from "Service Catalog" to "Service". This change improves clarity and consistency in the user interface.
2024-06-11 18:31:10 +01:00
Simon Larsen
6fd37c62b8 refactor: Add MigrationName1718126316684 to SchemaMigrations/Index.ts
This commit adds the MigrationName1718126316684 to the list of schema migrations in the Index.ts file. The new migration introduces changes to the database schema and ensures that the codebase is up to date with the latest schema changes. This enhancement improves the maintainability and functionality of the application.
2024-06-11 18:29:19 +01:00
Simon Larsen
16b761e498 feat: Add option to enable pull requests for services in ServiceRepositoryPage
This commit adds a new field to the ServiceRepositoryPage component, allowing users to enable or disable pull requests for a service. When enabled, OneUptime will create pull requests for the service and automatically improve the code. This enhancement provides more control over pull request generation and improves the functionality of the ServiceRepositoryPage.
2024-06-11 18:21:51 +01:00
Simon Larsen
9b1e702c64 refactor: Add enablePullRequests property to ServiceRepository model
The enablePullRequests property has been added to the ServiceRepository model. This property allows for enabling or disabling automatic pull request creation by Copilot for this service. By default, enablePullRequests is set to true, indicating that Copilot will create pull requests for this service to automatically improve the codebase. This change enhances the functionality of the ServiceRepository model and provides more control over pull request generation.
2024-06-11 18:19:51 +01:00
Simon Larsen
f10fa9a48e ```text
refactor: Update defaultValue property in Field interface

The defaultValue property in the Field interface has been updated to include the number type. This change allows for setting default values of type number in form fields.
2024-06-11 18:15:09 +01:00
Simon Larsen
de91346155 refactor: Remove CopilotServiceService and update references to ServiceRepositoryService 2024-06-11 17:49:14 +01:00
Simon Larsen
629a442ff7 refactor: Remove CopilotServiceService and update references to ServiceRepositoryService
This commit removes the CopilotServiceService module, which is no longer needed. It also updates the references to the CopilotService class to use the ServiceRepository class instead. This change improves code organization and consistency by using the appropriate service class for managing service repositories.
2024-06-11 17:38:18 +01:00
Simon Larsen
be54d782e5 refactor: Update page title in CodeRepositoryViewLayout component 2024-06-11 17:30:39 +01:00
Simon Larsen
029c1b0704 refactor: Add CopilotService to BaseAPIFeatureSet
This commit adds the CopilotService to the BaseAPIFeatureSet in the Index.ts file. The CopilotService is imported from the CopilotServiceService module and is used to create a new router for the "/app" endpoint. This change allows for handling requests related to the CopilotService in the application.
2024-06-11 16:56:42 +01:00
Simon Larsen
36cfb7e20f refactor: Add new migrations for SchemaMigrations and Models
New migrations have been added to the SchemaMigrations and Models directories. These migrations introduce new changes to the database schema and model definitions. This commit ensures that the codebase is up to date with the latest schema changes and improves the maintainability of the application.
2024-06-11 16:52:56 +01:00
Simon Larsen
02046a525e refactor: Update service catalog titles to use consistent naming convention
The titles of the ServiceCatalog and ServiceCatalogOwnerTeam classes have been updated to use a consistent naming convention. The "ServiceCatalog" title has been changed to "Service Catalog" and the "ServiceCatalog ID" title has been changed to "Service Catalog ID". This change improves clarity and readability in the codebase.
2024-06-11 16:51:12 +01:00
Simon Larsen
c9a998da0b refactor: Add service language dropdown in ServiceCatalog form 2024-06-11 16:35:23 +01:00
Simon Larsen
7fc192e466 refactor: Quote the value of ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN in _helpers.tpl
The value of ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN in _helpers.tpl has been updated to be quoted using the | quote filter. This change ensures that the value is treated as a string, improving consistency and compatibility with other parts of the codebase.
2024-06-11 16:24:55 +01:00
Simon Larsen
eae8b79b2e refactor: Add debug configuration for running Copilot locally
A new debug configuration has been added to the launch.json file. This configuration allows for launching Copilot locally by running the "start" script using npm. It skips files in the "<node_internals>" directory and is specifically configured for debugging Node.js applications. This change improves the development experience by enabling easy debugging of Copilot locally.
2024-06-11 15:11:00 +01:00
Simon Larsen
08c6cf31a0 refactor: Update linting configuration to exclude .tsx files
The linting configuration in the package.json file has been updated to exclude .tsx files from linting. This change ensures that the linting process only targets .ts files and improves the efficiency of the linting process.
2024-06-11 15:04:28 +01:00
Simon Larsen
dfb7f2320c refactor: Update jest.config.json files with testPathIgnorePatterns
The jest.config.json files in the Model, Probe, Common, Copilot, CommonUI, Ingestor, IsolatedVM, TestServer, and CommonServer directories have been updated. The "testPathIgnorePatterns" property has been added to each file, excluding the "node_modules" and "dist" directories from test path matching. This change improves test performance and ensures that unnecessary files are not included in the test coverage.
2024-06-11 14:52:55 +01:00
Simon Larsen
c53b14f88f refactor: Update CodeRepositoryUtil methods to accept repoPath parameter
The CodeRepositoryUtil methods getGitCommitHashForFile and getFilesInDirectory have been updated to accept a repoPath parameter. This change allows for specifying the repository path when retrieving the git commit hash for a file or getting the files in a directory. It improves the flexibility and reusability of the CodeRepositoryUtil class.
2024-06-11 14:24:19 +01:00
Simon Larsen
c5680f6c26 refactor: Update import statements in MonitorService.ts
The import statements in the MonitorService.ts file have been updated to import the AllowedActiveMonitorCountInFreePlan and IsBillingEnabled constants from the EnvironmentConfig module. This change ensures that the codebase is using the correct import paths and improves code readability.
2024-06-11 13:53:05 +01:00
Simon Larsen
d2a1385123 refactor: Update ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN to 10
The ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN constant has been updated to 10 in multiple files. This change aligns the codebase with the new limit for active monitors allowed in the free plan.
2024-06-11 13:50:55 +01:00
Simon Larsen
feda1b0426 Merge branch 'master' of github.com:OneUptime/oneuptime 2024-06-11 13:26:28 +01:00
Simon Larsen
b97cc46a1e refactor: Remove commented code in NavBar.tsx
The commented code in the NavBar.tsx file has been removed. This change cleans up the codebase and improves readability.
2024-06-11 13:26:00 +01:00
Simon Larsen
41a8101b54 refactor: Update CodeRepositoryAPI endpoint to use POST method for getting code repository 2024-06-11 13:25:41 +01:00
Simon Larsen
676a2b18b3 refactor: Update CodeRepositoryAPI endpoint to use POST method for getting code repository 2024-06-11 13:20:52 +01:00
Simon Larsen
df7477929b refactor: Update CodeRepositoryAPI endpoint to use POST method for getting code repository
This code change updates the CodeRepositoryAPI endpoint in the CodeRepositoryAPI.ts file to use the POST method instead of the GET method for getting the code repository. The route has also been modified to "/get-code-repository/:secretkey" to align with the API design. This change ensures that the code repository is retrieved securely and follows the recommended RESTful API practices.
2024-06-11 13:00:33 +01:00
Simon Larsen
c1ebe14c50 refactor: Add CodeRepositoryAPI to BaseAPIFeatureSet
This code change adds the CodeRepositoryAPI endpoint to the BaseAPIFeatureSet in the App/FeatureSet/BaseAPI/Index.ts file. It imports the CodeRepository model and service, and includes the necessary routing and configuration for the endpoint. This change allows for the CRUD operations on CodeRepository data through the API.
2024-06-11 12:47:47 +01:00
Simon Larsen
2b478e7a13 refactor: Add TableBillingAccessControl to MonitorOwnerTeam, MonitorOwnerUser, StatusPageOwnerTeam, MonitorGroupOwnerTeam, MonitorGroupOwnerUser models
This code change adds the TableBillingAccessControl decorator to the MonitorOwnerTeam, MonitorOwnerUser, StatusPageOwnerTeam, MonitorGroupOwnerTeam, and MonitorGroupOwnerUser models. The decorator configures the access control settings for these models based on the specified subscription plans. This change ensures that the appropriate access control rules are applied when creating, reading, updating, or deleting records in these models.
2024-06-11 12:37:48 +01:00
Simon Larsen
3bb1d93f3e feat: Add forceNavigate option when navigating to new item
The DuplicateModel component in CommonUI has been updated to include a new option, forceNavigate, when navigating to a new item. This option allows for forced navigation to the new item, bypassing any checks or validations. This change provides more control over the navigation behavior and ensures consistent navigation to the new item.
2024-06-11 12:29:50 +01:00
Simon Larsen
703c4b7685 feat: Add doNotShowWhenCreating option to Field interface
The Field interface in the CommonUI project has been updated to include a new property, doNotShowWhenCreating. This property allows developers to specify whether a field should be shown in the form when creating a new entity. By setting doNotShowWhenCreating to true, the field will be hidden during the creation process. This change provides more control over the visibility of fields in the form based on the create or edit mode.
2024-06-11 11:33:26 +01:00
Simon Larsen
d7e9776a3c refactor: Update CodeRepository.getRepository() to return a Promise
Update CodeRepository.getRepository() to return a Promise<CodeRepositoryModel> instead of a string. This change improves the functionality and flexibility of the CodeRepository class.
2024-06-11 11:10:36 +01:00
Simon Larsen
3cb29b63fe refactor: Update CodeRepository.getRepository() to return a Promise
The CodeRepository.getRepository() method has been updated to return a Promise<CodeRepositoryModel> instead of a string. This change ensures that the method is asynchronous and can handle asynchronous operations, improving the overall functionality and flexibility of the CodeRepository class.
2024-06-10 22:10:29 +01:00
Simon Larsen
e7f4a36ec9 refactor: Update Copilot startup message for clarity
The startup message in the Copilot/Index.ts file has been updated to provide a clearer description of the application. This change improves the readability and understanding of the log output during the startup process.
2024-06-10 22:09:25 +01:00
Simon Larsen
be78862e49 Merge pull request #1470 from OneUptime/snyk-upgrade-ff9eba0658b72fe88ae2d11836a86990
[Snyk] Upgrade axios from 1.6.8 to 1.7.1
2024-06-10 19:45:41 +01:00
Simon Larsen
64ae5eeb89 add code repository basic functions 2024-06-10 19:40:27 +01:00
Simon Larsen
78bdc42534 refactor: Update SelfSignedSSL class to use async/await for generating SSL certificates 2024-06-10 19:25:41 +01:00
snyk-bot
68c81734e8 fix: upgrade axios from 1.6.8 to 1.7.1
Snyk has created this PR to upgrade axios from 1.6.8 to 1.7.1.

See this package in npm:
axios

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-06-10 17:16:56 +00:00
Simon Larsen
bf081d1ebe refactor: Update file paths in code changes 2024-06-10 17:59:22 +01:00
Simon Larsen
3de407842e refactor: Add CopilotEvent API endpoint to BaseAPIFeatureSet
This code change adds the CopilotEvent API endpoint to the BaseAPIFeatureSet in the App/FeatureSet/BaseAPI/Index.ts file. It imports the CopilotEvent model and service, and includes the necessary routing and configuration for the endpoint. This change allows for the CRUD operations on CopilotEvent data through the API.
2024-06-10 17:45:00 +01:00
Simon Larsen
a4a9e45fe0 refactor: Update Dockerfile and build steps for Copilot service
This code change updates the Dockerfile and build steps for the Copilot service. It adds a new workflow job, `docker-build-copilot`, which builds the Docker image for the Copilot service. The job includes steps to checkout the code, preinstall dependencies, and build the Docker image using the specified Dockerfile. This change ensures that the Copilot service is built and deployed correctly in the CI/CD pipeline.
2024-06-10 17:19:25 +01:00
Simon Larsen
f9c9480434 refactor: Update logger to use configured log level
This code change updates the logger class to use the configured log level from the environment configuration. The getLogLevel method is added to retrieve the log level, and the info, error, warn, and debug methods are updated to check the log level before logging the message. This change ensures that the logger behaves according to the configured log level, improving the consistency and control of log output.
2024-06-10 17:00:07 +01:00
Simon Larsen
eb644ad2f2 Merge branch 'master' into ai-copilot-main 2024-06-10 16:45:19 +01:00
Simon Larsen
5186e193a8 Merge pull request #1468 from OneUptime/snyk-upgrade-d773ac20b9cf77163d96fbbdf2660a1f
[Snyk] Upgrade axios from 1.6.8 to 1.7.0
2024-06-10 14:39:04 +01:00
Simon Larsen
55d947fb39 refactor: Add isMonotonic column to Metric model
This code change adds a new column, 'isMonotonic', to the Metric model in the AnalyticsModels directory. The column is optional and represents whether the metric is monotonic. This change ensures that the necessary data is captured and stored correctly for metrics in the system.
2024-06-10 12:49:29 +01:00
Simon Larsen
77f1262ff5 refactor: Update Telemetry class to add gauge and histogram metrics
This code change updates the Telemetry class in the CommonServer/Utils directory to include methods for creating gauge and histogram metrics. The getGauge and getHistogram methods are added, allowing for the creation of observable gauges and histograms with specified names and descriptions. This change enhances the telemetry capabilities of the application by providing more options for metric tracking and analysis.
2024-06-10 12:39:49 +01:00
Simon Larsen
47bf8f9c89 refactor: Update EventItem component to dynamically apply styles based on event type
This code change updates the EventItem component to dynamically apply styles based on the event type. The classNames for the title and description elements are now generated based on the eventType prop, allowing for more flexibility in styling. This change improves the visual representation of different event types in the application.
2024-06-10 11:48:47 +01:00
Simon Larsen
4ca4f28d1c refactor: Add Point Type to Metric model
This code change adds a new column, 'Metric Point Type', to the Metric model in the AnalyticsModels directory. The column is optional and represents the type of metric point. This change ensures that the necessary data is captured and stored correctly for metrics in the system.
2024-06-10 11:45:23 +01:00
Simon Larsen
b6565ce2bb refactor: Add Aggregation Temporality column to Metric model
This code change adds a new column, 'Aggregation Temporality', to the Metric model in the AnalyticsModels directory. The column is required and represents the aggregation temporality of the metric. This change ensures that the necessary data is captured and stored correctly for metrics in the system.
2024-06-10 11:34:08 +01:00
snyk-bot
0704e1f556 fix: upgrade axios from 1.6.8 to 1.7.0
Snyk has created this PR to upgrade axios from 1.6.8 to 1.7.0.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/oneuptime-RsC2nshvQ2Vnr35jHvMnMP/project/49c81d9c-12c2-4e8e-b9e8-72f98b1b595c?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-10 04:54:58 +00:00
Simon Larsen
87c16d7bc3 refactor: Update needs array in test-e2e-release-saas and test-e2e-release-self-hosted jobs
This code change updates the needs array in the test-e2e-release-saas and test-e2e-release-self-hosted jobs in the release.yml file. The nginx-docker-image-deploy job has been added to the needs array to ensure that it is executed before these jobs. This change ensures that the necessary dependencies are met for the successful execution of the test-e2e-release-saas and test-e2e-release-self-hosted jobs.
2024-06-09 21:47:31 +01:00
Simon Larsen
cc66820e7b refactor: Remove commented code in NavBar component 2024-06-09 19:45:25 +01:00
Simon Larsen
a478f60a39 Merge branch 'master' of github.com:OneUptime/oneuptime 2024-06-09 19:45:00 +01:00
Simon Larsen
5637f12d3a refactor: Remove commented code in NavBar component
This code change removes the commented code in the NavBar component that was no longer needed. The commented code was related to the AI Copilot feature and the Service Catalog feature, which have been refactored or removed from the application. Removing this commented code improves code readability and reduces clutter in the component.
2024-06-09 19:44:57 +01:00
Simon Larsen
27c28b17af refactor: Update viewPageRoute in CodeRepositoryPage component 2024-06-09 19:44:41 +01:00
Simon Larsen
c55b169488 Merge pull request #1460 from OneUptime/snyk-upgrade-46c810649599bbd5f034d681106116a8
[Snyk] Upgrade react-big-calendar from 1.12.1 to 1.12.2
2024-06-09 19:36:50 +01:00
Simon Larsen
9b584d69ff refactor: Update viewPageRoute in CodeRepositoryPage component 2024-06-09 19:35:28 +01:00
Simon Larsen
05c090445a change job names 2024-06-09 19:29:59 +01:00
Simon Larsen
597aeb74f4 add e2e to test release 2024-06-09 19:29:23 +01:00
Simon Larsen
b7191a9c2e update github e2e jobs 2024-06-09 19:25:34 +01:00
Simon Larsen
c686030014 ```text
refactor: Update viewPageRoute in CodeRepositoryPage component

This code change updates the viewPageRoute prop in the CodeRepositoryPage component to use the new route generated by the Navigation utility. This ensures that the component navigates to the correct view page when a code repository is clicked.
2024-06-09 19:21:02 +01:00
Simon Larsen
eed1078f06 refactor: Add viewPageRoute prop to CodeRepositoryPage component
This code change adds the viewPageRoute prop to the CodeRepositoryPage component in order to specify the route for viewing a code repository. This prop is set to the corresponding route from the RouteMap. By including this prop, the component will correctly navigate to the view page when the user clicks on a code repository.
2024-06-09 19:15:30 +01:00
Simon Larsen
051a3c43b2 refactor: Update Git Repository name to "Git Repositories" 2024-06-09 19:04:45 +01:00
Simon Larsen
a152813535 refactor: Add CodeRepository model and service
This code change adds the CodeRepository model and service to the project. It includes the following modifications:

- Added CodeRepository model to the Models/Index.ts file.
- Added CodeRepositoryService to the Services/Index.ts file.
- Created CodeRepositoryService.ts file with the necessary implementation.

These changes enable the project to manage code repositories.
2024-06-09 19:03:54 +01:00
Simon Larsen
decea5acfc refactor: Add AI Copilot breadcrumbs and code repository view 2024-06-09 19:03:23 +01:00
Simon Larsen
4c2dfb0f92 refactor: Add CodeRepository model, service, and permissions
This code change adds the CodeRepository model, service, and permissions to the project. It includes the following modifications:

- Added CodeRepository model to the Models/Index.ts file.
- Added CodeRepositoryService to the Services/Index.ts file.
- Created CodeRepositoryService.ts file with the necessary implementation.
- Updated Permission enum in the Permission.ts file to include CreateCodeRepository, DeleteCodeRepository, EditCodeRepository, and ReadCodeRepository permissions.

These changes enable the project to manage code repositories and define the necessary permissions for them.
2024-06-09 15:03:56 +01:00
Simon Larsen
a24bf077ce refactor: Improve error message in endpoint-status.sh
This code change updates the endpoint-status.sh script to improve the error message when the endpoint returns an HTTP status other than 200. The previous message mentioned that it usually takes a few minutes for the app to boot, which is not accurate. The updated message removes this misleading information and provides a more accurate description of the retry behavior.
2024-06-09 14:49:48 +01:00
Simon Larsen
2d82f50789 refactor: Enable cleanup and e2e cron jobs for development purposes 2024-06-09 14:39:14 +01:00
Simon Larsen
3e13776252 refactor: Improve BaseModelTable actions column logic
This code change updates the BaseModelTable component to improve the logic for determining whether to show the actions column. Previously, the actions column was shown based on various conditions, including permissions, action buttons, and the showViewIdButton prop. This logic has been refactored to simplify the conditions and make it easier to understand.

- The showActionsColumn variable is now initialized with the result of the Boolean function, which checks if any of the conditions are met.
- Additionally, if the user is a master admin, the actions column will always be shown, regardless of other conditions.

These changes enhance the readability and maintainability of the code, ensuring that the actions column is displayed correctly based on the specified conditions.
2024-06-09 13:18:56 +01:00
Simon Larsen
463bb32872 refactor: Enable cleanup and e2e cron jobs for development purposes
This code change updates the values.yaml file in the HelmChart/Public/oneuptime directory. It enables the cleanup and e2e cron jobs for development purposes. Please note that these cron jobs should not be enabled in production environments. This change allows for easier testing and debugging during development.
2024-06-09 12:48:42 +01:00
Simon Larsen
99dcee80cd refactor: Add support for Go language in CodeQL analysis workflow 2024-06-08 14:54:28 +01:00
Simon Larsen
c418dc41dd refactor: Add TypeScript support to CodeQL analysis workflow
This code change updates the CodeQL analysis workflow in the .github/workflows/codeql-analysis.yml file. It adds TypeScript as a supported language for CodeQL analysis, in addition to JavaScript. This ensures that the codebase can be thoroughly analyzed for potential security vulnerabilities and bugs in both JavaScript and TypeScript code.
2024-06-08 14:54:02 +01:00
Simon Larsen
c0678c410d refactor: Update SideMenu.tsx to fix indentation issue
This code change updates the SideMenu.tsx file in the ServiceCatalog/View directory. It fixes an indentation issue in the SideMenuItem component by properly aligning the opening tag. This ensures consistent code formatting and improves code readability.
2024-06-08 14:34:56 +01:00
Simon Larsen
081359f7e0 refactor: Update Delete.tsx to navigate to Service Catalog page after successful deletion
This code change updates the Delete.tsx file in the ServiceCatalog/View directory. It modifies the onDeleteSuccess callback function to navigate to the Service Catalog page (RouteMap[PageMap.SERVICE_CATALOG]) after a successful deletion. This ensures a smooth user experience by redirecting them to the appropriate page after completing the deletion process.
2024-06-08 14:26:02 +01:00
Simon Larsen
cc0cfe4f8c refactor: Update Settings.tsx to import ServiceCatalog instead of TelemetryService 2024-06-08 14:24:42 +01:00
Simon Larsen
dc87905f05 refactor: Update ServiceCatalogPage to include lazy loading for ServiceCatalogElement 2024-06-08 14:24:05 +01:00
Simon Larsen
9c31047d52 refactor: Update ServiceCatalogPage to include lazy loading for ServiceCatalogElement
This code change updates the ServiceCatalogPage component in ServiceCatalog.tsx to include lazy loading for the ServiceCatalogElement component. By implementing lazy loading, the page load performance is improved as the ServiceCatalogElement component is only loaded when it is actually needed. This enhances the overall user experience by reducing initial page load time.
2024-06-08 14:20:21 +01:00
Simon Larsen
91d196ddea refactor: Update ServiceCatalogRoutePath in RouteMap.ts
This code change updates the ServiceCatalogRoutePath in the RouteMap.ts file to include new routes for viewing, managing owners, and deleting service catalogs. This ensures that the application can properly handle these actions related to service catalogs within the dashboard.
2024-06-08 14:12:41 +01:00
Simon Larsen
78db5cab39 refactor: Update ServiceCatalogRoutePath in RouteMap.ts 2024-06-08 14:12:06 +01:00
Simon Larsen
689e72e5ec refactor: Update ServiceCatalogRoutePath in RouteMap.ts
This code change updates the ServiceCatalogRoutePath in the RouteMap.ts file to include new routes for viewing, managing owners, and deleting service catalogs. This ensures that the application can properly handle these actions related to service catalogs within the dashboard.
2024-06-08 14:05:24 +01:00
Simon Larsen
bc9e97f67c refactor: Fix typo in status pages description 2024-06-08 13:57:48 +01:00
Simon Larsen
19550c23ed refactor: Add ServiceCatalogOwnerTeamService and ServiceCatalogOwnerUserService to Services/Index.ts
This code change adds the ServiceCatalogOwnerTeamService and ServiceCatalogOwnerUserService to the Services/Index.ts file. These services are necessary for managing ownership of Service Catalogs, allowing teams and individual users to be assigned as owners of specific Service Catalogs.
2024-06-08 13:33:53 +01:00
Simon Larsen
2d09df2d87 feat: Add ServiceCatalogOwnerTeam and ServiceCatalogOwnerUser models
This code change adds the ServiceCatalogOwnerTeam and ServiceCatalogOwnerUser models to the project. These models are necessary for managing ownership of Service Catalogs, allowing teams and individual users to be assigned as owners of specific Service Catalogs.
2024-06-08 13:31:40 +01:00
Simon Larsen
2dfebdd2e4 refactor: Update SchemaMigrations/Index.ts to fix formatting
This code change fixes the formatting in the SchemaMigrations/Index.ts file. The changes include removing unnecessary whitespace and ensuring consistent indentation. This improves code readability and maintainability.
2024-06-08 13:16:51 +01:00
Simon Larsen
ebec143c9c refactor: Import and include ServiceCatalogService and ServiceCatalog model
This code change updates the Index.ts and Models/Index.ts files to import and include the ServiceCatalogService and ServiceCatalog model respectively. This allows for the management of Service Catalogs, including creating, deleting, editing, and reading them within the project.
2024-06-08 10:35:17 +01:00
Simon Larsen
aa68a6316a feat: Add Service Catalog permissions
This code change adds new permissions for managing Service Catalogs. The permissions include creating, deleting, editing, and reading Service Catalogs. These permissions allow users to perform various actions related to Service Catalogs within the project.

Refactor: Update Index.ts and Models/Index.ts to import and include the ServiceCatalogService and ServiceCatalog model respectively.
2024-06-08 10:25:37 +01:00
Simon Larsen
27a37581e4 Merge pull request #1461 from OneUptime/metric-detail
refactor: Update Navigation class to use getLastParamAsString method
2024-06-08 09:51:32 +01:00
Simon Larsen
091622f8cf refactor: Update StatementGenerator.ts to improve code readability and maintainability 2024-06-08 09:49:49 +01:00
Simon Larsen
87caae077c ```text
refactor: Change metric column types to decimal

This code change modifies the Metric model in the AnalyticsModels directory to change the column types of various metrics from TableColumnType.Number to TableColumnType.Decimal. This update ensures more accurate and precise calculations for the metrics.
2024-06-07 22:55:39 +01:00
Simon Larsen
a146691773 refactor: Improve code readability and maintainability in LogItem.tsx 2024-06-07 22:16:36 +01:00
Simon Larsen
9fce103b11 refactor: Update ResetPassword.tsx to improve password confirmation validation 2024-06-07 22:04:56 +01:00
Simon Larsen
2aa0ae89fc refactor: Improve code readability and maintainability in OTelIngest.ts 2024-06-07 21:45:09 +01:00
Simon Larsen
0a16f6bf44 refactor: Update OTelIngest.ts to improve code readability and maintainability 2024-06-07 21:27:11 +01:00
Simon Larsen
0f49e6e100 refactor: Improve code readability and maintainability in OTelIngest.ts 2024-06-07 17:49:58 +01:00
Simon Larsen
d954b4a5df refactor: Add unit column to metrics table and improve code readability in StatementGenerator.ts 2024-06-07 17:36:09 +01:00
Simon Larsen
e762778fc6 refactor: Improve code readability and maintainability in StatementGenerator.ts 2024-06-07 17:33:23 +01:00
Simon Larsen
4cced50857 feat: Add unit column to metrics table 2024-06-07 17:00:30 +01:00
Simon Larsen
26c900d8e2 refactor: Update CommonServer/package.json and Ingestor/Service/OTelIngest.ts
This code change updates the CommonServer/package.json file to remove the "@opentelemetry/metrics" dependency and adds the "@opentelemetry/sdk-metrics" dependency with version "^1.21.0". It also updates the Ingestor/Service/OTelIngest.ts file to import the "JSONFunctions" module from "Common/Types/JSONFunctions" and use the "flattenObject" function from that module to flatten the final object before returning it.
2024-06-07 16:57:24 +01:00
Simon Larsen
63461343ba refactor: Update OTelIngest.ts to improve code readability and maintainability
This code change updates the OTelIngest.ts file to improve code readability and maintainability. It includes formatting changes such as indentation and line breaks to enhance code structure and organization. These modifications make the code easier to understand and maintain for future development.
2024-06-07 14:57:24 +01:00
Simon Larsen
931abc522b refactor: Update Telemetry class to include new metrics for status checks 2024-06-07 14:54:39 +01:00
Simon Larsen
054592eed3 refactor: Update Telemetry class to include new metrics for status checks
This code change updates the Telemetry class to include new metrics for status checks. It adds counters for successful and failed status checks, including separate counters for ready and live checks. This modification enhances the monitoring capabilities of the application and provides more granular insights into the status of the system.
2024-06-07 14:25:40 +01:00
Simon Larsen
82b2307b51 Merge branch 'master' into metric-detail 2024-06-07 12:50:17 +01:00
Simon Larsen
b1dba73842 refactor: Update external database configuration in HelmChart
This code change updates the external database configuration in the HelmChart. It adds support for using an existing secret for the password instead of directly specifying the password. This modification enhances security and allows for easier management of the database credentials.
2024-06-07 10:11:27 +01:00
Simon Larsen
babbf5f8a6 refactor: Update external database configuration in HelmChart 2024-06-07 10:10:55 +01:00
Simon Larsen
39c7da79ab refactor: Update external database configuration in HelmChart
This code change updates the external database configuration in the HelmChart. It adds support for using an existing secret for the password instead of directly specifying the password. This modification enhances security and allows for easier management of the database credentials.
2024-06-07 10:10:00 +01:00
Simon Larsen
938bd32915 refactor: Update PageMap and RouteMap to include new telemetry metric page
This code change updates the PageMap and RouteMap files to include a new telemetry metric page. The new page, TELEMETRY_SERVICES_VIEW_METRIC, is added to both files with its corresponding route path. This modification ensures that the new page is properly mapped and accessible within the application.
2024-06-07 10:08:40 +01:00
Simon Larsen
f8e1ace311 refactor: Update Navigation class to use getLastParamAsString method 2024-06-06 21:45:29 +01:00
Simon Larsen
05e2e22e1d refactor: Update DATABASE_USERNAME value in _helpers.tpl
This code change updates the value of DATABASE_USERNAME in the _helpers.tpl file. The value is changed to "postgres" when the postgresql.enabled flag is true, and to the value of $.Values.externalPostgres.username when the flag is false. This modification ensures that the correct username is used for the database connection.
2024-06-06 21:19:19 +01:00
Simon Larsen
9054c49b3e refactor: Update Kubernetes secret retrieval commands
The code changes update the commands used to retrieve secrets from Kubernetes. The previous commands were using the deprecated `go-template` option, which has been replaced with the `jsonpath` option. This refactor ensures that the correct commands are used to retrieve the secrets, improving the reliability and maintainability of the code.
2024-06-06 18:36:05 +01:00
Simon Larsen
5d5468603f refactor: Add lazy loading for images in DateFilter component
This code change adds lazy loading for images in the DateFilter component. By implementing lazy loading, the images in the component will only load when they are in the viewport, improving the page load performance. This enhancement provides a better user experience by reducing the initial load time of the page.
2024-06-06 17:49:43 +01:00
Simon Larsen
4c6979cfa1 refactor: Add date filter to FiltersForm and FilterViewer components
This code change adds a new date filter to the FiltersForm and FilterViewer components. The date filter allows users to filter data based on a specific date range. This enhancement improves the filtering capabilities of the components and provides users with more flexibility in data analysis.
2024-06-06 17:40:24 +01:00
Simon Larsen
714a4be2b0 refactor: Update DatabaseService to set isRoot flag when updating items
This code change updates the DatabaseService class to set the isRoot flag to true when updating items. Previously, the props object in the update query only included the ignoreHooks flag. By adding the isRoot flag, we ensure that the update operation is performed as a root user, bypassing any access restrictions or hooks. This modification enhances the flexibility and control of the update process.
2024-06-06 14:43:11 +01:00
Simon Larsen
b935443f96 refactor: Update PingMonitorView to display monitor destination and port
This code change updates the PingMonitorView component to display the monitor destination and port. It ensures that the destination URL or IP address and the corresponding port are correctly displayed in the component. This enhancement improves the visibility of the monitoring information for users.
2024-06-06 14:28:32 +01:00
Simon Larsen
d3a3f01f20 refactor: Update monitorDestination and monitorDestinationPort in ProbeMonitorResponse interface
This code change updates the ProbeMonitorResponse interface to include the monitorDestination and monitorDestinationPort properties. These properties represent the destination URL or IP address and the corresponding port for monitoring. By adding these properties to the interface, we ensure that the necessary information is available for monitoring purposes.
2024-06-06 13:58:51 +01:00
snyk-bot
56b0fea04a fix: upgrade react-big-calendar from 1.12.1 to 1.12.2
Snyk has created this PR to upgrade react-big-calendar from 1.12.1 to 1.12.2.

See this package in npm:
react-big-calendar

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-06-06 03:46:25 +00:00
Simon Larsen
2605140166 refactor: Update DATABASE_USERNAME value in _helpers.tpl
This code change updates the value of DATABASE_USERNAME in the _helpers.tpl file. The value is changed to "postgres" when the postgresql.enabled flag is true, and to the value of $.Values.externalPostgres.username when the flag is false. This modification ensures that the correct username is used for the database connection.
2024-06-05 19:03:49 +01:00
Simon Larsen
8b9611e145 refactor: Update import statement for InitialMigration in Postgres SchemaMigrations 2024-06-05 17:52:16 +01:00
Simon Larsen
21057038d1 refactor: Update import statement for InitialMigration in Postgres SchemaMigrations
This code change updates the import statement for the InitialMigration file in the Postgres SchemaMigrations folder. The file name was changed from "1717605043663-MigrationName.ts" to "1717605043663-InitialMigration.ts". This refactor ensures that the correct file is imported and used for schema migrations.
2024-06-05 17:36:45 +01:00
Simon Larsen
e587d4ba19 refactor: Update Postgres migration generation command to use correct file path 2024-06-05 17:33:30 +01:00
Simon Larsen
14da201c8d refactor: Remove unused PostgresConfig file and update PostgresDatabase imports 2024-06-05 17:26:48 +01:00
Simon Larsen
f5584a5037 Add error handling to monitor probing in the FetchListAndProbe class. This change modifies the code to catch any errors that occur during the probing process and log them using the logger. This enhancement improves the robustness of the monitoring functionality by ensuring that errors are properly handled and logged. 2024-06-05 13:30:33 +01:00
Simon Larsen
157f8e95d7 refactor: Update TableRow component to include padding in drag handle 2024-06-05 12:59:35 +01:00
Simon Larsen
fb83126f37 Update Postgres password retrieval in Helm chart documentation 2024-06-05 12:27:31 +01:00
Simon Larsen
964def0c45 refactor: Disable ClickHouse when not enabled in values.yaml 2024-06-05 12:16:40 +01:00
Simon Larsen
650d7cc939 refactor: Add support for HTTPS in ClickHouse configuration 2024-06-05 11:42:01 +01:00
Simon Larsen
bfb4c46bd0 refactor: Update ClickHouse configuration to support HTTPS
This code change updates the ClickHouse configuration to support HTTPS. It introduces a new environment variable, `ClickHouseIsHostHttps`, which is set to `true` in the `EnvironmentConfig.ts` file. The `ClickhouseConfig.ts` file is modified to conditionally set the host protocol to `https` based on the value of `ClickHouseIsHostHttps`. This enhancement allows users to easily configure ClickHouse to use HTTPS when connecting to the host.
2024-06-05 11:41:39 +01:00
Simon Larsen
149c8c763d refactor: Remove empty line in values.yaml 2024-06-05 10:57:57 +01:00
Simon Larsen
fdbcace48c refactor: Update ClickHouse TLS configuration to use "tls" instead of "ssl" in values.yaml 2024-06-05 10:53:47 +01:00
Simon Larsen
3cfe0517a8 refactor: Update ClickHouse environment variables 2024-06-05 10:53:05 +01:00
Simon Larsen
9a32a47146 refactor: Update Redis and ClickHouse TLS configuration
This code change updates the Redis and ClickHouse TLS configuration to support the new `RedisTlsCert`, `RedisTlsKey`, `ClickhouseTlsCert`, and `ClickhouseTlsKey` environment variables. It modifies the `Redis.ts` and `ClickhouseConfig.ts` files to conditionally set the TLS options based on the presence of these variables. This enhancement allows users to easily enable TLS for Redis and ClickHouse by providing the necessary certificate and key files.
2024-06-05 10:49:01 +01:00
Simon Larsen
fd83a71a56 refactor: Enable Redis and ClickHouse in Helm chart
This code change enables Redis and ClickHouse in the Helm chart by setting the `enabled` flag to `true` for both services in the `values.yaml` file. This enhancement allows users to utilize Redis and ClickHouse functionalities when deploying the Helm chart.
2024-06-05 10:08:11 +01:00
snyk-bot
070190dd31 fix: upgrade @nivo/line from 0.86.0 to 0.87.0
Snyk has created this PR to upgrade @nivo/line from 0.86.0 to 0.87.0.

See this package in npm:
@nivo/line

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-06-03 04:20:30 +00:00
1828 changed files with 228730 additions and 213546 deletions

View File

@@ -1,28 +0,0 @@
*/node_modules/*
*/build/*
*/coverage/*
*/dist/*
*/public/*
*/views/*
*fonts*
*logos*
.*
*.png
*.sh
*.txt
*.snap
*.enc
Dockerfile
CHANGELOG
LICENSE
marketing/*/*
licenses/*
certifications/*
ApiReference/public/assets/*
JavaScriptSDK/src/cli/server-monitor/out/scripts/prettify/*
_test/*

View File

@@ -1,216 +0,0 @@
{
"parserOptions": {
"ecmaVersion": 8,
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true,
"tsx": true,
"spread": true
},
"sourceType": "module",
"project": [
"./tsconfig.json"
]
},
"env": {
"browser": true,
"node": true,
"jquery": true,
"es6": true,
"jest": true,
"jasmine": true
},
"plugins": [
"react",
"jsx-a11y",
"progress",
"@typescript-eslint",
"unused-imports"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:prettier/recommended",
"prettier"
],
"globals": {
"describe": true,
"context": true,
"before": true,
"beforeEach": true,
"after": true,
"afterEach": true,
"it": true,
"expect": true,
"workbox": true,
"importScripts": true,
"$TSFixMe": true,
"NodeJS": true,
"JSX": true
},
"parser": "@typescript-eslint/parser",
"rules": {
"no-fallthrough": "error",
"no-unreachable": "error",
"no-cond-assign": "error",
"valid-typeof": "error",
"no-func-assign": "error",
"curly": "error",
"no-extra-semi": "error",
"no-else-return": "error",
"no-div-regex": "error",
"no-octal": "error",
"no-extra-bind": "error",
"unicode-bom": "error",
"no-extra-boolean-cast": "error",
"wrap-regex": "error",
"wrap-iife": "error",
"yield-star-spacing": "error",
"no-implicit-coercion": "error",
"no-extra-label": "error",
"multiline-comment-style": "off",
"no-lonely-if": "error",
"no-floating-decimal": "error",
"eqeqeq": "error",
"dot-notation": "off", // Off because it messes up with typescript compiler.
"@typescript-eslint/dot-notation": "off", //temp off.
"progress/activate": 1,
"linebreak-style": [
"error",
"unix"
],
"@typescript-eslint/no-empty-interface": [
"error",
{
"allowSingleExtends": true
}
],
// https://www.npmjs.com/package/eslint-plugin-unused-imports
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/no-extra-non-null-assertion": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"error",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/explicit-member-accessibility": [
"error"
],
"no-console": "error",
"no-undef": "error",
"no-empty": "error",
"no-control-regex": "off",
"prefer-arrow-callback": "error",
"constructor-super": "error",
"no-case-declarations": "error",
"no-mixed-spaces-and-tabs": "error",
"no-useless-escape": "error",
"prettier/prettier": "error",
"react/jsx-no-undef": "error",
"react/jsx-no-bind": [
"error",
{
"allowArrowFunctions": true,
"allowBind": false,
"ignoreRefs": false
}
],
"react/no-children-prop": "error",
"react/no-deprecated": "error",
"react/boolean-prop-naming": "error",
"react/no-is-mounted": "error",
"react/no-find-dom-node": "error",
"one-var-declaration-per-line": "error",
"arrow-parens": "error",
"arrow-body-style": [
"error",
"always"
],
"@typescript-eslint/typedef": [
"error",
{
"arrowParameter": true,
"variableDeclaration": true
}
],
"@typescript-eslint/strict-boolean-expressions": "off", //Need to enable this very soon
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": true
}
],
"react/no-did-update-set-state": "error",
"react/no-unknown-property": "error",
"react/no-unused-prop-types": "error",
"react/jsx-no-duplicate-props": "error",
"react/no-unused-state": "error",
"react/jsx-uses-vars": "error",
"react/prop-types": "error",
"react/react-in-jsx-scope": "error",
"react/no-string-refs": "error",
"jsx-a11y/href-no-hash": [
0
],
"react/no-unescaped-entities": "error",
"react/display-name": "error",
"react/jsx-pascal-case": "error",
"array-callback-return": "error",
"no-loop-func": "error",
"no-duplicate-imports": "error",
"no-promise-executor-return": "error",
"capitalized-comments": "off", // this is turned off because come commented code should not be capitalized.
"for-direction": "error",
"getter-return": "error",
"jsx-a11y/anchor-is-valid": "error",
"no-async-promise-executor": "error",
"prefer-const": [
"error",
{
"destructuring": "any",
"ignoreReadBeforeAssign": false
}
],
"no-var": "error",
"object-curly-spacing": [
"error",
"always"
],
"no-unneeded-ternary": "error",
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"String": true,
"Boolean": true,
"Number": true,
"Symbol": false,
"{}": true,
"Object": true,
"object": true,
"Function": true
},
"extendDefaults": true
}
]
},
"settings": {
"react": {
"version": "18.1.0"
}
}
}

View File

@@ -72,6 +72,22 @@ jobs:
- name: build docker image
run: sudo docker build -f ./App/Dockerfile .
docker-build-copilot:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Preinstall
run: npm run prerun
# build image for accounts service
- name: build docker image
run: sudo docker build -f ./Copilot/Dockerfile .
docker-build-e2e:
runs-on: ubuntu-latest
env:

View File

@@ -34,7 +34,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
language: [ 'javascript', 'typescript', 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support

View File

@@ -91,6 +91,20 @@ jobs:
- run: cd CommonUI && npm install --force
- run: cd App && npm install && npm run compile && npm run dep-check
compile-copilot:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: cd Common && npm install
- run: cd Model && npm install
- run: cd CommonServer && npm install
- run: cd Copilot && npm install && npm run compile && npm run dep-check
compile-nginx:
runs-on: ubuntu-latest
env:

View File

@@ -18,35 +18,12 @@ jobs:
token: ${{secrets.github_token}}
- run: echo "Build number is ${{ steps.buildnumber.outputs.build_number }}"
github-release:
needs: [generate-build-number]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/release'
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- run: echo "${{needs.generate-build-number.outputs.build_number}}"
- name: "Build Changelog"
id: build_changelog
uses: mikepenz/release-changelog-builder-action@v4.2.0
with:
configuration: "./Scripts/Release/ChangelogConfig.json"
- run: echo "Changelog:"
- run: echo "${{steps.build_changelog.outputs.changelog}}"
- uses: ncipollo/release-action@v1
with:
tag: "7.0.${{needs.generate-build-number.outputs.build_number}}"
artifactErrorsFailBuild: true
body: |
${{steps.build_changelog.outputs.changelog}}
helm-chart-deploy:
runs-on: ubuntu-latest
needs: [generate-build-number, github-release]
needs: [generate-build-number]
env:
CI_COMMIT_AUTHOR: Continuous Integration
steps:
@@ -92,7 +69,7 @@ jobs:
git push origin master
nginx-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -152,7 +129,7 @@ jobs:
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
e2e-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -212,7 +189,7 @@ jobs:
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
isolated-vm-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -272,7 +249,7 @@ jobs:
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
test-server-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -332,7 +309,7 @@ jobs:
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
otel-collector-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -394,7 +371,7 @@ jobs:
status-page-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -454,7 +431,7 @@ jobs:
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
test-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -514,7 +491,7 @@ jobs:
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
ingestor-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -574,7 +551,7 @@ jobs:
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
probe-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -635,7 +612,7 @@ jobs:
haraka-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -695,7 +672,7 @@ jobs:
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
admin-dashboard-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -756,7 +733,7 @@ jobs:
dashboard-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -816,7 +793,7 @@ jobs:
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
app-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -875,8 +852,69 @@ jobs:
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
copilot-docker-image-deploy:
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
oneuptime/copilot
ghcr.io/oneuptime/copilot
tags: |
type=raw,value=release,enable=true
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}},pattern={{version}},enable=true
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Generate Dockerfile from Dockerfile.tpl
run: npm run prerun
# Build and deploy app.
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.2.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
file: ./Copilot/Dockerfile
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
accounts-docker-image-deploy:
needs: [generate-build-number, github-release]
needs: [generate-build-number]
runs-on: ubuntu-latest
steps:
- name: Docker Meta
@@ -938,7 +976,7 @@ jobs:
publish-npm-packages:
runs-on: ubuntu-latest
needs: [generate-build-number, github-release]
needs: [generate-build-number]
env:
CI_PIPELINE_ID: ${{github.run_number}}
NPM_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
@@ -951,8 +989,140 @@ jobs:
- name: Publish Infrastructure Agent
run: bash ./Scripts/NPM/PublishAllPackages.sh
test-e2e-release-saas:
runs-on: ubuntu-latest
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy]
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
- name: Start Server with release tag
run: npm run start
- name: Wait for server to start
run: bash ./Tests/Scripts/status-check.sh http://localhost
- name: Run E2E Tests. Run docker container e2e in docker compose file
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
- name: Upload test results
uses: actions/upload-artifact@v4
# Run this on failure
if: failure()
with:
# Name of the artifact to upload.
# Optional. Default is 'artifact'
name: test-results
# A file, directory or wildcard pattern that describes what to upload
# Required.
path: |
./E2E
# Duration after which artifact will expire in days. 0 means using default retention.
# Minimum 1 day.
# Maximum 90 days unless changed from the repository settings page.
# Optional. Defaults to repository settings.
retention-days: 7
test-e2e-release-self-hosted:
runs-on: ubuntu-latest
# After all the jobs runs
needs: [copilot-docker-image-deploy, accounts-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, haraka-docker-image-deploy, ingestor-docker-image-deploy, isolated-vm-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, nginx-docker-image-deploy]
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: npm run prerun
- name: Start Server with release tag
run: npm run start
- name: Wait for server to start
run: bash ./Tests/Scripts/status-check.sh http://localhost
- name: Run E2E Tests. Run docker container e2e in docker compose file
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
- name: Upload test results
uses: actions/upload-artifact@v4
# Run this on failure
if: failure()
with:
# Name of the artifact to upload.
# Optional. Default is 'artifact'
name: test-results
# A file, directory or wildcard pattern that describes what to upload
# Required.
path: |
./E2E
# Duration after which artifact will expire in days. 0 means using default retention.
# Minimum 1 day.
# Maximum 90 days unless changed from the repository settings page.
# Optional. Defaults to repository settings.
retention-days: 7
github-release:
needs: [test-e2e-release-saas, test-e2e-release-self-hosted, generate-build-number]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/release'
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- run: echo "${{needs.generate-build-number.outputs.build_number}}"
- name: "Build Changelog"
id: build_changelog
uses: mikepenz/release-changelog-builder-action@v4.2.0
with:
configuration: "./Scripts/Release/ChangelogConfig.json"
- run: echo "Changelog:"
- run: echo "${{steps.build_changelog.outputs.changelog}}"
- uses: ncipollo/release-action@v1
with:
tag: "7.0.${{needs.generate-build-number.outputs.build_number}}"
artifactErrorsFailBuild: true
body: |
${{steps.build_changelog.outputs.changelog}}
infrastructure-agent-deploy:
needs: [generate-build-number, github-release]
needs: [github-release, generate-build-number]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -983,3 +1153,5 @@ jobs:
draft: false
prerelease: false
tag_name: 7.0.${{needs.generate-build-number.outputs.build_number}}

View File

@@ -876,10 +876,72 @@ jobs:
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
copilot-docker-image-deploy:
needs: generate-build-number
runs-on: ubuntu-latest
steps:
- name: Docker Meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
oneuptime/copilot
ghcr.io/oneuptime/copilot
tags: |
type=raw,value=test,enable=true
type=semver,value=7.0.${{needs.generate-build-number.outputs.build_number}}-test,pattern={{version}},enable=true
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Generate Dockerfile from Dockerfile.tpl
run: npm run prerun
# Build and deploy accounts.
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.2.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
file: ./Copilot/Dockerfile
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_SHA=${{ github.sha }}
APP_VERSION=7.0.${{needs.generate-build-number.outputs.build_number}}
test-helm-chart:
runs-on: ubuntu-latest
needs: [isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy]
needs: [copilot-docker-image-deploy, isolated-vm-docker-image-deploy, test-server-docker-image-deploy, test-docker-image-deploy, ingestor-docker-image-deploy, probe-docker-image-deploy, haraka-docker-image-deploy, dashboard-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, accounts-docker-image-deploy, otel-collector-docker-image-deploy, status-page-docker-image-deploy, nginx-docker-image-deploy, e2e-docker-image-deploy]
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
@@ -888,3 +950,111 @@ jobs:
with:
node-version: 18.3.0
- run: cd HelmChart && cd Tests && bash index.sh
test-e2e-test-saas:
runs-on: ubuntu-latest
needs: [test-helm-chart]
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: npm run prerun && bash ./Tests/Scripts/change-release-to-test-tag.sh
- name: Start Server with release tag
run: npm run start
- name: Wait for server to start
run: bash ./Tests/Scripts/status-check.sh http://localhost
- name: Run E2E Tests. Run docker container e2e in docker compose file
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
- name: Upload test results
uses: actions/upload-artifact@v4
# Run this on failure
if: failure()
with:
# Name of the artifact to upload.
# Optional. Default is 'artifact'
name: test-results
# A file, directory or wildcard pattern that describes what to upload
# Required.
path: |
./E2E
# Duration after which artifact will expire in days. 0 means using default retention.
# Minimum 1 day.
# Maximum 90 days unless changed from the repository settings page.
# Optional. Defaults to repository settings.
retention-days: 7
test-e2e-test-self-hosted:
runs-on: ubuntu-latest
# After all the jobs runs
needs: [test-helm-chart]
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
# Docker compose needs a lot of space to build images, so we need to free up some space first in the GitHub Actions runner
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 18.3.0
- run: npm run prerun && bash ./Tests/Scripts/change-release-to-test-tag.sh
- name: Start Server with release tag
run: npm run start
- name: Wait for server to start
run: bash ./Tests/Scripts/status-check.sh http://localhost
- name: Run E2E Tests. Run docker container e2e in docker compose file
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
- name: Upload test results
uses: actions/upload-artifact@v4
# Run this on failure
if: failure()
with:
# Name of the artifact to upload.
# Optional. Default is 'artifact'
name: test-results
# A file, directory or wildcard pattern that describes what to upload
# Required.
path: |
./E2E
# Duration after which artifact will expire in days. 0 means using default retention.
# Minimum 1 day.
# Maximum 90 days unless changed from the repository settings page.
# Optional. Defaults to repository settings.
retention-days: 7

View File

@@ -32,10 +32,12 @@ jobs:
node-version: 18.3.0
- run: npm run prerun && bash ./Tests/Scripts/enable-billing-env-var.sh
- run: npm run dev
- name: Sleep for 2 minutes to wait for server to start
run: sleep 120
- name: Wait for server to start
run: bash ./Tests/Scripts/status-check.sh http://localhost
run: bash ./Tests/Scripts/status-check.sh http://localhost
- name: Run E2E Tests. Run docker container e2e in docker compose file
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs e2e && exit 1)
run: export $(grep -v '^#' config.env | xargs) && docker-compose -f docker-compose.dev.yml up --exit-code-from e2e --abort-on-container-exit e2e || (docker-compose -f docker-compose.dev.yml logs && exit 1)
- name: Upload test results
uses: actions/upload-artifact@v4
# Run this on failure
@@ -48,15 +50,11 @@ jobs:
# A file, directory or wildcard pattern that describes what to upload
# Required.
path: |
./E2E/playwright-report
./E2E/test-results
./E2E
# Duration after which artifact will expire in days. 0 means using default retention.
# Minimum 1 day.
# Maximum 90 days unless changed from the repository settings page.
# Optional. Defaults to repository settings.
retention-days: 7
retention-days: 7

2
.gitignore vendored
View File

@@ -98,6 +98,8 @@ Llama/Models/llama*
Llama/__pycache__/*
Llama/Models/*
Examples/otel-dotnet/obj/*
InfrastructureAgent/sea-prep.blob

View File

@@ -1,12 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"bracketSpacing": true,
"arrowParens": "avoid",
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"importOrderParserPlugins": ["typescript", "decorators", "dynamicImport", "jsx"]
}

14
.vscode/launch.json vendored
View File

@@ -19,6 +19,20 @@
}
],
"configurations": [
{
"name": "Debug: Copilot Locally",
"request": "launch",
"localRoot": "${workspaceFolder}/Copilot",
"runtimeArgs": [
"run-script",
"start"
],
"runtimeExecutable": "npm",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
{
"name": "Debug Infrastructure Agent",
"type": "go",

View File

@@ -1,37 +1,37 @@
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
import logger from 'CommonServer/Utils/Logger';
import App from 'CommonServer/Utils/StartServer';
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
import logger from "CommonServer/Utils/Logger";
import App from "CommonServer/Utils/StartServer";
export const APP_NAME: string = 'accounts';
export const APP_NAME: string = "accounts";
const app: ExpressApplication = Express.getExpressApp();
const init: PromiseVoidFunction = async (): Promise<void> => {
try {
// init the app
await App.init({
appName: APP_NAME,
port: undefined,
isFrontendApp: true,
statusOptions: {
liveCheck: async () => {},
readyCheck: async () => {},
},
});
// add default routes
await App.addDefaultRoutes();
} catch (err) {
logger.error('App Init Failed:');
logger.error(err);
throw err;
}
try {
// init the app
await App.init({
appName: APP_NAME,
port: undefined,
isFrontendApp: true,
statusOptions: {
liveCheck: async () => {},
readyCheck: async () => {},
},
});
// add default routes
await App.addDefaultRoutes();
} catch (err) {
logger.error("App Init Failed:");
logger.error(err);
throw err;
}
};
init().catch((err: Error) => {
logger.error(err);
logger.error('Exiting node process');
process.exit(1);
logger.error(err);
logger.error("Exiting node process");
process.exit(1);
});
export default app;

8
Accounts/index.d.ts vendored
View File

@@ -1,4 +1,4 @@
declare module '*.png';
declare module '*.svg';
declare module '*.jpg';
declare module '*.gif';
declare module "*.png";
declare module "*.svg";
declare module "*.jpg";
declare module "*.gif";

View File

@@ -1,47 +1,47 @@
import ForgotPasswordPage from './Pages/ForgotPassword';
import LoginPage from './Pages/Login';
import NotFound from './Pages/NotFound';
import RegisterPage from './Pages/Register';
import ResetPasswordPage from './Pages/ResetPassword';
import VerifyEmail from './Pages/VerifyEmail';
import Navigation from 'CommonUI/src/Utils/Navigation';
import React, { ReactElement } from 'react';
import ForgotPasswordPage from "./Pages/ForgotPassword";
import LoginPage from "./Pages/Login";
import LoginWithSSO from "./Pages/LoginWithSSO";
import NotFound from "./Pages/NotFound";
import RegisterPage from "./Pages/Register";
import ResetPasswordPage from "./Pages/ResetPassword";
import VerifyEmail from "./Pages/VerifyEmail";
import Navigation from "CommonUI/src/Utils/Navigation";
import React, { ReactElement } from "react";
import {
Route,
Routes,
useLocation,
useNavigate,
useParams,
} from 'react-router-dom';
Route,
Routes,
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
function App(): ReactElement {
Navigation.setNavigateHook(useNavigate());
Navigation.setLocation(useLocation());
Navigation.setParams(useParams());
Navigation.setNavigateHook(useNavigate());
Navigation.setLocation(useLocation());
Navigation.setParams(useParams());
return (
<div className="m-auto h-screen">
<Routes>
<Route path="/accounts" element={<LoginPage />} />
<Route path="/accounts/login" element={<LoginPage />} />
<Route
path="/accounts/forgot-password"
element={<ForgotPasswordPage />}
/>
<Route
path="/accounts/reset-password/:token"
element={<ResetPasswordPage />}
/>
<Route path="/accounts/register" element={<RegisterPage />} />
<Route
path="/accounts/verify-email/:token"
element={<VerifyEmail />}
/>
{/* 👇️ only match this when no other routes match */}
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
return (
<div className="m-auto h-screen">
<Routes>
<Route path="/accounts" element={<LoginPage />} />
<Route path="/accounts/login" element={<LoginPage />} />
<Route path="/accounts/sso" element={<LoginWithSSO />} />
<Route
path="/accounts/forgot-password"
element={<ForgotPasswordPage />}
/>
<Route
path="/accounts/reset-password/:token"
element={<ResetPasswordPage />}
/>
<Route path="/accounts/register" element={<RegisterPage />} />
<Route path="/accounts/verify-email/:token" element={<VerifyEmail />} />
{/* 👇️ only match this when no other routes match */}
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
}
export default App;

View File

@@ -1,20 +1,20 @@
import React from 'react';
import { Link } from 'react-router-dom';
import React from "react";
import { Link } from "react-router-dom";
const Footer: () => JSX.Element = () => {
return (
<div className="footer">
<p>
<Link to="/">&copy; OneUptime</Link>
</p>
<p>
<Link to="/">Contact</Link>
</p>
<p>
<Link to="/">Privacy &amp; terms</Link>
</p>
</div>
);
return (
<div className="footer">
<p>
<Link to="/">&copy; OneUptime</Link>
</p>
<p>
<Link to="/">Contact</Link>
</p>
<p>
<Link to="/">Privacy &amp; terms</Link>
</p>
</div>
);
};
export default Footer;

View File

@@ -1,19 +1,19 @@
import App from './App';
import Telemetry from 'CommonUI/src/Utils/Telemetry';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from "./App";
import Telemetry from "CommonUI/src/Utils/Telemetry";
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
Telemetry.init({
serviceName: 'Accounts',
serviceName: "Accounts",
});
const root: any = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
document.getElementById("root") as HTMLElement,
);
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
<BrowserRouter>
<App />
</BrowserRouter>,
);

View File

@@ -1,99 +1,99 @@
import { FORGOT_PASSWORD_API_URL } from '../Utils/ApiPaths';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import Link from 'CommonUI/src/Components/Link/Link';
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
import User from 'Model/Models/User';
import React, { useState } from 'react';
import { FORGOT_PASSWORD_API_URL } from "../Utils/ApiPaths";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import User from "Model/Models/User";
import React, { useState } from "react";
const ForgotPassword: () => JSX.Element = () => {
const apiUrl: URL = FORGOT_PASSWORD_API_URL;
const apiUrl: URL = FORGOT_PASSWORD_API_URL;
const [isSuccess, setIsSuccess] = useState<boolean>(false);
const [isSuccess, setIsSuccess] = useState<boolean>(false);
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="Your Company"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Forgot your password
</h2>
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="Your Company"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Forgot your password
</h2>
{!isSuccess && (
<p className="mt-2 text-center text-sm text-gray-600">
Please enter your email and the password reset link will
be sent to you.
</p>
)}
{!isSuccess && (
<p className="mt-2 text-center text-sm text-gray-600">
Please enter your email and the password reset link will be sent to
you.
</p>
)}
{isSuccess && (
<p className="mt-2 text-center text-sm text-gray-600">
We have emailed you the password reset link. Please do
not forget to check spam.
</p>
)}
</div>
{isSuccess && (
<p className="mt-2 text-center text-sm text-gray-600">
We have emailed you the password reset link. Please do not forget to
check spam.
</p>
)}
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
{!isSuccess && (
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<ModelForm<User>
modelType={User}
name="Forgot Password"
id="login-form"
createOrUpdateApiUrl={apiUrl}
fields={[
{
field: {
email: true,
},
title: 'Email',
fieldType: FormFieldSchemaType.Email,
required: true,
},
]}
onSuccess={() => {
setIsSuccess(true);
}}
submitButtonText={'Send Password Reset Link'}
formType={FormType.Create}
maxPrimaryButtonWidth={true}
footer={
<div className="actions pointer text-center mt-4 hover:underline fw-semibold">
<p>
<Link
to={new Route('/accounts/login')}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
>
Return to Sign in.
</Link>
</p>
</div>
}
/>
</div>
)}
<div className="mt-5 text-center">
<p className="text-muted mb-0 text-gray-500">
Remember your password?{' '}
<Link
to={new Route('/accounts/login')}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Login.
</Link>
</p>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
{!isSuccess && (
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<ModelForm<User>
modelType={User}
name="Forgot Password"
id="login-form"
createOrUpdateApiUrl={apiUrl}
fields={[
{
field: {
email: true,
},
title: "Email",
fieldType: FormFieldSchemaType.Email,
required: true,
},
]}
onSuccess={() => {
setIsSuccess(true);
}}
submitButtonText={"Send Password Reset Link"}
formType={FormType.Create}
maxPrimaryButtonWidth={true}
footer={
<div className="actions pointer text-center mt-4 hover:underline fw-semibold">
<p>
<Link
to={new Route("/accounts/login")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
>
Return to Sign in.
</Link>
</p>
</div>
</div>
}
/>
</div>
)}
<div className="mt-5 text-center">
<p className="text-muted mb-0 text-gray-500">
Remember your password?{" "}
<Link
to={new Route("/accounts/login")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Login.
</Link>
</p>
</div>
);
</div>
</div>
);
};
export default ForgotPassword;

View File

@@ -1,167 +1,132 @@
import { LOGIN_API_URL } from '../Utils/ApiPaths';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import { JSONObject } from 'Common/Types/JSON';
import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert';
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import Link from 'CommonUI/src/Components/Link/Link';
import { DASHBOARD_URL } from 'CommonUI/src/Config';
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
import LoginUtil from 'CommonUI/src/Utils/Login';
import Navigation from 'CommonUI/src/Utils/Navigation';
import UserUtil from 'CommonUI/src/Utils/User';
import User from 'Model/Models/User';
import React, { useState } from 'react';
import useAsyncEffect from 'use-async-effect';
import { LOGIN_API_URL } from "../Utils/ApiPaths";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import { JSONObject } from "Common/Types/JSON";
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import { DASHBOARD_URL } from "CommonUI/src/Config";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import UiAnalytics from "CommonUI/src/Utils/Analytics";
import LoginUtil from "CommonUI/src/Utils/Login";
import Navigation from "CommonUI/src/Utils/Navigation";
import UserUtil from "CommonUI/src/Utils/User";
import User from "Model/Models/User";
import React from "react";
import useAsyncEffect from "use-async-effect";
const LoginPage: () => JSX.Element = () => {
const apiUrl: URL = LOGIN_API_URL;
const apiUrl: URL = LOGIN_API_URL;
if (UserUtil.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
if (UserUtil.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
}
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
useAsyncEffect(async () => {
if (Navigation.getQueryStringByName("email")) {
setInitialValues({
email: Navigation.getQueryStringByName("email"),
});
}
}, []);
const showSsoMessage: boolean = Boolean(
Navigation.getQueryStringByName('sso')
);
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Sign in to your account
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Join thousands of business that use OneUptime to help them stay online
all the time.
</p>
</div>
const [showSsoTip, setShowSSOTip] = useState<boolean>(false);
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<ModelForm<User>
modelType={User}
id="login-form"
name="Login"
fields={[
{
field: {
email: true,
},
fieldType: FormFieldSchemaType.Email,
placeholder: "jeff@example.com",
required: true,
disabled: Boolean(initialValues && initialValues["email"]),
title: "Email",
dataTestId: "email",
},
{
field: {
password: true,
},
title: "Password",
required: true,
validation: {
minLength: 6,
},
fieldType: FormFieldSchemaType.Password,
sideLink: {
text: "Forgot password?",
url: new Route("/accounts/forgot-password"),
openLinkInNewTab: false,
},
dataTestId: "password",
},
]}
createOrUpdateApiUrl={apiUrl}
formType={FormType.Create}
submitButtonText={"Login"}
onSuccess={(value: User, miscData: JSONObject | undefined) => {
if (value && value.email) {
UiAnalytics.userAuth(value.email);
UiAnalytics.capture("accounts/login");
}
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
useAsyncEffect(async () => {
if (Navigation.getQueryStringByName('email')) {
setInitialValues({
email: Navigation.getQueryStringByName('email'),
});
}
}, []);
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Sign in to your account
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Join thousands of business that use OneUptime to help them
stay online all the time.
</p>
</div>
{showSsoMessage && (
<div className="sm:mx-auto sm:w-full sm:max-w-md mt-8">
{' '}
<Alert
type={AlertType.DANGER}
title="You must be logged into OneUptime account to use single sign-on (SSO) for your project. Logging in to OneUptime account and single sign on (SSO) for your project are two separate steps. Please use the form below to log in to your OneUptime account before you use SSO."
/>{' '}
</div>
)}
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<ModelForm<User>
modelType={User}
id="login-form"
name="Login"
fields={[
{
field: {
email: true,
},
fieldType: FormFieldSchemaType.Email,
placeholder: 'jeff@example.com',
required: true,
disabled: Boolean(
initialValues && initialValues['email']
),
title: 'Email',
dataTestId: 'email',
},
{
field: {
password: true,
},
title: 'Password',
required: true,
validation: {
minLength: 6,
},
fieldType: FormFieldSchemaType.Password,
sideLink: {
text: 'Forgot password?',
url: new Route('/accounts/forgot-password'),
openLinkInNewTab: false,
},
dataTestId: 'password',
},
]}
createOrUpdateApiUrl={apiUrl}
formType={FormType.Create}
submitButtonText={'Login'}
onSuccess={(
value: User,
miscData: JSONObject | undefined
) => {
if (value && value.email) {
UiAnalytics.userAuth(value.email);
UiAnalytics.capture('accounts/login');
}
LoginUtil.login({
user: value,
token: miscData ? miscData['token'] : undefined,
});
}}
maxPrimaryButtonWidth={true}
footer={
<div className="actions text-center mt-4 hover:underline fw-semibold">
<div>
{!showSsoTip && (
<div
onClick={() => {
setShowSSOTip(true);
}}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
>
Use single sign-on (SSO) instead
</div>
)}
{showSsoTip && (
<div className="text-gray-500 text-sm">
Please sign in with your SSO
provider like Okta, Auth0, Entra ID
or any other SAML 2.0 provider.
</div>
)}
</div>
</div>
}
/>
</div>
<div className="mt-10 text-center">
<div className="text-muted mb-0 text-gray-500">
Don&apos;t have an account?{' '}
<Link
to={new Route('/accounts/register')}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Register.
</Link>
LoginUtil.login({
user: value,
token: miscData ? miscData["token"] : undefined,
});
}}
maxPrimaryButtonWidth={true}
footer={
<div className="actions text-center mt-4 hover:underline fw-semibold">
<div>
<Link to={new Route("/accounts/sso")}>
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
Use single sign-on (SSO) instead
</div>
</Link>
</div>
</div>
</div>
}
/>
</div>
);
<div className="mt-10 text-center">
<div className="text-muted mb-0 text-gray-500">
Don&apos;t have an account?{" "}
<Link
to={new Route("/accounts/register")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Register.
</Link>
</div>
</div>
</div>
</div>
);
};
export default LoginPage;

View File

@@ -0,0 +1,235 @@
import { SERVICE_PROVIDER_LOGIN_URL } from "../Utils/ApiPaths";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import { JSONArray, JSONObject } from "Common/Types/JSON";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import { DASHBOARD_URL, IDENTITY_URL } from "CommonUI/src/Config";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import Navigation from "CommonUI/src/Utils/Navigation";
import UserUtil from "CommonUI/src/Utils/User";
import User from "Model/Models/User";
import React, { ReactElement, useState } from "react";
import ProjectSSO from "Model/Models/ProjectSso";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import API from "CommonUI/src/Utils/API/API";
import BasicForm from "CommonUI/src/Components/Forms/BasicForm";
import Email from "Common/Types/Email";
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
import HTTPResponse from "Common/Types/API/HTTPResponse";
import StaticModelList from "CommonUI/src/Components/ModelList/StaticModelList";
const LoginPage: () => JSX.Element = () => {
const apiUrl: URL = SERVICE_PROVIDER_LOGIN_URL;
if (UserUtil.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
}
const [error, setError] = useState<string | undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [projectSsoConfigList, setProjectSsoConfigList] = useState<
Array<ProjectSSO>
>([]);
type FetchSSOConfigsFunction = (email: Email) => Promise<void>;
const fetchSsoConfigs: FetchSSOConfigsFunction = async (
email: Email,
): Promise<void> => {
if (email) {
setIsLoading(true);
try {
// get sso config by email.
const listResult: HTTPErrorResponse | HTTPResponse<JSONArray> =
await API.get(
URL.fromString(apiUrl.toString()).addQueryParam(
"email",
email.toString(),
),
);
if (listResult instanceof HTTPErrorResponse) {
throw listResult;
}
if (!listResult.data || (listResult.data as JSONArray).length === 0) {
setError(
"No SSO configuration found for the email: " + email.toString(),
);
} else {
setProjectSsoConfigList(
ProjectSSO.fromJSONArray(listResult["data"], ProjectSSO),
);
}
} catch (error) {
setError(API.getFriendlyErrorMessage(error as Error));
}
} else {
setError("Email is required to perform this action");
}
setIsLoading(false);
};
type GetSsoConfigModelListFunction = (
configs: Array<ProjectSSO>,
) => ReactElement;
const getSsoConfigModelList: GetSsoConfigModelListFunction = (
configs: Array<ProjectSSO>,
): ReactElement => {
return (
<StaticModelList<ProjectSSO>
list={configs}
titleField="name"
selectedItems={[]}
descriptionField="description"
onClick={(item: ProjectSSO) => {
setIsLoading(true);
Navigation.navigate(
URL.fromURL(IDENTITY_URL).addRoute(
new Route(
`/sso/${item.projectId?.toString()}/${item.id?.toString()}`,
),
),
);
}}
/>
);
};
if (isLoading) {
return <PageLoader isVisible={true} />;
}
type GetProjectNameFunction = (projectId: string) => string;
const getProjectName: GetProjectNameFunction = (
projectId: string,
): string => {
const projectNames: Array<string | undefined> = projectSsoConfigList
.filter((config: ProjectSSO) => {
return config.projectId?.toString() === projectId.toString();
})
.map((config: ProjectSSO) => {
return config.project?.name;
});
return projectNames[0] || "Project";
};
if (projectSsoConfigList.length > 0 && !error && !isLoading) {
const projectIds: Array<string> = projectSsoConfigList.map(
(config: ProjectSSO) => {
return config.projectId?.toString() as string;
},
);
return (
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-10 text-center text-xl tracking-tight text-gray-900">
Select Project
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Select the project you want to login to.
</p>
</div>
{projectIds.map((projectId: string) => {
return (
<div key={projectId}>
<h3 className="mt-6 font-medium tracking-tight">
{getProjectName(projectId)}
</h3>
{getSsoConfigModelList(
projectSsoConfigList.filter((config: ProjectSSO) => {
return (
config.projectId?.toString() === projectId.toString()
);
}),
)}
</div>
);
})}
</div>
</div>
);
}
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Login with SSO
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Login with your SSO provider to access your account.
</p>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<BasicForm
modelType={User}
id="login-form"
error={error}
name="Login"
fields={[
{
field: {
email: true,
},
fieldType: FormFieldSchemaType.Email,
placeholder: "jeff@example.com",
required: true,
title: "Email",
dataTestId: "email",
},
]}
maxPrimaryButtonWidth={true}
submitButtonText="Login with SSO"
onSubmit={async (data: JSONObject) => {
await fetchSsoConfigs(data["email"] as Email);
}}
footer={
<div className="actions text-center mt-4 hover:underline fw-semibold">
<div>
<Link to={new Route("/accounts/login")}>
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
Use username and password insead.
</div>
</Link>
</div>
</div>
}
/>
</div>
<div className="mt-10 text-center">
<div className="text-muted mb-0 text-gray-500">
Don&apos;t have an account?{" "}
<Link
to={new Route("/accounts/register")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Register.
</Link>
</div>
</div>
</div>
</div>
);
};
export default LoginPage;

View File

@@ -1,18 +1,18 @@
import React from 'react';
import React from "react";
const LoginPage: () => JSX.Element = () => {
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Page not found
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Page you are looking for does not exist.
</p>
</div>
</div>
);
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Page not found
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Page you are looking for does not exist.
</p>
</div>
</div>
);
};
export default LoginPage;

View File

@@ -1,276 +1,267 @@
import { SIGNUP_API_URL } from '../Utils/ApiPaths';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import Dictionary from 'Common/Types/Dictionary';
import { JSONObject } from 'Common/Types/JSON';
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
import Fields from 'CommonUI/src/Components/Forms/Types/Fields';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import Link from 'CommonUI/src/Components/Link/Link';
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
import { BILLING_ENABLED, DASHBOARD_URL } from 'CommonUI/src/Config';
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
import BaseAPI from 'CommonUI/src/Utils/API/API';
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
import LocalStorage from 'CommonUI/src/Utils/LocalStorage';
import LoginUtil from 'CommonUI/src/Utils/Login';
import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import Navigation from 'CommonUI/src/Utils/Navigation';
import UserUtil from 'CommonUI/src/Utils/User';
import Reseller from 'Model/Models/Reseller';
import User from 'Model/Models/User';
import React, { useState } from 'react';
import useAsyncEffect from 'use-async-effect';
import { SIGNUP_API_URL } from "../Utils/ApiPaths";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import Dictionary from "Common/Types/Dictionary";
import { JSONObject } from "Common/Types/JSON";
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import Fields from "CommonUI/src/Components/Forms/Types/Fields";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import { BILLING_ENABLED, DASHBOARD_URL } from "CommonUI/src/Config";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import BaseAPI from "CommonUI/src/Utils/API/API";
import UiAnalytics from "CommonUI/src/Utils/Analytics";
import LocalStorage from "CommonUI/src/Utils/LocalStorage";
import LoginUtil from "CommonUI/src/Utils/Login";
import ModelAPI, { ListResult } from "CommonUI/src/Utils/ModelAPI/ModelAPI";
import Navigation from "CommonUI/src/Utils/Navigation";
import UserUtil from "CommonUI/src/Utils/User";
import Reseller from "Model/Models/Reseller";
import User from "Model/Models/User";
import React, { useState } from "react";
import useAsyncEffect from "use-async-effect";
const RegisterPage: () => JSX.Element = () => {
const apiUrl: URL = SIGNUP_API_URL;
const apiUrl: URL = SIGNUP_API_URL;
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
const [error, setError] = useState<string>('');
const [error, setError] = useState<string>("");
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [reseller, setResller] = React.useState<Reseller | undefined>(
undefined
);
const [reseller, setResller] = React.useState<Reseller | undefined>(
undefined,
);
if (UserUtil.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
if (UserUtil.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
}
type FetchResellerFunction = (resellerId: string) => Promise<void>;
const fetchReseller: FetchResellerFunction = async (
resellerId: string,
): Promise<void> => {
setIsLoading(true);
try {
const reseller: ListResult<Reseller> = await ModelAPI.getList<Reseller>({
modelType: Reseller,
query: {
resellerId: resellerId,
},
limit: 1,
skip: 0,
select: {
hidePhoneNumberOnSignup: true,
},
sort: {},
requestOptions: {},
});
if (reseller.data.length > 0) {
setResller(reseller.data[0]);
}
} catch (err) {
setError(BaseAPI.getFriendlyMessage(err));
}
type FetchResellerFunction = (resellerId: string) => Promise<void>;
setIsLoading(false);
};
const fetchReseller: FetchResellerFunction = async (
resellerId: string
): Promise<void> => {
setIsLoading(true);
try {
const reseller: ListResult<Reseller> =
await ModelAPI.getList<Reseller>({
modelType: Reseller,
query: {
resellerId: resellerId,
},
limit: 1,
skip: 0,
select: {
hidePhoneNumberOnSignup: true,
},
sort: {},
requestOptions: {},
});
if (reseller.data.length > 0) {
setResller(reseller.data[0]);
}
} catch (err) {
setError(BaseAPI.getFriendlyMessage(err));
}
setIsLoading(false);
};
useAsyncEffect(async () => {
// if promo code is found, please save it in localstorage.
if (Navigation.getQueryStringByName('promoCode')) {
LocalStorage.setItem(
'promoCode',
Navigation.getQueryStringByName('promoCode')
);
}
if (Navigation.getQueryStringByName('email')) {
setInitialValues({
email: Navigation.getQueryStringByName('email'),
});
}
// if promo code is found, please save it in localstorage.
if (Navigation.getQueryStringByName('partnerId')) {
await fetchReseller(Navigation.getQueryStringByName('partnerId')!);
}
}, []);
let formFields: Fields<User> = [
{
field: {
email: true,
},
fieldType: FormFieldSchemaType.Email,
placeholder: 'jeff@example.com',
required: true,
disabled: Boolean(initialValues && initialValues['email']),
title: 'Email',
dataTestId: 'email',
},
{
field: {
name: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: 'Jeff Smith',
required: true,
title: 'Full Name',
dataTestId: 'name',
},
];
if (BILLING_ENABLED) {
formFields = formFields.concat([
{
field: {
companyName: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: 'Acme, Inc.',
required: true,
title: 'Company Name',
dataTestId: 'companyName',
},
]);
// If reseller wants to hide phone number on sign up, we hide it.
if (!reseller || !reseller.hidePhoneNumberOnSignup) {
formFields.push({
field: {
companyPhoneNumber: true,
},
fieldType: FormFieldSchemaType.Phone,
required: true,
placeholder: '+11234567890',
title: 'Phone Number',
dataTestId: 'companyPhoneNumber',
});
}
useAsyncEffect(async () => {
// if promo code is found, please save it in localstorage.
if (Navigation.getQueryStringByName("promoCode")) {
LocalStorage.setItem(
"promoCode",
Navigation.getQueryStringByName("promoCode"),
);
}
if (Navigation.getQueryStringByName("email")) {
setInitialValues({
email: Navigation.getQueryStringByName("email"),
});
}
// if promo code is found, please save it in localstorage.
if (Navigation.getQueryStringByName("partnerId")) {
await fetchReseller(Navigation.getQueryStringByName("partnerId")!);
}
}, []);
let formFields: Fields<User> = [
{
field: {
email: true,
},
fieldType: FormFieldSchemaType.Email,
placeholder: "jeff@example.com",
required: true,
disabled: Boolean(initialValues && initialValues["email"]),
title: "Email",
dataTestId: "email",
},
{
field: {
name: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: "Jeff Smith",
required: true,
title: "Full Name",
dataTestId: "name",
},
];
if (BILLING_ENABLED) {
formFields = formFields.concat([
{
field: {
password: true,
},
fieldType: FormFieldSchemaType.Password,
validation: {
minLength: 6,
},
placeholder: 'Password',
title: 'Password',
required: true,
dataTestId: 'password',
},
{
field: {
confirmPassword: true,
} as any,
validation: {
minLength: 6,
toMatchField: 'password',
},
fieldType: FormFieldSchemaType.Password,
placeholder: 'Confirm Password',
title: 'Confirm Password',
overrideFieldKey: 'confirmPassword',
required: true,
showEvenIfPermissionDoesNotExist: true,
dataTestId: 'confirmPassword',
{
field: {
companyName: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: "Acme, Inc.",
required: true,
title: "Company Name",
dataTestId: "companyName",
},
]);
if (error) {
return <ErrorMessage error={error} />;
// If reseller wants to hide phone number on sign up, we hide it.
if (!reseller || !reseller.hidePhoneNumberOnSignup) {
formFields.push({
field: {
companyPhoneNumber: true,
},
fieldType: FormFieldSchemaType.Phone,
required: true,
placeholder: "+11234567890",
title: "Phone Number",
dataTestId: "companyPhoneNumber",
});
}
}
if (isLoading) {
return <PageLoader isVisible={true} />;
}
formFields = formFields.concat([
{
field: {
password: true,
},
fieldType: FormFieldSchemaType.Password,
validation: {
minLength: 6,
},
placeholder: "Password",
title: "Password",
required: true,
dataTestId: "password",
},
{
field: {
confirmPassword: true,
} as any,
validation: {
minLength: 6,
toMatchField: "password",
},
fieldType: FormFieldSchemaType.Password,
placeholder: "Confirm Password",
title: "Confirm Password",
overrideFieldKey: "confirmPassword",
required: true,
showEvenIfPermissionDoesNotExist: true,
dataTestId: "confirmPassword",
},
]);
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Create your OneUptime account
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Join thousands of business that use OneUptime to help them
stay online all the time.
</p>
<p className="mt-2 text-center text-sm text-gray-600">
No credit card required.
</p>
</div>
if (error) {
return <ErrorMessage error={error} />;
}
<div className="mt-8 lg:mx-auto lg:w-full lg:max-w-2xl">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<ModelForm<User>
modelType={User}
id="register-form"
showAsColumns={reseller ? 1 : 2}
name="Register"
initialValues={initialValues}
maxPrimaryButtonWidth={true}
fields={formFields}
createOrUpdateApiUrl={apiUrl}
onBeforeCreate={(item: User): Promise<User> => {
const utmParams: Dictionary<string> =
UserUtil.getUtmParams();
if (isLoading) {
return <PageLoader isVisible={true} />;
}
if (
utmParams &&
Object.keys(utmParams).length > 0
) {
item.utmSource = utmParams['utmSource'] || '';
item.utmMedium = utmParams['utmMedium'] || '';
item.utmCampaign =
utmParams['utmCampaign'] || '';
item.utmTerm = utmParams['utmTerm'] || '';
item.utmContent = utmParams['utmContent'] || '';
item.utmUrl = utmParams['utmUrl'] || '';
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Create your OneUptime account
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Join thousands of business that use OneUptime to help them stay online
all the time.
</p>
<p className="mt-2 text-center text-sm text-gray-600">
No credit card required.
</p>
</div>
UiAnalytics.capture('utm_event', utmParams);
}
<div className="mt-8 lg:mx-auto lg:w-full lg:max-w-2xl">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<ModelForm<User>
modelType={User}
id="register-form"
showAsColumns={reseller ? 1 : 2}
name="Register"
initialValues={initialValues}
maxPrimaryButtonWidth={true}
fields={formFields}
createOrUpdateApiUrl={apiUrl}
onBeforeCreate={(item: User): Promise<User> => {
const utmParams: Dictionary<string> = UserUtil.getUtmParams();
return Promise.resolve(item);
}}
formType={FormType.Create}
submitButtonText={'Sign Up'}
onSuccess={(
value: User,
miscData: JSONObject | undefined
) => {
if (value && value.email) {
UiAnalytics.userAuth(value.email);
UiAnalytics.capture('accounts/register');
}
if (utmParams && Object.keys(utmParams).length > 0) {
item.utmSource = utmParams["utmSource"] || "";
item.utmMedium = utmParams["utmMedium"] || "";
item.utmCampaign = utmParams["utmCampaign"] || "";
item.utmTerm = utmParams["utmTerm"] || "";
item.utmContent = utmParams["utmContent"] || "";
item.utmUrl = utmParams["utmUrl"] || "";
LoginUtil.login({
user: value,
token: miscData ? miscData['token'] : undefined,
});
}}
/>
</div>
<div className="mt-5 text-center text-gray-500">
<p className="text-muted mb-0">
Already have an account?{' '}
<Link
to={new Route('/accounts/login')}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Log in.
</Link>
</p>
</div>
</div>
UiAnalytics.capture("utm_event", utmParams);
}
return Promise.resolve(item);
}}
formType={FormType.Create}
submitButtonText={"Sign Up"}
onSuccess={(value: User, miscData: JSONObject | undefined) => {
if (value && value.email) {
UiAnalytics.userAuth(value.email);
UiAnalytics.capture("accounts/register");
}
LoginUtil.login({
user: value,
token: miscData ? miscData["token"] : undefined,
});
}}
/>
</div>
);
<div className="mt-5 text-center text-gray-500">
<p className="text-muted mb-0">
Already have an account?{" "}
<Link
to={new Route("/accounts/login")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Log in.
</Link>
</p>
</div>
</div>
</div>
);
};
export default RegisterPage;

View File

@@ -1,113 +1,114 @@
import { RESET_PASSWORD_API_URL } from '../Utils/ApiPaths';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import Link from 'CommonUI/src/Components/Link/Link';
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
import Navigation from 'CommonUI/src/Utils/Navigation';
import User from 'Model/Models/User';
import React, { useState } from 'react';
import { RESET_PASSWORD_API_URL } from "../Utils/ApiPaths";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import ModelForm, { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import Link from "CommonUI/src/Components/Link/Link";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import Navigation from "CommonUI/src/Utils/Navigation";
import User from "Model/Models/User";
import React, { useState } from "react";
const RegisterPage: () => JSX.Element = () => {
const apiUrl: URL = RESET_PASSWORD_API_URL;
const [isSuccess, setIsSuccess] = useState<boolean>(false);
const apiUrl: URL = RESET_PASSWORD_API_URL;
const [isSuccess, setIsSuccess] = useState<boolean>(false);
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="Your Company"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Reset your password
</h2>
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="Your Company"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Reset your password
</h2>
{!isSuccess && (
<p className="mt-2 text-center text-sm text-gray-600">
Please enter your new password and we will have it
updated.{' '}
</p>
)}
{!isSuccess && (
<p className="mt-2 text-center text-sm text-gray-600">
Please enter your new password and we will have it updated.{" "}
</p>
)}
{isSuccess && (
<p className="mt-2 text-center text-sm text-gray-600">
Your password has been updated. Please log in.
</p>
)}
</div>
{isSuccess && (
<p className="mt-2 text-center text-sm text-gray-600">
Your password has been updated. Please log in.
</p>
)}
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
{!isSuccess && (
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<ModelForm<User>
modelType={User}
id="register-form"
name="Reset Password"
onBeforeCreate={(item: User): Promise<User> => {
item.resetPasswordToken =
Navigation.getLastParam()
?.toString()
.replace('/', '')
.toString() || '';
return Promise.resolve(item);
}}
showAsColumns={1}
maxPrimaryButtonWidth={true}
fields={[
{
field: {
password: true,
},
fieldType: FormFieldSchemaType.Password,
validation: {
minLength: 6,
},
placeholder: 'New Password',
title: 'New Password',
required: true,
},
{
field: {
password: true,
},
validation: {
minLength: 6,
toMatchField: 'password',
},
fieldType: FormFieldSchemaType.Password,
placeholder: 'Confirm Password',
title: 'Confirm Password',
overrideFieldKey: 'confirmPassword',
required: true,
},
]}
createOrUpdateApiUrl={apiUrl}
formType={FormType.Create}
submitButtonText={'Reset Password'}
onSuccess={() => {
setIsSuccess(true);
}}
/>
</div>
)}
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
{!isSuccess && (
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<ModelForm<User>
modelType={User}
id="register-form"
name="Reset Password"
onBeforeCreate={(item: User): Promise<User> => {
item.resetPasswordToken =
Navigation.getLastParam()
?.toString()
.replace("/", "")
.toString() || "";
return Promise.resolve(item);
}}
showAsColumns={1}
maxPrimaryButtonWidth={true}
fields={[
{
field: {
password: true,
},
fieldType: FormFieldSchemaType.Password,
validation: {
minLength: 6,
},
placeholder: "New Password",
title: "New Password",
required: true,
showEvenIfPermissionDoesNotExist: true,
},
{
field: {
confirmPassword: true,
} as any,
validation: {
minLength: 6,
toMatchField: "password",
},
fieldType: FormFieldSchemaType.Password,
placeholder: "Confirm Password",
title: "Confirm Password",
overrideFieldKey: "confirmPassword",
required: true,
showEvenIfPermissionDoesNotExist: true,
},
]}
createOrUpdateApiUrl={apiUrl}
formType={FormType.Create}
submitButtonText={"Reset Password"}
onSuccess={() => {
setIsSuccess(true);
}}
/>
</div>
)}
<div className="mt-5 text-center">
<p className="text-muted mb-0 text-gray-500">
Know your password?{' '}
<Link
to={new Route('/accounts/login')}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Log in.
</Link>
</p>
</div>
</div>
<div className="mt-5 text-center">
<p className="text-muted mb-0 text-gray-500">
Know your password?{" "}
<Link
to={new Route("/accounts/login")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Log in.
</Link>
</p>
</div>
);
</div>
</div>
);
};
export default RegisterPage;

View File

@@ -1,132 +1,121 @@
import { VERIFY_EMAIL_API_URL } from '../Utils/ApiPaths';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
import ObjectID from 'Common/Types/ObjectID';
import { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
import Link from 'CommonUI/src/Components/Link/Link';
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
import API from 'CommonUI/src/Utils/API/API';
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import Navigation from 'CommonUI/src/Utils/Navigation';
import EmailVerificationToken from 'Model/Models/EmailVerificationToken';
import React, { useEffect, useState } from 'react';
import { VERIFY_EMAIL_API_URL } from "../Utils/ApiPaths";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import ObjectID from "Common/Types/ObjectID";
import { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import Link from "CommonUI/src/Components/Link/Link";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import API from "CommonUI/src/Utils/API/API";
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
import Navigation from "CommonUI/src/Utils/Navigation";
import EmailVerificationToken from "Model/Models/EmailVerificationToken";
import React, { useEffect, useState } from "react";
const VerifyEmail: () => JSX.Element = () => {
const apiUrl: URL = VERIFY_EMAIL_API_URL;
const [error, setError] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(true);
const apiUrl: URL = VERIFY_EMAIL_API_URL;
const [error, setError] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(true);
const init: PromiseVoidFunction = async (): Promise<void> => {
// Ping an API here.
setError('');
setIsLoading(true);
const init: PromiseVoidFunction = async (): Promise<void> => {
// Ping an API here.
setError("");
setIsLoading(true);
try {
// strip data.
const emailverificationToken: EmailVerificationToken =
new EmailVerificationToken();
emailverificationToken.token = new ObjectID(
Navigation.getLastParam()?.toString().replace('/', '') || ''
);
try {
// strip data.
const emailverificationToken: EmailVerificationToken =
new EmailVerificationToken();
emailverificationToken.token = new ObjectID(
Navigation.getLastParam()?.toString().replace("/", "") || "",
);
await ModelAPI.createOrUpdate<EmailVerificationToken>({
model: emailverificationToken,
modelType: EmailVerificationToken,
formType: FormType.Create,
miscDataProps: {},
requestOptions: {
overrideRequestUrl: apiUrl,
},
});
} catch (err) {
setError(API.getFriendlyMessage(err));
}
setIsLoading(false);
};
useEffect(() => {
init().catch((err: Error) => {
setError(err.toString());
});
}, []);
if (isLoading) {
return <PageLoader isVisible={true} />;
await ModelAPI.createOrUpdate<EmailVerificationToken>({
model: emailverificationToken,
modelType: EmailVerificationToken,
formType: FormType.Create,
miscDataProps: {},
requestOptions: {
overrideRequestUrl: apiUrl,
},
});
} catch (err) {
setError(API.getFriendlyMessage(err));
}
return (
<div className="auth-page">
<div className="container-fluid p-0">
<div className="row g-0">
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
setIsLoading(false);
};
<div className="col-xxl-4 col-lg-4 col-md-6">
<div className="auth-full-page-content d-flex p-sm-5 p-4">
<div className="w-100">
<div className="d-flex flex-column h-100">
<div className="auth-content my-auto">
<div
className="mt-4 text-center flex justify-center"
style={{ marginBottom: '40px' }}
>
<img
style={{ height: '50px' }}
src={`${OneUptimeLogo}`}
/>
</div>
{!error && (
<div className="text-center">
<h5 className="mb-0">
Your email is verified.
</h5>
<p className="text-muted mt-2 mb-0">
Thank you for verifying your
email. You can now log in to
OneUptime.{' '}
</p>
</div>
)}
useEffect(() => {
init().catch((err: Error) => {
setError(err.toString());
});
}, []);
{error && (
<div className="text-center">
<h5 className="mb-0">
Sorry, something went wrong!
</h5>
<p className="text-muted mt-2 mb-0">
{error}
</p>
</div>
)}
if (isLoading) {
return <PageLoader isVisible={true} />;
}
<div className="mt-5 text-center">
<p className="text-muted mb-0">
Return to sign in?{' '}
<Link
to={
new Route(
'/accounts/login'
)
}
className="hover:underline text-primary fw-semibold"
>
Login.
</Link>
</p>
</div>
</div>
</div>
</div>
</div>
return (
<div className="auth-page">
<div className="container-fluid p-0">
<div className="row g-0">
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
<div className="col-xxl-4 col-lg-4 col-md-6">
<div className="auth-full-page-content d-flex p-sm-5 p-4">
<div className="w-100">
<div className="d-flex flex-column h-100">
<div className="auth-content my-auto">
<div
className="mt-4 text-center flex justify-center"
style={{ marginBottom: "40px" }}
>
<img
style={{ height: "50px" }}
src={`${OneUptimeLogo}`}
/>
</div>
{!error && (
<div className="text-center">
<h5 className="mb-0">Your email is verified.</h5>
<p className="text-muted mt-2 mb-0">
Thank you for verifying your email. You can now log in
to OneUptime.{" "}
</p>
</div>
)}
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
{error && (
<div className="text-center">
<h5 className="mb-0">Sorry, something went wrong!</h5>
<p className="text-muted mt-2 mb-0">{error}</p>
</div>
)}
<div className="mt-5 text-center">
<p className="text-muted mb-0">
Return to sign in?{" "}
<Link
to={new Route("/accounts/login")}
className="hover:underline text-primary fw-semibold"
>
Login.
</Link>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="col-xxl-4 col-lg-4 col-md-3"></div>
</div>
);
</div>
</div>
);
};
export default VerifyEmail;

View File

@@ -1,22 +1,26 @@
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import { IDENTITY_URL } from 'CommonUI/src/Config';
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import { IDENTITY_URL } from "CommonUI/src/Config";
export const SIGNUP_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
new Route('/signup')
new Route("/signup"),
);
export const LOGIN_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
new Route('/login')
new Route("/login"),
);
export const SERVICE_PROVIDER_LOGIN_URL: URL = URL.fromURL(
IDENTITY_URL,
).addRoute(new Route("/service-provider-login"));
export const FORGOT_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
new Route('/forgot-password')
new Route("/forgot-password"),
);
export const VERIFY_EMAIL_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
new Route('/verify-email')
new Route("/verify-email"),
);
export const RESET_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
new Route('/reset-password')
new Route("/reset-password"),
);

View File

@@ -1,83 +1,84 @@
require('ts-loader');
require('file-loader');
require('style-loader');
require('css-loader');
require('sass-loader');
require("ts-loader");
require("file-loader");
require("style-loader");
require("css-loader");
require("sass-loader");
const path = require("path");
const webpack = require("webpack");
const dotenv = require('dotenv');
const express = require('express');
const dotenv = require("dotenv");
const express = require("express");
const readEnvFile = (pathToFile) => {
const parsed = dotenv.config({ path: pathToFile }).parsed;
const parsed = dotenv.config({ path: pathToFile }).parsed;
const env = {};
const env = {
};
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
return env;
}
return env;
};
module.exports = {
entry: "./src/Index.tsx",
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/accounts/dist/",
entry: "./src/Index.tsx",
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/accounts/dist/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
alias: {
react: path.resolve("./node_modules/react"),
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'],
alias: {
react: path.resolve('./node_modules/react'),
}
},
externals: {
'react-native-sqlite-storage': 'react-native-sqlite-storage'
},
plugins: [
new webpack.DefinePlugin({
'process': {
'env': {
...readEnvFile('/usr/src/app/dev-env/.env')
}
}
}),
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader'
},
{
test: /\.s[ac]ss$/i,
use: ['style-loader', 'css-loader', "sass-loader"]
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader']
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: 'file-loader'
}
],
},
devServer: {
historyApiFallback: true,
devMiddleware: {
writeToDisk: true,
},
externals: {
"react-native-sqlite-storage": "react-native-sqlite-storage",
},
plugins: [
new webpack.DefinePlugin({
process: {
env: {
...readEnvFile("/usr/src/app/dev-env/.env"),
},
allowedHosts: "all",
setupMiddlewares: (middlewares, devServer) => {
devServer.app.use('/accounts/assets', express.static(path.resolve(__dirname, 'public', 'assets')));
return middlewares;
}
},
}),
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: "ts-loader",
},
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: "file-loader",
},
],
},
devServer: {
historyApiFallback: true,
devMiddleware: {
writeToDisk: true,
},
devtool: 'eval-source-map',
}
allowedHosts: "all",
setupMiddlewares: (middlewares, devServer) => {
devServer.app.use(
"/accounts/assets",
express.static(path.resolve(__dirname, "public", "assets")),
);
return middlewares;
},
},
devtool: "eval-source-map",
};

View File

@@ -1,38 +1,38 @@
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
import logger from 'CommonServer/Utils/Logger';
import App from 'CommonServer/Utils/StartServer';
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
import logger from "CommonServer/Utils/Logger";
import App from "CommonServer/Utils/StartServer";
export const APP_NAME: string = 'admin';
export const APP_NAME: string = "admin";
const app: ExpressApplication = Express.getExpressApp();
const init: PromiseVoidFunction = async (): Promise<void> => {
try {
// init the app
await App.init({
appName: APP_NAME,
port: undefined,
isFrontendApp: true,
statusOptions: {
liveCheck: async () => {},
readyCheck: async () => {},
},
});
try {
// init the app
await App.init({
appName: APP_NAME,
port: undefined,
isFrontendApp: true,
statusOptions: {
liveCheck: async () => {},
readyCheck: async () => {},
},
});
// add default routes
await App.addDefaultRoutes();
} catch (err) {
logger.error('App Init Failed:');
logger.error(err);
throw err;
}
// add default routes
await App.addDefaultRoutes();
} catch (err) {
logger.error("App Init Failed:");
logger.error(err);
throw err;
}
};
init().catch((err: Error) => {
logger.error(err);
logger.error('Exiting node process');
process.exit(1);
logger.error(err);
logger.error("Exiting node process");
process.exit(1);
});
export default app;

View File

@@ -1,4 +1,4 @@
declare module '*.png';
declare module '*.svg';
declare module '*.jpg';
declare module '*.gif';
declare module "*.png";
declare module "*.svg";
declare module "*.jpg";
declare module "*.gif";

View File

@@ -1,112 +1,103 @@
import MasterPage from './Components/MasterPage/MasterPage';
import Init from './Pages/Init/Init';
import Logout from './Pages/Logout/Logout';
import Projects from './Pages/Projects/Index';
import SettingsAPIKey from './Pages/Settings/APIKey/Index';
import SettingsAuthentication from './Pages/Settings/Authentication/Index';
import SettingsCallSMS from './Pages/Settings/CallSMS/Index';
import MasterPage from "./Components/MasterPage/MasterPage";
import Init from "./Pages/Init/Init";
import Logout from "./Pages/Logout/Logout";
import Projects from "./Pages/Projects/Index";
import SettingsAPIKey from "./Pages/Settings/APIKey/Index";
import SettingsAuthentication from "./Pages/Settings/Authentication/Index";
import SettingsCallSMS from "./Pages/Settings/CallSMS/Index";
// Settings Pages.
import SettingsEmail from './Pages/Settings/Email/Index';
import SettingsProbes from './Pages/Settings/Probes/Index';
import Users from './Pages/Users/Index';
import PageMap from './Utils/PageMap';
import RouteMap from './Utils/RouteMap';
import URL from 'Common/Types/API/URL';
import { ACCOUNTS_URL, DASHBOARD_URL } from 'CommonUI/src/Config';
import Navigation from 'CommonUI/src/Utils/Navigation';
import User from 'CommonUI/src/Utils/User';
import React from 'react';
import SettingsEmail from "./Pages/Settings/Email/Index";
import SettingsProbes from "./Pages/Settings/Probes/Index";
import Users from "./Pages/Users/Index";
import PageMap from "./Utils/PageMap";
import RouteMap from "./Utils/RouteMap";
import URL from "Common/Types/API/URL";
import { ACCOUNTS_URL, DASHBOARD_URL } from "CommonUI/src/Config";
import Navigation from "CommonUI/src/Utils/Navigation";
import User from "CommonUI/src/Utils/User";
import React from "react";
import {
Route as PageRoute,
Routes,
useLocation,
useNavigate,
useParams,
} from 'react-router-dom';
Route as PageRoute,
Routes,
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
const App: () => JSX.Element = () => {
Navigation.setNavigateHook(useNavigate());
Navigation.setLocation(useLocation());
Navigation.setParams(useParams());
Navigation.setNavigateHook(useNavigate());
Navigation.setLocation(useLocation());
Navigation.setParams(useParams());
if (!User.isLoggedIn()) {
if (Navigation.getQueryStringByName('sso_token')) {
Navigation.navigate(
URL.fromString(ACCOUNTS_URL.toString()).addQueryParam(
'sso',
'true'
)
);
} else {
Navigation.navigate(URL.fromString(ACCOUNTS_URL.toString()));
}
if (!User.isLoggedIn()) {
if (Navigation.getQueryStringByName("sso_token")) {
Navigation.navigate(
URL.fromString(ACCOUNTS_URL.toString()).addQueryParam("sso", "true"),
);
} else {
Navigation.navigate(URL.fromString(ACCOUNTS_URL.toString()));
}
}
if (!User.isMasterAdmin()) {
Navigation.navigate(URL.fromString(DASHBOARD_URL.toString()));
}
if (!User.isMasterAdmin()) {
Navigation.navigate(URL.fromString(DASHBOARD_URL.toString()));
}
return (
<MasterPage>
<Routes>
<PageRoute
path={RouteMap[PageMap.INIT]?.toString() || ''}
element={<Init />}
/>
return (
<MasterPage>
<Routes>
<PageRoute
path={RouteMap[PageMap.INIT]?.toString() || ""}
element={<Init />}
/>
<PageRoute
path={RouteMap[PageMap.PROJECTS]?.toString() || ''}
element={<Projects />}
/>
<PageRoute
path={RouteMap[PageMap.PROJECTS]?.toString() || ""}
element={<Projects />}
/>
<PageRoute
path={RouteMap[PageMap.USERS]?.toString() || ''}
element={<Users />}
/>
<PageRoute
path={RouteMap[PageMap.USERS]?.toString() || ""}
element={<Users />}
/>
<PageRoute
path={RouteMap[PageMap.LOGOUT]?.toString() || ''}
element={<Logout />}
/>
<PageRoute
path={RouteMap[PageMap.LOGOUT]?.toString() || ""}
element={<Logout />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS]?.toString() || ''}
element={<SettingsAuthentication />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS]?.toString() || ""}
element={<SettingsAuthentication />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_SMTP]?.toString() || ''}
element={<SettingsEmail />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_SMTP]?.toString() || ""}
element={<SettingsEmail />}
/>
<PageRoute
path={
RouteMap[PageMap.SETTINGS_CALL_AND_SMS]?.toString() ||
''
}
element={<SettingsCallSMS />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_CALL_AND_SMS]?.toString() || ""}
element={<SettingsCallSMS />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_PROBES]?.toString() || ''}
element={<SettingsProbes />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_PROBES]?.toString() || ""}
element={<SettingsProbes />}
/>
<PageRoute
path={
RouteMap[PageMap.SETTINGS_AUTHENTICATION]?.toString() ||
''
}
element={<SettingsAuthentication />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_AUTHENTICATION]?.toString() || ""}
element={<SettingsAuthentication />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_API_KEY]?.toString() || ''}
element={<SettingsAPIKey />}
/>
</Routes>
</MasterPage>
);
<PageRoute
path={RouteMap[PageMap.SETTINGS_API_KEY]?.toString() || ""}
element={<SettingsAPIKey />}
/>
</Routes>
</MasterPage>
);
};
export default App;

View File

@@ -1,122 +1,114 @@
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import URL from 'Common/Types/API/URL';
import Dictionary from 'Common/Types/Dictionary';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
import { JSONObject } from 'Common/Types/JSON';
import API from 'Common/Utils/API';
import Footer from 'CommonUI/src/Components/Footer/Footer';
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
import { HOST, HTTP_PROTOCOL } from 'CommonUI/src/Config';
import React from 'react';
import HTTPResponse from "Common/Types/API/HTTPResponse";
import URL from "Common/Types/API/URL";
import Dictionary from "Common/Types/Dictionary";
import BadDataException from "Common/Types/Exception/BadDataException";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import { JSONObject } from "Common/Types/JSON";
import API from "Common/Utils/API";
import Footer from "CommonUI/src/Components/Footer/Footer";
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
import { HOST, HTTP_PROTOCOL } from "CommonUI/src/Config";
import React from "react";
const DashboardFooter: () => JSX.Element = () => {
const [showAboutModal, setShowAboutModal] = React.useState<boolean>(false);
const [isAboutModalLoading, setIsAboutModalLoading] =
React.useState<boolean>(false);
const [versionText, setVersionText] = React.useState<Dictionary<string>>(
{}
const [showAboutModal, setShowAboutModal] = React.useState<boolean>(false);
const [isAboutModalLoading, setIsAboutModalLoading] =
React.useState<boolean>(false);
const [versionText, setVersionText] = React.useState<Dictionary<string>>({});
const fetchVersions: PromiseVoidFunction = async (): Promise<void> => {
setIsAboutModalLoading(true);
try {
const verText: Dictionary<string> = {};
const apps: Array<{
name: string;
path: string;
}> = [
{
name: "API",
path: "/api",
},
{
name: "Dashboard",
path: "/dashboard",
},
];
for (const app of apps) {
const version: JSONObject = await fetchAppVersion(app.path);
verText[app.name] =
`${app.name}: ${version["version"]} (${version["commit"]})`;
}
setVersionText(verText);
} catch (err) {
setVersionText({
error: "Version data is not available: " + (err as Error).message,
});
}
setIsAboutModalLoading(false);
};
const fetchAppVersion: (appName: string) => Promise<JSONObject> = async (
appName: string,
): Promise<JSONObject> => {
const response: HTTPResponse<JSONObject> = await API.get<JSONObject>(
URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`),
);
const fetchVersions: PromiseVoidFunction = async (): Promise<void> => {
setIsAboutModalLoading(true);
if (response.data) {
return response.data as JSONObject;
}
throw new BadDataException("Version data is not available");
};
try {
const verText: Dictionary<string> = {};
const apps: Array<{
name: string;
path: string;
}> = [
{
name: 'API',
path: '/api',
},
{
name: 'Dashboard',
path: '/dashboard',
},
];
return (
<>
<Footer
className="bg-white h-16 inset-x-0 bottom-0 px-8"
copyright="HackerBay, Inc."
links={[
{
title: "Help and Support",
to: URL.fromString("https://oneuptime.com/support"),
},
{
title: "Legal",
to: URL.fromString("https://oneuptime.com/legal"),
},
{
title: "Version",
onClick: async () => {
setShowAboutModal(true);
await fetchVersions();
},
},
]}
/>
for (const app of apps) {
const version: JSONObject = await fetchAppVersion(app.path);
verText[
app.name
] = `${app.name}: ${version['version']} (${version['commit']})`;
}
setVersionText(verText);
} catch (err) {
setVersionText({
error:
'Version data is not available: ' + (err as Error).message,
});
}
setIsAboutModalLoading(false);
};
const fetchAppVersion: (appName: string) => Promise<JSONObject> = async (
appName: string
): Promise<JSONObject> => {
const response: HTTPResponse<JSONObject> = await API.get<JSONObject>(
URL.fromString(`${HTTP_PROTOCOL}/${HOST}${appName}/version`)
);
if (response.data) {
return response.data as JSONObject;
}
throw new BadDataException('Version data is not available');
};
return (
<>
<Footer
className="bg-white h-16 inset-x-0 bottom-0 px-8"
copyright="HackerBay, Inc."
links={[
{
title: 'Help and Support',
to: URL.fromString('https://oneuptime.com/support'),
},
{
title: 'Legal',
to: URL.fromString('https://oneuptime.com/legal'),
},
{
title: 'Version',
onClick: async () => {
setShowAboutModal(true);
await fetchVersions();
},
},
]}
/>
{showAboutModal ? (
<ConfirmModal
title={`OneUptime Version`}
description={
<div>
{Object.keys(versionText).map(
(key: string, i: number) => {
return (
<div key={i}>{versionText[key]}</div>
);
}
)}
</div>
}
isLoading={isAboutModalLoading}
submitButtonText={'Close'}
onSubmit={() => {
return setShowAboutModal(false);
}}
/>
) : (
<></>
)}
</>
);
{showAboutModal ? (
<ConfirmModal
title={`OneUptime Version`}
description={
<div>
{Object.keys(versionText).map((key: string, i: number) => {
return <div key={i}>{versionText[key]}</div>;
})}
</div>
}
isLoading={isAboutModalLoading}
submitButtonText={"Close"}
onSubmit={() => {
return setShowAboutModal(false);
}}
/>
) : (
<></>
)}
</>
);
};
export default DashboardFooter;

View File

@@ -1,46 +1,46 @@
import Help from './Help';
import Logo from './Logo';
import UserProfile from './UserProfile';
import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
import Header from 'CommonUI/src/Components/Header/Header';
import { DASHBOARD_URL } from 'CommonUI/src/Config';
import Navigation from 'CommonUI/src/Utils/Navigation';
import React, { FunctionComponent, ReactElement } from 'react';
import Help from "./Help";
import Logo from "./Logo";
import UserProfile from "./UserProfile";
import Button, { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
import Header from "CommonUI/src/Components/Header/Header";
import { DASHBOARD_URL } from "CommonUI/src/Config";
import Navigation from "CommonUI/src/Utils/Navigation";
import React, { FunctionComponent, ReactElement } from "react";
const DashboardHeader: FunctionComponent = (): ReactElement => {
return (
<>
<Header
leftComponents={
<>
<Logo onClick={() => {}} />
</>
}
centerComponents={
<>
{/* <SearchBox
return (
<>
<Header
leftComponents={
<>
<Logo onClick={() => {}} />
</>
}
centerComponents={
<>
{/* <SearchBox
key={2}
selectedProject={props.selectedProject}
onChange={(_value: string) => { }}
/>{' '} */}
</>
}
rightComponents={
<>
<Button
title="Exit Admin"
buttonStyle={ButtonStyleType.NORMAL}
onClick={() => {
Navigation.navigate(DASHBOARD_URL);
}}
/>
<Help />
<UserProfile />
</>
}
</>
}
rightComponents={
<>
<Button
title="Exit Admin"
buttonStyle={ButtonStyleType.NORMAL}
onClick={() => {
Navigation.navigate(DASHBOARD_URL);
}}
/>
</>
);
<Help />
<UserProfile />
</>
}
/>
</>
);
};
export default DashboardHeader;

View File

@@ -1,60 +1,58 @@
import URL from 'Common/Types/API/URL';
import IconProp from 'Common/Types/Icon/IconProp';
import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton';
import IconDropdownItem from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownItem';
import IconDropdownMenu from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu';
import IconDropdownRow from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownRow';
import React, { ReactElement, useState } from 'react';
import URL from "Common/Types/API/URL";
import IconProp from "Common/Types/Icon/IconProp";
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem";
import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu";
import IconDropdownRow from "CommonUI/src/Components/Header/IconDropdown/IconDropdownRow";
import React, { ReactElement, useState } from "react";
const Help: () => JSX.Element = (): ReactElement => {
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
return (
<HeaderIconDropdownButton
icon={IconProp.Help}
name="Help"
showDropdown={isDropdownVisible}
return (
<HeaderIconDropdownButton
icon={IconProp.Help}
name="Help"
showDropdown={isDropdownVisible}
onClick={() => {
setIsDropdownVisible(true);
}}
>
<IconDropdownMenu>
<IconDropdownRow>
<IconDropdownItem
title="Support Email"
icon={IconProp.Email}
openInNewTab={true}
url={URL.fromString("mailto:support@oneuptime.com")}
onClick={() => {
setIsDropdownVisible(true);
setIsDropdownVisible(false);
}}
>
<IconDropdownMenu>
<IconDropdownRow>
<IconDropdownItem
title="Support Email"
icon={IconProp.Email}
openInNewTab={true}
url={URL.fromString('mailto:support@oneuptime.com')}
onClick={() => {
setIsDropdownVisible(false);
}}
/>
<IconDropdownItem
title="Chat on Slack"
icon={IconProp.Slack}
openInNewTab={true}
onClick={() => {
setIsDropdownVisible(false);
}}
url={URL.fromString(
'https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ'
)}
/>
<IconDropdownItem
title="Request Demo"
icon={IconProp.Window}
onClick={() => {
setIsDropdownVisible(false);
}}
openInNewTab={true}
url={URL.fromString(
'https://oneuptime.com/enterprise/demo'
)}
/>
</IconDropdownRow>
</IconDropdownMenu>
</HeaderIconDropdownButton>
);
/>
<IconDropdownItem
title="Chat on Slack"
icon={IconProp.Slack}
openInNewTab={true}
onClick={() => {
setIsDropdownVisible(false);
}}
url={URL.fromString(
"https://join.slack.com/t/oneuptimesupport/shared_invite/zt-1kavkds2f-gegm_wePorvwvM3M_SaoCQ",
)}
/>
<IconDropdownItem
title="Request Demo"
icon={IconProp.Window}
onClick={() => {
setIsDropdownVisible(false);
}}
openInNewTab={true}
url={URL.fromString("https://oneuptime.com/enterprise/demo")}
/>
</IconDropdownRow>
</IconDropdownMenu>
</HeaderIconDropdownButton>
);
};
export default Help;

View File

@@ -1,30 +1,30 @@
// Tailwind
import Route from 'Common/Types/API/Route';
import Image from 'CommonUI/src/Components/Image/Image';
import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg';
import React, { FunctionComponent, ReactElement } from 'react';
import Route from "Common/Types/API/Route";
import Image from "CommonUI/src/Components/Image/Image";
import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.svg";
import React, { FunctionComponent, ReactElement } from "react";
export interface ComponentProps {
onClick: () => void;
onClick: () => void;
}
const Logo: FunctionComponent<ComponentProps> = (
props: ComponentProps
props: ComponentProps,
): ReactElement => {
return (
<div className="relative z-10 flex px-2 lg:px-0">
<div className="flex flex-shrink-0 items-center">
<Image
className="block h-8 w-auto"
onClick={() => {
props.onClick && props.onClick();
}}
imageUrl={Route.fromString(`${OneUptimeLogo}`)}
alt={'OneUptime'}
/>
</div>
</div>
);
return (
<div className="relative z-10 flex px-2 lg:px-0">
<div className="flex flex-shrink-0 items-center">
<Image
className="block h-8 w-auto"
onClick={() => {
props.onClick && props.onClick();
}}
imageUrl={Route.fromString(`${OneUptimeLogo}`)}
alt={"OneUptime"}
/>
</div>
</div>
);
};
export default Logo;

View File

@@ -1,35 +1,35 @@
import IconProp from 'Common/Types/Icon/IconProp';
import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton';
import NotificationItem from 'CommonUI/src/Components/Header/Notifications/NotificationItem';
import Notifications from 'CommonUI/src/Components/Header/Notifications/Notifications';
import React, { ReactElement, useState } from 'react';
import IconProp from "Common/Types/Icon/IconProp";
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
import NotificationItem from "CommonUI/src/Components/Header/Notifications/NotificationItem";
import Notifications from "CommonUI/src/Components/Header/Notifications/Notifications";
import React, { ReactElement, useState } from "react";
const DashboardHeader: () => JSX.Element = (): ReactElement => {
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
return (
<HeaderIconDropdownButton
name="Notifications"
onClick={() => {
setIsDropdownVisible(true);
}}
showDropdown={isDropdownVisible}
icon={IconProp.Notification}
badge={4}
>
<Notifications>
<NotificationItem
title="Sample Title"
description="Sample Description"
createdAt={new Date()}
icon={IconProp.Home}
onClick={() => {
setIsDropdownVisible(false);
}}
/>
</Notifications>
</HeaderIconDropdownButton>
);
return (
<HeaderIconDropdownButton
name="Notifications"
onClick={() => {
setIsDropdownVisible(true);
}}
showDropdown={isDropdownVisible}
icon={IconProp.Notification}
badge={4}
>
<Notifications>
<NotificationItem
title="Sample Title"
description="Sample Description"
createdAt={new Date()}
icon={IconProp.Home}
onClick={() => {
setIsDropdownVisible(false);
}}
/>
</Notifications>
</HeaderIconDropdownButton>
);
};
export default DashboardHeader;

View File

@@ -1,286 +1,273 @@
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
import IconProp from 'Common/Types/Icon/IconProp';
import { FormType } from 'CommonUI/src/Components/Forms/ModelForm';
import Field from 'CommonUI/src/Components/Forms/Types/Field';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import ProjectPicker from 'CommonUI/src/Components/Header/ProjectPicker/ProjectPicker';
import ModelFormModal from 'CommonUI/src/Components/ModelFormModal/ModelFormModal';
import { RadioButton } from 'CommonUI/src/Components/RadioButtons/GroupRadioButtons';
import Toggle from 'CommonUI/src/Components/Toggle/Toggle';
import { BILLING_ENABLED, getAllEnvVars } from 'CommonUI/src/Config';
import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes';
import ProjectUtil from 'CommonUI/src/Utils/Project';
import Project from 'Model/Models/Project';
import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan";
import IconProp from "Common/Types/Icon/IconProp";
import { FormType } from "CommonUI/src/Components/Forms/ModelForm";
import Field from "CommonUI/src/Components/Forms/Types/Field";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import ProjectPicker from "CommonUI/src/Components/Header/ProjectPicker/ProjectPicker";
import ModelFormModal from "CommonUI/src/Components/ModelFormModal/ModelFormModal";
import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons";
import Toggle from "CommonUI/src/Components/Toggle/Toggle";
import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config";
import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes";
import ProjectUtil from "CommonUI/src/Utils/Project";
import Project from "Model/Models/Project";
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from 'react';
FunctionComponent,
ReactElement,
useEffect,
useState,
} from "react";
export interface ComponentProps {
projects: Array<Project>;
onProjectSelected: (project: Project) => void;
showProjectModal: boolean;
onProjectModalClose: () => void;
projects: Array<Project>;
onProjectSelected: (project: Project) => void;
showProjectModal: boolean;
onProjectModalClose: () => void;
}
const DashboardProjectPicker: FunctionComponent<ComponentProps> = (
props: ComponentProps
props: ComponentProps,
): ReactElement => {
const [showModal, setShowModal] = useState<boolean>(false);
const [selectedProject, setSelectedProject] = useState<Project | null>(
null
);
const [showModal, setShowModal] = useState<boolean>(false);
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const getFooter: GetReactElementFunction = (): ReactElement => {
if (!BILLING_ENABLED) {
return <></>;
}
return (
<Toggle
title="Yearly Plan"
value={isSubscriptionPlanYearly}
description="(Save 20%)"
onChange={(value: boolean) => {
setIsSubscriptionPlanYearly(value);
}}
/>
);
};
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
useState<boolean>(true);
const [fields, setFields] = useState<Array<Field<Project>>>([]);
useEffect(() => {
if (props.showProjectModal) {
setShowModal(true);
}
}, [props.showProjectModal]);
useEffect(() => {
const currentProject: Project | null = ProjectUtil.getCurrentProject();
setSelectedProject(currentProject);
if (currentProject && props.onProjectSelected) {
props.onProjectSelected(currentProject);
}
}, []);
useEffect(() => {
if (selectedProject) {
ProjectUtil.setCurrentProject(selectedProject);
if (props.onProjectSelected) {
props.onProjectSelected(selectedProject);
}
}
}, [selectedProject]);
useEffect(() => {
if (
props.projects &&
props.projects.length > 0 &&
!selectedProject &&
props.projects[0]
) {
const currentProject: Project | null =
ProjectUtil.getCurrentProject();
if (!currentProject) {
setSelectedProject(props.projects[0]);
} else if (
props.projects.filter((project: Project) => {
return project._id === currentProject._id;
}).length > 0
) {
setSelectedProject(
props.projects.filter((project: Project) => {
return project._id === currentProject._id;
})[0] as Project
);
} else {
setSelectedProject(props.projects[0]);
}
}
}, [props.projects]);
useEffect(() => {
refreshFields();
}, [isSubscriptionPlanYearly]);
const refreshFields: VoidFunction = (): void => {
let formFields: Array<Field<Project>> = [
{
field: {
name: true,
},
validation: {
minLength: 4,
},
fieldType: FormFieldSchemaType.Text,
placeholder: 'My Project',
description: 'Pick a friendly name.',
title: 'Project Name',
required: true,
stepId: BILLING_ENABLED ? 'basic' : undefined,
},
];
if (BILLING_ENABLED) {
formFields = [
...formFields,
{
field: {
paymentProviderPlanId: true,
},
stepId: 'plan',
validation: {
minLength: 6,
},
footerElement: getFooter(),
fieldType: FormFieldSchemaType.RadioButton,
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
getAllEnvVars()
).map((plan: SubscriptionPlan): RadioButton => {
let description: string = plan.isCustomPricing()
? `Our sales team will contact you soon.`
: `Billed ${
isSubscriptionPlanYearly
? 'yearly'
: 'monthly'
}. ${
plan.getTrialPeriod() > 0
? `Free ${plan.getTrialPeriod()} days trial.`
: ''
}`;
if (
isSubscriptionPlanYearly &&
plan.getYearlySubscriptionAmountInUSD() === 0
) {
description = 'This plan is free, forever. ';
}
if (
!isSubscriptionPlanYearly &&
plan.getMonthlySubscriptionAmountInUSD() === 0
) {
description = 'This plan is free, forever. ';
}
return {
value: isSubscriptionPlanYearly
? plan.getYearlyPlanId()
: plan.getMonthlyPlanId(),
title: plan.getName(),
description: description,
sideTitle: plan.isCustomPricing()
? 'Custom Price'
: isSubscriptionPlanYearly
? '$' +
plan
.getYearlySubscriptionAmountInUSD()
.toString() +
'/mo billed yearly'
: '$' +
plan
.getMonthlySubscriptionAmountInUSD()
.toString(),
sideDescription: plan.isCustomPricing()
? ''
: isSubscriptionPlanYearly
? `~ $${
plan.getYearlySubscriptionAmountInUSD() *
12
} per user / year`
: `/month per user`,
};
}),
title: 'Please select a plan.',
required: true,
},
{
field: {
paymentProviderPromoCode: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: 'Promo Code (Optional)',
description: 'If you have a coupon code, enter it here.',
title: 'Promo Code',
required: false,
stepId: 'plan',
},
];
}
setFields(formFields);
};
const getFooter: GetReactElementFunction = (): ReactElement => {
if (!BILLING_ENABLED) {
return <></>;
}
return (
<>
{props.projects.length !== 0 && (
<ProjectPicker
selectedProjectName={selectedProject?.name || ''}
selectedProjectIcon={IconProp.Folder}
projects={props.projects}
onCreateProjectButtonClicked={() => {
setShowModal(true);
props.onProjectModalClose();
}}
onProjectSelected={(project: Project) => {
setSelectedProject(project);
}}
/>
)}
{showModal ? (
<ModelFormModal<Project>
modelType={Project}
name="Create New Project"
title="Create New Project"
description="Please create a new OneUptime project to get started."
onClose={() => {
setShowModal(false);
props.onProjectModalClose();
}}
submitButtonText="Create Project"
onSuccess={(project: Project | null) => {
setSelectedProject(project);
if (project && props.onProjectSelected) {
props.onProjectSelected(project);
}
setShowModal(false);
props.onProjectModalClose();
}}
formProps={{
name: 'Create New Project',
steps: BILLING_ENABLED
? [
{
title: 'Basic',
id: 'basic',
},
{
title: 'Select Plan',
id: 'plan',
},
]
: undefined,
saveRequestOptions: {
isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request
},
modelType: Project,
id: 'create-project-from',
fields: [...fields],
formType: FormType.Create,
}}
/>
) : (
<></>
)}
</>
<Toggle
title="Yearly Plan"
value={isSubscriptionPlanYearly}
description="(Save 20%)"
onChange={(value: boolean) => {
setIsSubscriptionPlanYearly(value);
}}
/>
);
};
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
useState<boolean>(true);
const [fields, setFields] = useState<Array<Field<Project>>>([]);
useEffect(() => {
if (props.showProjectModal) {
setShowModal(true);
}
}, [props.showProjectModal]);
useEffect(() => {
const currentProject: Project | null = ProjectUtil.getCurrentProject();
setSelectedProject(currentProject);
if (currentProject && props.onProjectSelected) {
props.onProjectSelected(currentProject);
}
}, []);
useEffect(() => {
if (selectedProject) {
ProjectUtil.setCurrentProject(selectedProject);
if (props.onProjectSelected) {
props.onProjectSelected(selectedProject);
}
}
}, [selectedProject]);
useEffect(() => {
if (
props.projects &&
props.projects.length > 0 &&
!selectedProject &&
props.projects[0]
) {
const currentProject: Project | null = ProjectUtil.getCurrentProject();
if (!currentProject) {
setSelectedProject(props.projects[0]);
} else if (
props.projects.filter((project: Project) => {
return project._id === currentProject._id;
}).length > 0
) {
setSelectedProject(
props.projects.filter((project: Project) => {
return project._id === currentProject._id;
})[0] as Project,
);
} else {
setSelectedProject(props.projects[0]);
}
}
}, [props.projects]);
useEffect(() => {
refreshFields();
}, [isSubscriptionPlanYearly]);
const refreshFields: VoidFunction = (): void => {
let formFields: Array<Field<Project>> = [
{
field: {
name: true,
},
validation: {
minLength: 4,
},
fieldType: FormFieldSchemaType.Text,
placeholder: "My Project",
description: "Pick a friendly name.",
title: "Project Name",
required: true,
stepId: BILLING_ENABLED ? "basic" : undefined,
},
];
if (BILLING_ENABLED) {
formFields = [
...formFields,
{
field: {
paymentProviderPlanId: true,
},
stepId: "plan",
validation: {
minLength: 6,
},
footerElement: getFooter(),
fieldType: FormFieldSchemaType.RadioButton,
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
getAllEnvVars(),
).map((plan: SubscriptionPlan): RadioButton => {
let description: string = plan.isCustomPricing()
? `Our sales team will contact you soon.`
: `Billed ${isSubscriptionPlanYearly ? "yearly" : "monthly"}. ${
plan.getTrialPeriod() > 0
? `Free ${plan.getTrialPeriod()} days trial.`
: ""
}`;
if (
isSubscriptionPlanYearly &&
plan.getYearlySubscriptionAmountInUSD() === 0
) {
description = "This plan is free, forever. ";
}
if (
!isSubscriptionPlanYearly &&
plan.getMonthlySubscriptionAmountInUSD() === 0
) {
description = "This plan is free, forever. ";
}
return {
value: isSubscriptionPlanYearly
? plan.getYearlyPlanId()
: plan.getMonthlyPlanId(),
title: plan.getName(),
description: description,
sideTitle: plan.isCustomPricing()
? "Custom Price"
: isSubscriptionPlanYearly
? "$" +
plan.getYearlySubscriptionAmountInUSD().toString() +
"/mo billed yearly"
: "$" + plan.getMonthlySubscriptionAmountInUSD().toString(),
sideDescription: plan.isCustomPricing()
? ""
: isSubscriptionPlanYearly
? `~ $${
plan.getYearlySubscriptionAmountInUSD() * 12
} per user / year`
: `/month per user`,
};
}),
title: "Please select a plan.",
required: true,
},
{
field: {
paymentProviderPromoCode: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: "Promo Code (Optional)",
description: "If you have a coupon code, enter it here.",
title: "Promo Code",
required: false,
stepId: "plan",
},
];
}
setFields(formFields);
};
return (
<>
{props.projects.length !== 0 && (
<ProjectPicker
selectedProjectName={selectedProject?.name || ""}
selectedProjectIcon={IconProp.Folder}
projects={props.projects}
onCreateProjectButtonClicked={() => {
setShowModal(true);
props.onProjectModalClose();
}}
onProjectSelected={(project: Project) => {
setSelectedProject(project);
}}
/>
)}
{showModal ? (
<ModelFormModal<Project>
modelType={Project}
name="Create New Project"
title="Create New Project"
description="Please create a new OneUptime project to get started."
onClose={() => {
setShowModal(false);
props.onProjectModalClose();
}}
submitButtonText="Create Project"
onSuccess={(project: Project | null) => {
setSelectedProject(project);
if (project && props.onProjectSelected) {
props.onProjectSelected(project);
}
setShowModal(false);
props.onProjectModalClose();
}}
formProps={{
name: "Create New Project",
steps: BILLING_ENABLED
? [
{
title: "Basic",
id: "basic",
},
{
title: "Select Plan",
id: "plan",
},
]
: undefined,
saveRequestOptions: {
isMultiTenantRequest: true, // because this is a tenant request, we do not have to include the header in the request
},
modelType: Project,
id: "create-project-from",
fields: [...fields],
formType: FormType.Create,
}}
/>
) : (
<></>
)}
</>
);
};
export default DashboardProjectPicker;

View File

@@ -1,20 +1,20 @@
import SearchBox from 'CommonUI/src/Components/Header/SearchBox';
import Project from 'Model/Models/Project';
import React, { FunctionComponent, ReactElement } from 'react';
import SearchBox from "CommonUI/src/Components/Header/SearchBox";
import Project from "Model/Models/Project";
import React, { FunctionComponent, ReactElement } from "react";
export interface ComponentProps {
onChange: (search: string) => void;
selectedProject: Project | null;
onChange: (search: string) => void;
selectedProject: Project | null;
}
const Search: FunctionComponent<ComponentProps> = (
props: ComponentProps
props: ComponentProps,
): ReactElement => {
if (!props.selectedProject) {
return <></>;
}
if (!props.selectedProject) {
return <></>;
}
return <SearchBox key={2} onChange={props.onChange} />;
return <SearchBox key={2} onChange={props.onChange} />;
};
export default Search;

View File

@@ -1,57 +1,57 @@
import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import Route from 'Common/Types/API/Route';
import IconProp from 'Common/Types/Icon/IconProp';
import HeaderIconDropdownButton from 'CommonUI/src/Components/Header/HeaderIconDropdownButton';
import IconDropdownItem from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownItem';
import IconDropdownMenu from 'CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu';
import { DASHBOARD_URL } from 'CommonUI/src/Config';
import BlankProfilePic from 'CommonUI/src/Images/users/blank-profile.svg';
import Navigation from 'CommonUI/src/Utils/Navigation';
import User from 'CommonUI/src/Utils/User';
import React, { FunctionComponent, ReactElement, useState } from 'react';
import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import IconProp from "Common/Types/Icon/IconProp";
import HeaderIconDropdownButton from "CommonUI/src/Components/Header/HeaderIconDropdownButton";
import IconDropdownItem from "CommonUI/src/Components/Header/IconDropdown/IconDropdownItem";
import IconDropdownMenu from "CommonUI/src/Components/Header/IconDropdown/IconDropdownMenu";
import { DASHBOARD_URL } from "CommonUI/src/Config";
import BlankProfilePic from "CommonUI/src/Images/users/blank-profile.svg";
import Navigation from "CommonUI/src/Utils/Navigation";
import User from "CommonUI/src/Utils/User";
import React, { FunctionComponent, ReactElement, useState } from "react";
const DashboardUserProfile: FunctionComponent = (): ReactElement => {
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
const [isDropdownVisible, setIsDropdownVisible] = useState<boolean>(false);
return (
<>
<HeaderIconDropdownButton
iconImageUrl={BlankProfilePic}
name="User Profile"
showDropdown={isDropdownVisible}
onClick={() => {
setIsDropdownVisible(true);
}}
>
<IconDropdownMenu>
{User.isMasterAdmin() ? (
<IconDropdownItem
title="Exit Admin"
onClick={() => {
setIsDropdownVisible(false);
Navigation.navigate(DASHBOARD_URL);
}}
icon={IconProp.ExternalLink}
/>
) : (
<></>
)}
return (
<>
<HeaderIconDropdownButton
iconImageUrl={BlankProfilePic}
name="User Profile"
showDropdown={isDropdownVisible}
onClick={() => {
setIsDropdownVisible(true);
}}
>
<IconDropdownMenu>
{User.isMasterAdmin() ? (
<IconDropdownItem
title="Exit Admin"
onClick={() => {
setIsDropdownVisible(false);
Navigation.navigate(DASHBOARD_URL);
}}
icon={IconProp.ExternalLink}
/>
) : (
<></>
)}
<IconDropdownItem
title="Log out"
onClick={() => {
setIsDropdownVisible(false);
}}
url={RouteUtil.populateRouteParams(
RouteMap[PageMap.LOGOUT] as Route
)}
icon={IconProp.Logout}
/>
</IconDropdownMenu>
</HeaderIconDropdownButton>
</>
);
<IconDropdownItem
title="Log out"
onClick={() => {
setIsDropdownVisible(false);
}}
url={RouteUtil.populateRouteParams(
RouteMap[PageMap.LOGOUT] as Route,
)}
icon={IconProp.Logout}
/>
</IconDropdownMenu>
</HeaderIconDropdownButton>
</>
);
};
export default DashboardUserProfile;

View File

@@ -1,35 +1,35 @@
import Footer from '../Footer/Footer';
import Header from '../Header/Header';
import NavBar from '../NavBar/NavBar';
import MasterPage from 'CommonUI/src/Components/MasterPage/MasterPage';
import TopAlert from 'CommonUI/src/Components/TopAlert/TopAlert';
import React, { FunctionComponent, ReactElement } from 'react';
import Footer from "../Footer/Footer";
import Header from "../Header/Header";
import NavBar from "../NavBar/NavBar";
import MasterPage from "CommonUI/src/Components/MasterPage/MasterPage";
import TopAlert from "CommonUI/src/Components/TopAlert/TopAlert";
import React, { FunctionComponent, ReactElement } from "react";
export interface ComponentProps {
children: ReactElement | Array<ReactElement>;
children: ReactElement | Array<ReactElement>;
}
const DashboardMasterPage: FunctionComponent<ComponentProps> = (
props: ComponentProps
props: ComponentProps,
): ReactElement => {
return (
<div>
<TopAlert
title="OneUptime Admin Dashboard"
description="You can perform your OneUptime server related tasks on this dashboard."
/>
<MasterPage
footer={<Footer />}
header={<Header />}
navBar={<NavBar />}
isLoading={false}
error={''}
className="flex flex-col h-screen justify-between"
>
{props.children}
</MasterPage>
</div>
);
return (
<div>
<TopAlert
title="OneUptime Admin Dashboard"
description="You can perform your OneUptime server related tasks on this dashboard."
/>
<MasterPage
footer={<Footer />}
header={<Header />}
navBar={<NavBar />}
isLoading={false}
error={""}
className="flex flex-col h-screen justify-between"
>
{props.children}
</MasterPage>
</div>
);
};
export default DashboardMasterPage;

View File

@@ -1,39 +1,37 @@
import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import Route from 'Common/Types/API/Route';
import IconProp from 'Common/Types/Icon/IconProp';
import NavBar from 'CommonUI/src/Components/Navbar/NavBar';
import NavBarItem from 'CommonUI/src/Components/Navbar/NavBarItem';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import IconProp from "Common/Types/Icon/IconProp";
import NavBar from "CommonUI/src/Components/Navbar/NavBar";
import NavBarItem from "CommonUI/src/Components/Navbar/NavBarItem";
import React, { FunctionComponent, ReactElement } from "react";
const DashboardNavbar: FunctionComponent = (): ReactElement => {
return (
<NavBar>
<NavBarItem
title="Users"
icon={IconProp.User}
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.USERS] as Route
)}
></NavBarItem>
return (
<NavBar>
<NavBarItem
title="Users"
icon={IconProp.User}
route={RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route)}
></NavBarItem>
<NavBarItem
title="Projects"
icon={IconProp.Folder}
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECTS] as Route
)}
></NavBarItem>
<NavBarItem
title="Projects"
icon={IconProp.Folder}
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECTS] as Route,
)}
></NavBarItem>
<NavBarItem
title="Settings"
icon={IconProp.Settings}
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route
)}
></NavBarItem>
</NavBar>
);
<NavBarItem
title="Settings"
icon={IconProp.Settings}
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route,
)}
></NavBarItem>
</NavBar>
);
};
export default DashboardNavbar;

View File

@@ -1,19 +1,19 @@
import App from './App';
import Telemetry from 'CommonUI/src/Utils/Telemetry';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from "./App";
import Telemetry from "CommonUI/src/Utils/Telemetry";
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
Telemetry.init({
serviceName: 'AdminDashboard',
serviceName: "AdminDashboard",
});
const root: any = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
document.getElementById("root") as HTMLElement,
);
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
<BrowserRouter>
<App />
</BrowserRouter>,
);

View File

@@ -1,22 +1,22 @@
import PageMap from '../../Utils/PageMap';
import RouteMap from '../../Utils/RouteMap';
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
import Page from 'CommonUI/src/Components/Page/Page';
import Navigation from 'CommonUI/src/Utils/Navigation';
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
import PageMap from "../../Utils/PageMap";
import RouteMap from "../../Utils/RouteMap";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import Page from "CommonUI/src/Components/Page/Page";
import Navigation from "CommonUI/src/Utils/Navigation";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
const Init: FunctionComponent = (): ReactElement => {
useEffect(() => {
Navigation.navigate(RouteMap[PageMap.USERS]!, {
forceNavigate: true,
});
}, []);
useEffect(() => {
Navigation.navigate(RouteMap[PageMap.USERS]!, {
forceNavigate: true,
});
}, []);
return (
<Page title={''} breadcrumbLinks={[]}>
<PageLoader isVisible={true} />
</Page>
);
return (
<Page title={""} breadcrumbLinks={[]}>
<PageLoader isVisible={true} />
</Page>
);
};
export default Init;

View File

@@ -1,53 +1,49 @@
import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import Route from 'Common/Types/API/Route';
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
import Page from 'CommonUI/src/Components/Page/Page';
import { ACCOUNTS_URL } from 'CommonUI/src/Config';
import UiAnalytics from 'CommonUI/src/Utils/Analytics';
import Navigation from 'CommonUI/src/Utils/Navigation';
import UserUtil from 'CommonUI/src/Utils/User';
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import Page from "CommonUI/src/Components/Page/Page";
import { ACCOUNTS_URL } from "CommonUI/src/Config";
import UiAnalytics from "CommonUI/src/Utils/Analytics";
import Navigation from "CommonUI/src/Utils/Navigation";
import UserUtil from "CommonUI/src/Utils/User";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
const Logout: FunctionComponent = (): ReactElement => {
const [error, setError] = React.useState<string | null>(null);
const [error, setError] = React.useState<string | null>(null);
const logout: PromiseVoidFunction = async (): Promise<void> => {
UiAnalytics.logout();
await UserUtil.logout();
Navigation.navigate(ACCOUNTS_URL);
};
const logout: PromiseVoidFunction = async (): Promise<void> => {
UiAnalytics.logout();
await UserUtil.logout();
Navigation.navigate(ACCOUNTS_URL);
};
useEffect(() => {
logout().catch((error: Error) => {
setError(error.message || error.toString());
});
}, []);
useEffect(() => {
logout().catch((error: Error) => {
setError(error.message || error.toString());
});
}, []);
return (
<Page
title={'Logout'}
breadcrumbLinks={[
{
title: 'Admin Dashboard',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INIT] as Route
),
},
{
title: 'Logout',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.LOGOUT] as Route
),
},
]}
>
{!error ? <PageLoader isVisible={true} /> : <></>}
{error ? <ErrorMessage error={error} /> : <></>}
</Page>
);
return (
<Page
title={"Logout"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.INIT] as Route),
},
{
title: "Logout",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.LOGOUT] as Route),
},
]}
>
{!error ? <PageLoader isVisible={true} /> : <></>}
{error ? <ErrorMessage error={error} /> : <></>}
</Page>
);
};
export default Logout;

View File

@@ -1,263 +1,251 @@
import AdminModelAPI from '../../Utils/ModelAPI';
import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import Route from 'Common/Types/API/Route';
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
import Field from 'CommonUI/src/Components/Forms/Types/Field';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import Page from 'CommonUI/src/Components/Page/Page';
import { RadioButton } from 'CommonUI/src/Components/RadioButtons/GroupRadioButtons';
import Toggle from 'CommonUI/src/Components/Toggle/Toggle';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { BILLING_ENABLED, getAllEnvVars } from 'CommonUI/src/Config';
import { GetReactElementFunction } from 'CommonUI/src/Types/FunctionTypes';
import Navigation from 'CommonUI/src/Utils/Navigation';
import Project from 'Model/Models/Project';
import User from 'Model/Models/User';
import AdminModelAPI from "../../Utils/ModelAPI";
import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import SubscriptionPlan from "Common/Types/Billing/SubscriptionPlan";
import Field from "CommonUI/src/Components/Forms/Types/Field";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
import Page from "CommonUI/src/Components/Page/Page";
import { RadioButton } from "CommonUI/src/Components/RadioButtons/GroupRadioButtons";
import Toggle from "CommonUI/src/Components/Toggle/Toggle";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import { BILLING_ENABLED, getAllEnvVars } from "CommonUI/src/Config";
import { GetReactElementFunction } from "CommonUI/src/Types/FunctionTypes";
import Navigation from "CommonUI/src/Utils/Navigation";
import Project from "Model/Models/Project";
import User from "Model/Models/User";
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from 'react';
FunctionComponent,
ReactElement,
useEffect,
useState,
} from "react";
const Projects: FunctionComponent = (): ReactElement => {
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
useState<boolean>(true);
const [isSubscriptionPlanYearly, setIsSubscriptionPlanYearly] =
useState<boolean>(true);
useEffect(() => {
refreshFields();
}, [isSubscriptionPlanYearly]);
useEffect(() => {
refreshFields();
}, [isSubscriptionPlanYearly]);
const refreshFields: VoidFunction = (): void => {
let formFields: Array<Field<Project>> = [
{
field: {
name: true,
},
validation: {
minLength: 4,
},
fieldType: FormFieldSchemaType.Text,
placeholder: 'My Project',
description: 'Pick a friendly name.',
title: 'Project Name',
required: true,
stepId: BILLING_ENABLED ? 'basic' : undefined,
},
{
field: {
createdByUser: true,
},
title: 'Owner',
description:
'Who would you like the owner of this project to be? If you leave this blank - you will be the owner of the project',
fieldType: FormFieldSchemaType.Dropdown,
stepId: BILLING_ENABLED ? 'basic' : undefined,
dropdownModal: {
type: User,
labelField: 'email',
valueField: '_id',
},
},
];
const refreshFields: VoidFunction = (): void => {
let formFields: Array<Field<Project>> = [
{
field: {
name: true,
},
validation: {
minLength: 4,
},
fieldType: FormFieldSchemaType.Text,
placeholder: "My Project",
description: "Pick a friendly name.",
title: "Project Name",
required: true,
stepId: BILLING_ENABLED ? "basic" : undefined,
},
{
field: {
createdByUser: true,
},
title: "Owner",
description:
"Who would you like the owner of this project to be? If you leave this blank - you will be the owner of the project",
fieldType: FormFieldSchemaType.Dropdown,
stepId: BILLING_ENABLED ? "basic" : undefined,
dropdownModal: {
type: User,
labelField: "email",
valueField: "_id",
},
},
];
if (BILLING_ENABLED) {
formFields = [
...formFields,
{
field: {
paymentProviderPlanId: true,
},
stepId: 'plan',
validation: {
minLength: 6,
},
footerElement: getFooter(),
fieldType: FormFieldSchemaType.RadioButton,
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
getAllEnvVars()
).map((plan: SubscriptionPlan): RadioButton => {
let description: string = plan.isCustomPricing()
? `Our sales team will contact you soon.`
: `Billed ${
isSubscriptionPlanYearly
? 'yearly'
: 'monthly'
}. ${
plan.getTrialPeriod() > 0
? `Free ${plan.getTrialPeriod()} days trial.`
: ''
}`;
if (BILLING_ENABLED) {
formFields = [
...formFields,
{
field: {
paymentProviderPlanId: true,
},
stepId: "plan",
validation: {
minLength: 6,
},
footerElement: getFooter(),
fieldType: FormFieldSchemaType.RadioButton,
radioButtonOptions: SubscriptionPlan.getSubscriptionPlans(
getAllEnvVars(),
).map((plan: SubscriptionPlan): RadioButton => {
let description: string = plan.isCustomPricing()
? `Our sales team will contact you soon.`
: `Billed ${isSubscriptionPlanYearly ? "yearly" : "monthly"}. ${
plan.getTrialPeriod() > 0
? `Free ${plan.getTrialPeriod()} days trial.`
: ""
}`;
if (
isSubscriptionPlanYearly &&
plan.getYearlySubscriptionAmountInUSD() === 0
) {
description = 'This plan is free, forever. ';
}
if (
isSubscriptionPlanYearly &&
plan.getYearlySubscriptionAmountInUSD() === 0
) {
description = "This plan is free, forever. ";
}
if (
!isSubscriptionPlanYearly &&
plan.getMonthlySubscriptionAmountInUSD() === 0
) {
description = 'This plan is free, forever. ';
}
if (
!isSubscriptionPlanYearly &&
plan.getMonthlySubscriptionAmountInUSD() === 0
) {
description = "This plan is free, forever. ";
}
return {
value: isSubscriptionPlanYearly
? plan.getYearlyPlanId()
: plan.getMonthlyPlanId(),
title: plan.getName(),
description: description,
sideTitle: plan.isCustomPricing()
? 'Custom Price'
: isSubscriptionPlanYearly
? '$' +
plan
.getYearlySubscriptionAmountInUSD()
.toString() +
'/mo billed yearly'
: '$' +
plan
.getMonthlySubscriptionAmountInUSD()
.toString(),
sideDescription: plan.isCustomPricing()
? ''
: isSubscriptionPlanYearly
? `~ $${
plan.getYearlySubscriptionAmountInUSD() *
12
} per user / year`
: `/month per user`,
};
}),
title: 'Please select a plan.',
required: true,
},
{
field: {
paymentProviderPromoCode: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: 'Promo Code (Optional)',
description: 'If you have a coupon code, enter it here.',
title: 'Promo Code',
required: false,
stepId: 'plan',
disabled: false,
},
];
}
return {
value: isSubscriptionPlanYearly
? plan.getYearlyPlanId()
: plan.getMonthlyPlanId(),
title: plan.getName(),
description: description,
sideTitle: plan.isCustomPricing()
? "Custom Price"
: isSubscriptionPlanYearly
? "$" +
plan.getYearlySubscriptionAmountInUSD().toString() +
"/mo billed yearly"
: "$" + plan.getMonthlySubscriptionAmountInUSD().toString(),
sideDescription: plan.isCustomPricing()
? ""
: isSubscriptionPlanYearly
? `~ $${
plan.getYearlySubscriptionAmountInUSD() * 12
} per user / year`
: `/month per user`,
};
}),
title: "Please select a plan.",
required: true,
},
{
field: {
paymentProviderPromoCode: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: "Promo Code (Optional)",
description: "If you have a coupon code, enter it here.",
title: "Promo Code",
required: false,
stepId: "plan",
disabled: false,
},
];
}
setFields(formFields);
};
setFields(formFields);
};
const [fields, setFields] = useState<Array<Field<Project>>>([]);
const [fields, setFields] = useState<Array<Field<Project>>>([]);
const getFooter: GetReactElementFunction = (): ReactElement => {
if (!BILLING_ENABLED) {
return <></>;
}
return (
<Toggle
title="Yearly Plan"
value={isSubscriptionPlanYearly}
description="(Save 20%)"
onChange={(value: boolean) => {
setIsSubscriptionPlanYearly(value);
}}
/>
);
};
const getFooter: GetReactElementFunction = (): ReactElement => {
if (!BILLING_ENABLED) {
return <></>;
}
return (
<Page
title={'Projects'}
breadcrumbLinks={[
{
title: 'Admin Dashboard',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
},
{
title: 'Projects',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECTS] as Route
),
},
]}
>
<ModelTable<Project>
modelType={Project}
modelAPI={AdminModelAPI}
id="projects-table"
isDeleteable={false}
isEditable={false}
isCreateable={true}
name="Projects"
isViewable={false}
cardProps={{
title: 'Projects',
description: 'Here is a list of proejcts in OneUptime.',
}}
showViewIdButton={true}
formSteps={
BILLING_ENABLED
? [
{
title: 'Basic',
id: 'basic',
},
{
title: 'Select Plan',
id: 'plan',
},
]
: undefined
}
noItemsMessage={'No projects found.'}
formFields={fields}
showRefreshButton={true}
viewPageRoute={Navigation.getCurrentRoute()}
filters={[
{
field: {
name: true,
},
title: 'Name',
type: FieldType.Text,
},
{
field: {
createdAt: true,
},
title: 'Created At',
type: FieldType.DateTime,
},
]}
columns={[
{
field: {
name: true,
},
title: 'Name',
type: FieldType.Text,
},
{
field: {
createdAt: true,
},
title: 'Created At',
type: FieldType.DateTime,
},
]}
/>
</Page>
<Toggle
title="Yearly Plan"
value={isSubscriptionPlanYearly}
description="(Save 20%)"
onChange={(value: boolean) => {
setIsSubscriptionPlanYearly(value);
}}
/>
);
};
return (
<Page
title={"Projects"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
},
{
title: "Projects",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.PROJECTS] as Route,
),
},
]}
>
<ModelTable<Project>
modelType={Project}
modelAPI={AdminModelAPI}
id="projects-table"
isDeleteable={false}
isEditable={false}
isCreateable={true}
name="Projects"
isViewable={false}
cardProps={{
title: "Projects",
description: "Here is a list of proejcts in OneUptime.",
}}
showViewIdButton={true}
formSteps={
BILLING_ENABLED
? [
{
title: "Basic",
id: "basic",
},
{
title: "Select Plan",
id: "plan",
},
]
: undefined
}
noItemsMessage={"No projects found."}
formFields={fields}
showRefreshButton={true}
viewPageRoute={Navigation.getCurrentRoute()}
filters={[
{
field: {
name: true,
},
title: "Name",
type: FieldType.Text,
},
{
field: {
createdAt: true,
},
title: "Created At",
type: FieldType.DateTime,
},
]}
columns={[
{
field: {
name: true,
},
title: "Name",
type: FieldType.Text,
},
{
field: {
createdAt: true,
},
title: "Created At",
type: FieldType.DateTime,
},
]}
/>
</Page>
);
};
export default Projects;

View File

@@ -1,102 +1,100 @@
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import DashboardSideMenu from '../SideMenu';
import Route from 'Common/Types/API/Route';
import ObjectID from 'Common/Types/ObjectID';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
import Page from 'CommonUI/src/Components/Page/Page';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import GlobalConfig from 'Model/Models/GlobalConfig';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from "../../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
import DashboardSideMenu from "../SideMenu";
import Route from "Common/Types/API/Route";
import ObjectID from "Common/Types/ObjectID";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
import Page from "CommonUI/src/Components/Page/Page";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import GlobalConfig from "Model/Models/GlobalConfig";
import React, { FunctionComponent, ReactElement } from "react";
const Settings: FunctionComponent = (): ReactElement => {
return (
<Page
title={'Admin Settings'}
breadcrumbLinks={[
{
title: 'Admin Dashboard',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
},
{
title: 'Settings',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route
),
},
{
title: 'API Key',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_HOST] as Route
),
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
<CardModelDetail
name="API Key Settings"
cardProps={{
title: 'Master API Key Settings',
description:
'This API key has root access to all the resources in all the projects on OneUptime.',
}}
isEditable={true}
editButtonText="Edit API Key Settings"
formFields={[
{
field: {
masterApiKey: true,
},
title: 'Master API Key',
fieldType: FormFieldSchemaType.ObjectID,
required: false,
},
{
field: {
isMasterApiKeyEnabled: true,
},
title: 'Enabled',
fieldType: FormFieldSchemaType.Toggle,
required: false,
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: 'model-detail-global-config',
fields: [
{
field: {
masterApiKey: true,
},
title: 'Master API Key',
description:
'This API key has root access to all the resources in all the projects on OneUptime.',
fieldType: FieldType.HiddenText,
opts: {
isCopyable: true,
},
placeholder: 'API Key not generated yet.',
},
{
field: {
isMasterApiKeyEnabled: true,
},
title: 'Enabled',
description:
'Enable or disable the master API key. If disabled, all requests using this key will fail.',
fieldType: FieldType.Boolean,
placeholder: 'Not Enabled',
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
</Page>
);
return (
<Page
title={"Admin Settings"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
},
{
title: "Settings",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route,
),
},
{
title: "API Key",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_HOST] as Route,
),
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
<CardModelDetail
name="API Key Settings"
cardProps={{
title: "Master API Key Settings",
description:
"This API key has root access to all the resources in all the projects on OneUptime.",
}}
isEditable={true}
editButtonText="Edit API Key Settings"
formFields={[
{
field: {
masterApiKey: true,
},
title: "Master API Key",
fieldType: FormFieldSchemaType.ObjectID,
required: false,
},
{
field: {
isMasterApiKeyEnabled: true,
},
title: "Enabled",
fieldType: FormFieldSchemaType.Toggle,
required: false,
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: "model-detail-global-config",
fields: [
{
field: {
masterApiKey: true,
},
title: "Master API Key",
description:
"This API key has root access to all the resources in all the projects on OneUptime.",
fieldType: FieldType.HiddenText,
opts: {
isCopyable: true,
},
placeholder: "API Key not generated yet.",
},
{
field: {
isMasterApiKeyEnabled: true,
},
title: "Enabled",
description:
"Enable or disable the master API key. If disabled, all requests using this key will fail.",
fieldType: FieldType.Boolean,
placeholder: "Not Enabled",
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
</Page>
);
};
export default Settings;

View File

@@ -1,83 +1,80 @@
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import DashboardSideMenu from '../SideMenu';
import Route from 'Common/Types/API/Route';
import ObjectID from 'Common/Types/ObjectID';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
import Page from 'CommonUI/src/Components/Page/Page';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import GlobalConfig from 'Model/Models/GlobalConfig';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from "../../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
import DashboardSideMenu from "../SideMenu";
import Route from "Common/Types/API/Route";
import ObjectID from "Common/Types/ObjectID";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
import Page from "CommonUI/src/Components/Page/Page";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import GlobalConfig from "Model/Models/GlobalConfig";
import React, { FunctionComponent, ReactElement } from "react";
const Settings: FunctionComponent = (): ReactElement => {
return (
<Page
title={'Admin Settings'}
breadcrumbLinks={[
{
title: 'Admin Dashboard',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
},
{
title: 'Settings',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route
),
},
{
title: 'Authentication',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route
),
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
<CardModelDetail
name="Authentication Settings"
cardProps={{
title: 'Authentication Settings',
description:
'Authentication Settings for this OneUptime Server instance.',
}}
isEditable={true}
editButtonText="Edit Settings"
formFields={[
{
field: {
disableSignup: true,
},
title: 'Disable Sign Up',
fieldType: FormFieldSchemaType.Toggle,
required: false,
description:
'Should we disable sign up of new users to OneUptime?',
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: 'model-detail-global-config',
fields: [
{
field: {
disableSignup: true,
},
fieldType: FieldType.Boolean,
title: 'Disable Sign Up',
placeholder: 'No',
description:
'Should we disable sign up of new users to OneUptime?',
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
</Page>
);
return (
<Page
title={"Admin Settings"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
},
{
title: "Settings",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route,
),
},
{
title: "Authentication",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route,
),
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
<CardModelDetail
name="Authentication Settings"
cardProps={{
title: "Authentication Settings",
description:
"Authentication Settings for this OneUptime Server instance.",
}}
isEditable={true}
editButtonText="Edit Settings"
formFields={[
{
field: {
disableSignup: true,
},
title: "Disable Sign Up",
fieldType: FormFieldSchemaType.Toggle,
required: false,
description: "Should we disable sign up of new users to OneUptime?",
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: "model-detail-global-config",
fields: [
{
field: {
disableSignup: true,
},
fieldType: FieldType.Boolean,
title: "Disable Sign Up",
placeholder: "No",
description:
"Should we disable sign up of new users to OneUptime?",
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
</Page>
);
};
export default Settings;

View File

@@ -1,119 +1,114 @@
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import DashboardSideMenu from '../SideMenu';
import Route from 'Common/Types/API/Route';
import ObjectID from 'Common/Types/ObjectID';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
import Page from 'CommonUI/src/Components/Page/Page';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import GlobalConfig from 'Model/Models/GlobalConfig';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from "../../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
import DashboardSideMenu from "../SideMenu";
import Route from "Common/Types/API/Route";
import ObjectID from "Common/Types/ObjectID";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
import Page from "CommonUI/src/Components/Page/Page";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import GlobalConfig from "Model/Models/GlobalConfig";
import React, { FunctionComponent, ReactElement } from "react";
const Settings: FunctionComponent = (): ReactElement => {
return (
<Page
title={'Admin Settings'}
breadcrumbLinks={[
{
title: 'Admin Dashboard',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
},
{
title: 'Settings',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route
),
},
{
title: 'Calls and SMS',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route
),
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
<CardModelDetail
name="Call and SMS Settings"
cardProps={{
title: 'Twilio Config',
description: 'This will be used to make Call and send SMS.',
}}
isEditable={true}
editButtonText="Edit Twilio Config"
formFields={[
{
field: {
twilioAccountSID: true,
},
title: 'Twilio Account SID',
fieldType: FormFieldSchemaType.Text,
required: true,
description:
'You can find this in your Twilio console.',
placeholder: '',
validation: {
minLength: 2,
},
},
{
field: {
twilioAuthToken: true,
},
title: 'Twilio Auth Token',
fieldType: FormFieldSchemaType.Text,
required: true,
description:
'You can find this in your Twilio console.',
placeholder: '',
validation: {
minLength: 2,
},
},
{
field: {
twilioPhoneNumber: true,
},
title: 'Twilio Phone Number',
fieldType: FormFieldSchemaType.Phone,
required: true,
description:
'You can find this in your Twilio console.',
placeholder: '',
validation: {
minLength: 2,
},
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: 'model-detail-global-config',
fields: [
{
field: {
twilioAccountSID: true,
},
title: 'Twilio Account SID',
placeholder: 'None',
},
{
field: {
twilioPhoneNumber: true,
},
title: 'Twilio Phone Number',
fieldType: FieldType.Phone,
placeholder: 'None',
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
</Page>
);
return (
<Page
title={"Admin Settings"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
},
{
title: "Settings",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route,
),
},
{
title: "Calls and SMS",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route,
),
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
<CardModelDetail
name="Call and SMS Settings"
cardProps={{
title: "Twilio Config",
description: "This will be used to make Call and send SMS.",
}}
isEditable={true}
editButtonText="Edit Twilio Config"
formFields={[
{
field: {
twilioAccountSID: true,
},
title: "Twilio Account SID",
fieldType: FormFieldSchemaType.Text,
required: true,
description: "You can find this in your Twilio console.",
placeholder: "",
validation: {
minLength: 2,
},
},
{
field: {
twilioAuthToken: true,
},
title: "Twilio Auth Token",
fieldType: FormFieldSchemaType.Text,
required: true,
description: "You can find this in your Twilio console.",
placeholder: "",
validation: {
minLength: 2,
},
},
{
field: {
twilioPhoneNumber: true,
},
title: "Twilio Phone Number",
fieldType: FormFieldSchemaType.Phone,
required: true,
description: "You can find this in your Twilio console.",
placeholder: "",
validation: {
minLength: 2,
},
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: "model-detail-global-config",
fields: [
{
field: {
twilioAccountSID: true,
},
title: "Twilio Account SID",
placeholder: "None",
},
{
field: {
twilioPhoneNumber: true,
},
title: "Twilio Phone Number",
fieldType: FieldType.Phone,
placeholder: "None",
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
</Page>
);
};
export default Settings;

View File

@@ -1,427 +1,419 @@
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import DashboardSideMenu from '../SideMenu';
import Route from 'Common/Types/API/Route';
import { Green, Red } from 'Common/Types/BrandColors';
import { PromiseVoidFunction } from 'Common/Types/FunctionTypes';
import ObjectID from 'Common/Types/ObjectID';
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
import Page from 'CommonUI/src/Components/Page/Page';
import Pill from 'CommonUI/src/Components/Pill/Pill';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import GlobalConfig, { EmailServerType } from 'Model/Models/GlobalConfig';
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
import PageMap from "../../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
import DashboardSideMenu from "../SideMenu";
import Route from "Common/Types/API/Route";
import { Green, Red } from "Common/Types/BrandColors";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import ObjectID from "Common/Types/ObjectID";
import ErrorMessage from "CommonUI/src/Components/ErrorMessage/ErrorMessage";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import CardModelDetail from "CommonUI/src/Components/ModelDetail/CardModelDetail";
import Page from "CommonUI/src/Components/Page/Page";
import Pill from "CommonUI/src/Components/Pill/Pill";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import DropdownUtil from "CommonUI/src/Utils/Dropdown";
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
import GlobalConfig, { EmailServerType } from "Model/Models/GlobalConfig";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
const Settings: FunctionComponent = (): ReactElement => {
const [emailServerType, setemailServerType] =
React.useState<EmailServerType>(EmailServerType.Internal);
const [emailServerType, setemailServerType] = React.useState<EmailServerType>(
EmailServerType.Internal,
);
const [isLoading, setIsLoading] = React.useState<boolean>(true);
const [isLoading, setIsLoading] = React.useState<boolean>(true);
const [error, setError] = React.useState<string>('');
const [error, setError] = React.useState<string>("");
const fetchItem: PromiseVoidFunction = async (): Promise<void> => {
setIsLoading(true);
const fetchItem: PromiseVoidFunction = async (): Promise<void> => {
setIsLoading(true);
const globalConfig: GlobalConfig | null =
await ModelAPI.getItem<GlobalConfig>({
modelType: GlobalConfig,
id: ObjectID.getZeroObjectID(),
select: {
_id: true,
emailServerType: true,
},
});
const globalConfig: GlobalConfig | null =
await ModelAPI.getItem<GlobalConfig>({
modelType: GlobalConfig,
id: ObjectID.getZeroObjectID(),
select: {
_id: true,
emailServerType: true,
},
});
if (globalConfig) {
setemailServerType(
globalConfig.emailServerType || EmailServerType.Internal
);
}
setIsLoading(false);
};
useEffect(() => {
fetchItem().catch((err: Error) => {
setError(err.message);
});
}, []);
if (isLoading) {
return <PageLoader isVisible={true} />;
if (globalConfig) {
setemailServerType(
globalConfig.emailServerType || EmailServerType.Internal,
);
}
if (error) {
return <ErrorMessage error={error} />;
}
setIsLoading(false);
};
return (
<Page
title={'Admin Settings'}
breadcrumbLinks={[
{
title: 'Admin Dashboard',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
useEffect(() => {
fetchItem().catch((err: Error) => {
setError(err.message);
});
}, []);
if (isLoading) {
return <PageLoader isVisible={true} />;
}
if (error) {
return <ErrorMessage error={error} />;
}
return (
<Page
title={"Admin Settings"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
},
{
title: "Settings",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route,
),
},
{
title: "Email Settings",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_SMTP] as Route,
),
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
<CardModelDetail
name="Admin Notification Email"
cardProps={{
title: "Admin Notification Email",
description:
"Enter the email address where you would like to receive admin-level notifications.",
}}
isEditable={true}
editButtonText="Edit Email"
formFields={[
{
field: {
adminNotificationEmail: true,
},
title: "Admin Notification Email",
fieldType: FormFieldSchemaType.Email,
required: false,
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: "model-detail-global-config",
fields: [
{
field: {
adminNotificationEmail: true,
},
title: "Admin Notification Email",
fieldType: FieldType.Email,
placeholder: "None",
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
<CardModelDetail
name="Internal SMTP Settings"
cardProps={{
title: "Email Server Settings",
description:
"Pick which email server you would like to use to send emails.",
}}
isEditable={true}
editButtonText="Edit Server"
onSaveSuccess={() => {
window.location.reload();
}}
formFields={[
{
field: {
emailServerType: true,
},
title: "Email Server Type",
fieldType: FormFieldSchemaType.Dropdown,
dropdownOptions:
DropdownUtil.getDropdownOptionsFromEnum(EmailServerType),
required: true,
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: "model-detail-global-config",
fields: [
{
field: {
emailServerType: true,
},
title: "Email Server Type",
fieldType: FieldType.Text,
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
{emailServerType === EmailServerType.CustomSMTP ? (
<CardModelDetail
name="Host Settings"
cardProps={{
title: "Custom Email and SMTP Settings",
description:
"If you have not enabled Internal SMTP server to send emails. Please configure your SMTP server here.",
}}
isEditable={true}
editButtonText="Edit SMTP Config"
formSteps={[
{
title: "SMTP Server",
id: "server-info",
},
{
title: "Authentication",
id: "authentication",
},
{
title: "Email",
id: "email-info",
},
]}
formFields={[
{
field: {
smtpHost: true,
},
title: "Hostname",
stepId: "server-info",
fieldType: FormFieldSchemaType.Hostname,
required: true,
placeholder: "smtp.server.com",
},
{
field: {
smtpPort: true,
},
title: "Port",
stepId: "server-info",
fieldType: FormFieldSchemaType.Port,
required: true,
placeholder: "587",
},
{
field: {
isSMTPSecure: true,
},
title: "Use SSL / TLS",
stepId: "server-info",
fieldType: FormFieldSchemaType.Toggle,
description:
"If you use port 465, please enable this. Do not enable this if you use port 587.",
},
{
field: {
smtpUsername: true,
},
title: "Username",
stepId: "authentication",
fieldType: FormFieldSchemaType.Text,
required: false,
placeholder: "emailuser",
},
{
field: {
smtpPassword: true,
},
title: "Password",
stepId: "authentication",
fieldType: FormFieldSchemaType.EncryptedText,
required: false,
placeholder: "Password",
},
{
field: {
smtpFromEmail: true,
},
title: "Email From",
stepId: "email-info",
fieldType: FormFieldSchemaType.Email,
required: true,
description:
"This is the display email your team and customers see, when they receive emails from OneUptime.",
placeholder: "email@company.com",
},
{
field: {
smtpFromName: true,
},
title: "From Name",
stepId: "email-info",
fieldType: FormFieldSchemaType.Text,
required: true,
description:
"This is the display name your team and customers see, when they receive emails from OneUptime.",
placeholder: "Company, Inc.",
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: "model-detail-global-config",
fields: [
{
field: {
smtpHost: true,
},
{
title: 'Settings',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route
),
title: "SMTP Host",
placeholder: "None",
},
{
field: {
smtpPort: true,
},
{
title: 'Email Settings',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_SMTP] as Route
),
title: "SMTP Port",
placeholder: "None",
},
{
field: {
smtpUsername: true,
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
title: "SMTP Username",
placeholder: "None",
},
{
field: {
smtpFromEmail: true,
},
title: "SMTP Email",
placeholder: "None",
fieldType: FieldType.Email,
},
{
field: {
smtpFromName: true,
},
title: "SMTP From Name",
placeholder: "None",
},
{
field: {
isSMTPSecure: true,
},
title: "Use SSL/TLS",
placeholder: "No",
fieldType: FieldType.Boolean,
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
) : (
<></>
)}
<CardModelDetail
name="Admin Notification Email"
cardProps={{
title: 'Admin Notification Email',
description:
'Enter the email address where you would like to receive admin-level notifications.',
}}
isEditable={true}
editButtonText="Edit Email"
formFields={[
{
field: {
adminNotificationEmail: true,
},
title: 'Admin Notification Email',
fieldType: FormFieldSchemaType.Email,
required: false,
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: 'model-detail-global-config',
fields: [
{
field: {
adminNotificationEmail: true,
},
title: 'Admin Notification Email',
fieldType: FieldType.Email,
placeholder: 'None',
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
{emailServerType === EmailServerType.Sendgrid ? (
<CardModelDetail<GlobalConfig>
name="Sendgrid Settings"
cardProps={{
title: "Sendgrid Settings",
description:
"Enter your Sendgrid API key to send emails through Sendgrid.",
}}
isEditable={true}
editButtonText="Edit API Key"
formFields={[
{
field: {
sendgridApiKey: true,
},
title: "Sendgrid API Key",
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: "Sendgrid API Key",
},
{
field: {
sendgridFromEmail: true,
},
title: "From Email",
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: "email@yourcompany.com",
},
{
field: {
sendgridFromName: true,
},
title: "From Name",
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: "Acme, Inc.",
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: "model-detail-global-config",
selectMoreFields: {
sendgridFromEmail: true,
sendgridFromName: true,
},
fields: [
{
field: {
sendgridApiKey: true,
},
title: "",
placeholder: "None",
getElement: (item: GlobalConfig) => {
if (
item["sendgridApiKey"] &&
item["sendgridFromEmail"] &&
item["sendgridFromName"]
) {
return <Pill text="Enabled" color={Green} />;
} else if (!item["sendgridApiKey"]) {
return (
<Pill
text="Not Enabled. Please add the API key."
color={Red}
/>
);
} else if (!item["sendgridFromEmail"]) {
return (
<Pill
text="Not Enabled. Please add the From Email."
color={Red}
/>
);
} else if (!item["sendgridFromName"]) {
return (
<Pill
text="Not Enabled. Please add the From Name."
color={Red}
/>
);
}
<CardModelDetail
name="Internal SMTP Settings"
cardProps={{
title: 'Email Server Settings',
description:
'Pick which email server you would like to use to send emails.',
}}
isEditable={true}
editButtonText="Edit Server"
onSaveSuccess={() => {
window.location.reload();
}}
formFields={[
{
field: {
emailServerType: true,
},
title: 'Email Server Type',
fieldType: FormFieldSchemaType.Dropdown,
dropdownOptions:
DropdownUtil.getDropdownOptionsFromEnum(
EmailServerType
),
required: true,
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: 'model-detail-global-config',
fields: [
{
field: {
emailServerType: true,
},
title: 'Email Server Type',
fieldType: FieldType.Text,
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
{emailServerType === EmailServerType.CustomSMTP ? (
<CardModelDetail
name="Host Settings"
cardProps={{
title: 'Custom Email and SMTP Settings',
description:
'If you have not enabled Internal SMTP server to send emails. Please configure your SMTP server here.',
}}
isEditable={true}
editButtonText="Edit SMTP Config"
formSteps={[
{
title: 'SMTP Server',
id: 'server-info',
},
{
title: 'Authentication',
id: 'authentication',
},
{
title: 'Email',
id: 'email-info',
},
]}
formFields={[
{
field: {
smtpHost: true,
},
title: 'Hostname',
stepId: 'server-info',
fieldType: FormFieldSchemaType.Hostname,
required: true,
placeholder: 'smtp.server.com',
},
{
field: {
smtpPort: true,
},
title: 'Port',
stepId: 'server-info',
fieldType: FormFieldSchemaType.Port,
required: true,
placeholder: '587',
},
{
field: {
isSMTPSecure: true,
},
title: 'Use SSL / TLS',
stepId: 'server-info',
fieldType: FormFieldSchemaType.Toggle,
description:
'If you use port 465, please enable this. Do not enable this if you use port 587.',
},
{
field: {
smtpUsername: true,
},
title: 'Username',
stepId: 'authentication',
fieldType: FormFieldSchemaType.Text,
required: false,
placeholder: 'emailuser',
},
{
field: {
smtpPassword: true,
},
title: 'Password',
stepId: 'authentication',
fieldType: FormFieldSchemaType.EncryptedText,
required: false,
placeholder: 'Password',
},
{
field: {
smtpFromEmail: true,
},
title: 'Email From',
stepId: 'email-info',
fieldType: FormFieldSchemaType.Email,
required: true,
description:
'This is the display email your team and customers see, when they receive emails from OneUptime.',
placeholder: 'email@company.com',
},
{
field: {
smtpFromName: true,
},
title: 'From Name',
stepId: 'email-info',
fieldType: FormFieldSchemaType.Text,
required: true,
description:
'This is the display name your team and customers see, when they receive emails from OneUptime.',
placeholder: 'Company, Inc.',
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: 'model-detail-global-config',
fields: [
{
field: {
smtpHost: true,
},
title: 'SMTP Host',
placeholder: 'None',
},
{
field: {
smtpPort: true,
},
title: 'SMTP Port',
placeholder: 'None',
},
{
field: {
smtpUsername: true,
},
title: 'SMTP Username',
placeholder: 'None',
},
{
field: {
smtpFromEmail: true,
},
title: 'SMTP Email',
placeholder: 'None',
fieldType: FieldType.Email,
},
{
field: {
smtpFromName: true,
},
title: 'SMTP From Name',
placeholder: 'None',
},
{
field: {
isSMTPSecure: true,
},
title: 'Use SSL/TLS',
placeholder: 'No',
fieldType: FieldType.Boolean,
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
) : (
<></>
)}
{emailServerType === EmailServerType.Sendgrid ? (
<CardModelDetail<GlobalConfig>
name="Sendgrid Settings"
cardProps={{
title: 'Sendgrid Settings',
description:
'Enter your Sendgrid API key to send emails through Sendgrid.',
}}
isEditable={true}
editButtonText="Edit API Key"
formFields={[
{
field: {
sendgridApiKey: true,
},
title: 'Sendgrid API Key',
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: 'Sendgrid API Key',
},
{
field: {
sendgridFromEmail: true,
},
title: 'From Email',
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: 'email@yourcompany.com',
},
{
field: {
sendgridFromName: true,
},
title: 'From Name',
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: 'Acme, Inc.',
},
]}
modelDetailProps={{
modelType: GlobalConfig,
id: 'model-detail-global-config',
selectMoreFields: {
sendgridFromEmail: true,
sendgridFromName: true,
},
fields: [
{
field: {
sendgridApiKey: true,
},
title: '',
placeholder: 'None',
getElement: (item: GlobalConfig) => {
if (
item['sendgridApiKey'] &&
item['sendgridFromEmail'] &&
item['sendgridFromName']
) {
return (
<Pill
text="Enabled"
color={Green}
/>
);
} else if (!item['sendgridApiKey']) {
return (
<Pill
text="Not Enabled. Please add the API key."
color={Red}
/>
);
} else if (!item['sendgridFromEmail']) {
return (
<Pill
text="Not Enabled. Please add the From Email."
color={Red}
/>
);
} else if (!item['sendgridFromName']) {
return (
<Pill
text="Not Enabled. Please add the From Name."
color={Red}
/>
);
}
return <></>;
},
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
) : (
<></>
)}
</Page>
);
return <></>;
},
},
],
modelId: ObjectID.getZeroObjectID(),
}}
/>
) : (
<></>
)}
</Page>
);
};
export default Settings;

View File

@@ -1,251 +1,243 @@
import AdminModelAPI from '../../../Utils/ModelAPI';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import DashboardSideMenu from '../SideMenu';
import Route from 'Common/Types/API/Route';
import IsNull from 'Common/Types/BaseDatabase/IsNull';
import { Green, Red } from 'Common/Types/BrandColors';
import OneUptimeDate from 'Common/Types/Date';
import { ErrorFunction, VoidFunction } from 'Common/Types/FunctionTypes';
import Banner from 'CommonUI/src/Components/Banner/Banner';
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import Page from 'CommonUI/src/Components/Page/Page';
import ProbeElement from 'CommonUI/src/Components/Probe/Probe';
import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import Probe from 'Model/Models/Probe';
import React, { FunctionComponent, ReactElement, useState } from 'react';
import AdminModelAPI from "../../../Utils/ModelAPI";
import PageMap from "../../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
import DashboardSideMenu from "../SideMenu";
import Route from "Common/Types/API/Route";
import IsNull from "Common/Types/BaseDatabase/IsNull";
import { Green, Red } from "Common/Types/BrandColors";
import OneUptimeDate from "Common/Types/Date";
import { ErrorFunction, VoidFunction } from "Common/Types/FunctionTypes";
import Banner from "CommonUI/src/Components/Banner/Banner";
import { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
import Page from "CommonUI/src/Components/Page/Page";
import ProbeElement from "CommonUI/src/Components/Probe/Probe";
import Statusbubble from "CommonUI/src/Components/StatusBubble/StatusBubble";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import Probe from "Model/Models/Probe";
import React, { FunctionComponent, ReactElement, useState } from "react";
const Settings: FunctionComponent = (): ReactElement => {
const [showKeyModal, setShowKeyModal] = useState<boolean>(false);
const [showKeyModal, setShowKeyModal] = useState<boolean>(false);
const [currentProbe, setCurrentProbe] = useState<Probe | null>(null);
const [currentProbe, setCurrentProbe] = useState<Probe | null>(null);
return (
<Page
title={'Admin Settings'}
breadcrumbLinks={[
{
title: 'Admin Dashboard',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
},
{
title: 'Settings',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route
),
},
{
title: 'Global Probes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_PROBES] as Route
),
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
return (
<Page
title={"Admin Settings"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
},
{
title: "Settings",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route,
),
},
{
title: "Global Probes",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_PROBES] as Route,
),
},
]}
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
<Banner
openInNewTab={true}
title="Need help with setting up Global Probes?"
description="Here is a guide which will help you get set up"
link={Route.fromString('/docs/probe/custom-probe')}
/>
<Banner
openInNewTab={true}
title="Need help with setting up Global Probes?"
description="Here is a guide which will help you get set up"
link={Route.fromString("/docs/probe/custom-probe")}
/>
<ModelTable<Probe>
modelType={Probe}
id="probes-table"
name="Settings > Global Probes"
isDeleteable={true}
isEditable={true}
isCreateable={true}
cardProps={{
title: 'Global Probes',
description:
'Global Probes help you monitor external resources from different locations around the world.',
}}
query={{
projectId: new IsNull(),
isGlobalProbe: true,
}}
modelAPI={AdminModelAPI}
noItemsMessage={'No probes found.'}
showRefreshButton={true}
onBeforeCreate={(item: Probe) => {
item.isGlobalProbe = true;
return Promise.resolve(item);
}}
formFields={[
{
field: {
name: true,
},
title: 'Name',
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: 'internal-probe',
validation: {
minLength: 2,
},
},
<ModelTable<Probe>
modelType={Probe}
id="probes-table"
name="Settings > Global Probes"
isDeleteable={true}
isEditable={true}
isCreateable={true}
cardProps={{
title: "Global Probes",
description:
"Global Probes help you monitor external resources from different locations around the world.",
}}
query={{
projectId: new IsNull(),
isGlobalProbe: true,
}}
modelAPI={AdminModelAPI}
noItemsMessage={"No probes found."}
showRefreshButton={true}
onBeforeCreate={(item: Probe) => {
item.isGlobalProbe = true;
return Promise.resolve(item);
}}
formFields={[
{
field: {
name: true,
},
title: "Name",
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: "internal-probe",
validation: {
minLength: 2,
},
},
{
field: {
description: true,
},
title: 'Description',
fieldType: FormFieldSchemaType.LongText,
required: true,
placeholder:
'This probe is to monitor all the internal services.',
},
{
field: {
description: true,
},
title: "Description",
fieldType: FormFieldSchemaType.LongText,
required: true,
placeholder: "This probe is to monitor all the internal services.",
},
{
field: {
iconFile: true,
},
title: 'Probe Logo',
fieldType: FormFieldSchemaType.ImageFile,
required: false,
placeholder: 'Upload logo',
},
]}
selectMoreFields={{
key: true,
iconFileId: true,
}}
actionButtons={[
{
title: 'Show ID and Key',
buttonStyleType: ButtonStyleType.NORMAL,
onClick: async (
item: Probe,
onCompleteAction: VoidFunction,
onError: ErrorFunction
) => {
try {
setCurrentProbe(item);
setShowKeyModal(true);
{
field: {
iconFile: true,
},
title: "Probe Logo",
fieldType: FormFieldSchemaType.ImageFile,
required: false,
placeholder: "Upload logo",
},
]}
selectMoreFields={{
key: true,
iconFileId: true,
}}
actionButtons={[
{
title: "Show ID and Key",
buttonStyleType: ButtonStyleType.NORMAL,
onClick: async (
item: Probe,
onCompleteAction: VoidFunction,
onError: ErrorFunction,
) => {
try {
setCurrentProbe(item);
setShowKeyModal(true);
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
]}
filters={[
{
field: {
name: true,
},
title: 'Name',
type: FieldType.Text,
},
{
field: {
description: true,
},
title: 'Description',
type: FieldType.Text,
},
]}
columns={[
{
field: {
name: true,
},
title: 'Name',
type: FieldType.Text,
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
]}
filters={[
{
field: {
name: true,
},
title: "Name",
type: FieldType.Text,
},
{
field: {
description: true,
},
title: "Description",
type: FieldType.Text,
},
]}
columns={[
{
field: {
name: true,
},
title: "Name",
type: FieldType.Text,
getElement: (item: Probe): ReactElement => {
return <ProbeElement probe={item} />;
},
},
{
field: {
description: true,
},
title: 'Description',
type: FieldType.Text,
},
{
field: {
lastAlive: true,
},
title: 'Status',
type: FieldType.Text,
getElement: (item: Probe): ReactElement => {
if (
item &&
item['lastAlive'] &&
OneUptimeDate.getNumberOfMinutesBetweenDates(
OneUptimeDate.fromString(item['lastAlive']),
OneUptimeDate.getCurrentDate()
) < 5
) {
return (
<Statusbubble
text={'Connected'}
color={Green}
shouldAnimate={true}
/>
);
}
getElement: (item: Probe): ReactElement => {
return <ProbeElement probe={item} />;
},
},
{
field: {
description: true,
},
title: "Description",
type: FieldType.Text,
},
{
field: {
lastAlive: true,
},
title: "Status",
type: FieldType.Text,
getElement: (item: Probe): ReactElement => {
if (
item &&
item["lastAlive"] &&
OneUptimeDate.getNumberOfMinutesBetweenDates(
OneUptimeDate.fromString(item["lastAlive"]),
OneUptimeDate.getCurrentDate(),
) < 5
) {
return (
<Statusbubble
text={"Connected"}
color={Green}
shouldAnimate={true}
/>
);
}
return (
<Statusbubble
text={'Disconnected'}
color={Red}
shouldAnimate={false}
/>
);
},
},
]}
/>
{showKeyModal && currentProbe ? (
<ConfirmModal
title={`Probe Key`}
description={
<div>
<span>
Here is your probe key. Please keep this a
secret.
</span>
<br />
<br />
<span>
<b>Probe ID: </b>{' '}
{currentProbe['_id']?.toString()}
</span>
<br />
<br />
<span>
<b>Probe Key: </b>{' '}
{currentProbe['key']?.toString()}
</span>
</div>
}
submitButtonText={'Close'}
submitButtonType={ButtonStyleType.NORMAL}
onSubmit={async () => {
setShowKeyModal(false);
}}
return (
<Statusbubble
text={"Disconnected"}
color={Red}
shouldAnimate={false}
/>
) : (
<></>
)}
</Page>
);
);
},
},
]}
/>
{showKeyModal && currentProbe ? (
<ConfirmModal
title={`Probe Key`}
description={
<div>
<span>Here is your probe key. Please keep this a secret.</span>
<br />
<br />
<span>
<b>Probe ID: </b> {currentProbe["_id"]?.toString()}
</span>
<br />
<br />
<span>
<b>Probe Key: </b> {currentProbe["key"]?.toString()}
</span>
</div>
}
submitButtonText={"Close"}
submitButtonType={ButtonStyleType.NORMAL}
onSubmit={async () => {
setShowKeyModal(false);
}}
/>
) : (
<></>
)}
</Page>
);
};
export default Settings;

View File

@@ -1,17 +1,17 @@
import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import Route from 'Common/Types/API/Route';
import IconProp from 'Common/Types/Icon/IconProp';
import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu';
import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem';
import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection';
import React, { ReactElement } from 'react';
import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import IconProp from "Common/Types/Icon/IconProp";
import SideMenu from "CommonUI/src/Components/SideMenu/SideMenu";
import SideMenuItem from "CommonUI/src/Components/SideMenu/SideMenuItem";
import SideMenuSection from "CommonUI/src/Components/SideMenu/SideMenuSection";
import React, { ReactElement } from "react";
const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
return (
<SideMenu>
<SideMenuSection title="Basic">
{/* <SideMenuItem
return (
<SideMenu>
<SideMenuSection title="Basic">
{/* <SideMenuItem
link={{
title: 'Host',
to: RouteUtil.populateRouteParams(
@@ -20,62 +20,62 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
}}
icon={IconProp.Globe}
/> */}
<SideMenuItem
link={{
title: 'Authentication',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route
),
}}
icon={IconProp.Lock}
/>
</SideMenuSection>
<SideMenuItem
link={{
title: "Authentication",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_AUTHENTICATION] as Route,
),
}}
icon={IconProp.Lock}
/>
</SideMenuSection>
<SideMenuSection title="Notifications">
<SideMenuItem
link={{
title: 'Emails',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_SMTP] as Route
),
}}
icon={IconProp.Email}
/>
<SideMenuItem
link={{
title: 'Call and SMS',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route
),
}}
icon={IconProp.Call}
/>
</SideMenuSection>
<SideMenuSection title="Notifications">
<SideMenuItem
link={{
title: "Emails",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_SMTP] as Route,
),
}}
icon={IconProp.Email}
/>
<SideMenuItem
link={{
title: "Call and SMS",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_CALL_AND_SMS] as Route,
),
}}
icon={IconProp.Call}
/>
</SideMenuSection>
<SideMenuSection title="Monitoring">
<SideMenuItem
link={{
title: 'Global Probes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_PROBES] as Route
),
}}
icon={IconProp.Signal}
/>
</SideMenuSection>
<SideMenuSection title="API and Integrations">
<SideMenuItem
link={{
title: 'API Key',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_API_KEY] as Route
),
}}
icon={IconProp.Code}
/>
</SideMenuSection>
</SideMenu>
);
<SideMenuSection title="Monitoring">
<SideMenuItem
link={{
title: "Global Probes",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_PROBES] as Route,
),
}}
icon={IconProp.Signal}
/>
</SideMenuSection>
<SideMenuSection title="API and Integrations">
<SideMenuItem
link={{
title: "API Key",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS_API_KEY] as Route,
),
}}
icon={IconProp.Code}
/>
</SideMenuSection>
</SideMenu>
);
};
export default DashboardSideMenu;

View File

@@ -1,222 +1,218 @@
import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import Route from 'Common/Types/API/Route';
import { ErrorFunction } from 'Common/Types/FunctionTypes';
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import Page from 'CommonUI/src/Components/Page/Page';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import API from 'CommonUI/src/Utils/API/API';
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import Navigation from 'CommonUI/src/Utils/Navigation';
import User from 'Model/Models/User';
import React, { FunctionComponent, ReactElement, useState } from 'react';
import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import { ErrorFunction } from "Common/Types/FunctionTypes";
import { ButtonStyleType } from "CommonUI/src/Components/Button/Button";
import FormFieldSchemaType from "CommonUI/src/Components/Forms/Types/FormFieldSchemaType";
import ConfirmModal from "CommonUI/src/Components/Modal/ConfirmModal";
import ModelTable from "CommonUI/src/Components/ModelTable/ModelTable";
import Page from "CommonUI/src/Components/Page/Page";
import FieldType from "CommonUI/src/Components/Types/FieldType";
import API from "CommonUI/src/Utils/API/API";
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
import Navigation from "CommonUI/src/Utils/Navigation";
import User from "Model/Models/User";
import React, { FunctionComponent, ReactElement, useState } from "react";
const Users: FunctionComponent = (): ReactElement => {
const [showConfirmVerifyEmailModal, setShowConfirmVerifyEmailModal] =
useState<boolean>(false);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [error, setError] = useState<string | null>(null);
const [showConfirmVerifyEmailModal, setShowConfirmVerifyEmailModal] =
useState<boolean>(false);
const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [error, setError] = useState<string | null>(null);
const [isConfimModalLoading, setIsConfirmModalLoading] =
useState<boolean>(false);
const [isConfimModalLoading, setIsConfirmModalLoading] =
useState<boolean>(false);
const [refreshItemsTrigger, setRefreshItemsTrigger] =
useState<boolean>(false);
const [refreshItemsTrigger, setRefreshItemsTrigger] =
useState<boolean>(false);
return (
<Page
title={'Users'}
breadcrumbLinks={[
{
title: 'Admin Dashboard',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
return (
<Page
title={"Users"}
breadcrumbLinks={[
{
title: "Admin Dashboard",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.HOME] as Route),
},
{
title: "Users",
to: RouteUtil.populateRouteParams(RouteMap[PageMap.USERS] as Route),
},
]}
>
<ModelTable<User>
modelType={User}
id="users-table"
isDeleteable={false}
isEditable={false}
showViewIdButton={true}
refreshToggle={refreshItemsTrigger}
isCreateable={true}
name="Users"
isViewable={false}
cardProps={{
title: "Users",
description: "Here is a list of users in OneUptime.",
}}
actionButtons={[
{
title: "Verify Email",
buttonStyleType: ButtonStyleType.NORMAL,
isVisible: (item: User) => {
return !item.isEmailVerified;
},
onClick: async (
item: User,
onCompleteAction: VoidFunction,
onError: ErrorFunction,
) => {
try {
setSelectedUser(item);
setShowConfirmVerifyEmailModal(true);
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
]}
noItemsMessage={"No users found."}
formFields={[
{
field: {
email: true,
},
title: "Email",
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: "email@company.com",
},
{
field: {
password: true,
},
title: "Password",
fieldType: FormFieldSchemaType.Password,
required: true,
placeholder: "Password",
},
{
field: {
name: true,
},
title: "Full Name",
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: "John Smith",
},
]}
showRefreshButton={true}
viewPageRoute={Navigation.getCurrentRoute()}
filters={[
{
field: {
name: true,
},
title: "Full Name",
type: FieldType.Text,
},
{
field: {
email: true,
},
title: "Email",
type: FieldType.Email,
},
{
field: {
createdAt: true,
},
title: "Created At",
type: FieldType.DateTime,
},
]}
columns={[
{
field: {
name: true,
},
title: "Full Name",
type: FieldType.Text,
},
{
field: {
email: true,
},
title: "Email",
type: FieldType.Email,
},
{
field: {
isEmailVerified: true,
},
title: "Email Verified",
type: FieldType.Boolean,
},
{
field: {
createdAt: true,
},
title: "Created At",
type: FieldType.DateTime,
},
]}
/>
{error ? (
<ConfirmModal
title={`Error`}
description={error}
submitButtonText={"Close"}
onSubmit={async () => {
setError(null);
}}
submitButtonType={ButtonStyleType.NORMAL}
/>
) : (
<></>
)}
{showConfirmVerifyEmailModal && selectedUser ? (
<ConfirmModal
title={`Verify Email`}
description={`Are you sure you want to verify the email - ${selectedUser.email}?`}
isLoading={isConfimModalLoading}
submitButtonText={"Verify Email"}
onClose={async () => {
setShowConfirmVerifyEmailModal(false);
setSelectedUser(null);
}}
onSubmit={async () => {
try {
setIsConfirmModalLoading(true);
await ModelAPI.updateById<User>({
modelType: User,
id: selectedUser.id!,
data: {
isEmailVerified: true,
},
{
title: 'Users',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.USERS] as Route
),
},
]}
>
<ModelTable<User>
modelType={User}
id="users-table"
isDeleteable={false}
isEditable={false}
showViewIdButton={true}
refreshToggle={refreshItemsTrigger}
isCreateable={true}
name="Users"
isViewable={false}
cardProps={{
title: 'Users',
description: 'Here is a list of users in OneUptime.',
}}
actionButtons={[
{
title: 'Verify Email',
buttonStyleType: ButtonStyleType.NORMAL,
isVisible: (item: User) => {
return !item.isEmailVerified;
},
onClick: async (
item: User,
onCompleteAction: VoidFunction,
onError: ErrorFunction
) => {
try {
setSelectedUser(item);
setShowConfirmVerifyEmailModal(true);
});
} catch (err) {
setError(API.getFriendlyMessage(err as Error));
}
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
]}
noItemsMessage={'No users found.'}
formFields={[
{
field: {
email: true,
},
title: 'Email',
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: 'email@company.com',
},
{
field: {
password: true,
},
title: 'Password',
fieldType: FormFieldSchemaType.Password,
required: true,
placeholder: 'Password',
},
{
field: {
name: true,
},
title: 'Full Name',
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: 'John Smith',
},
]}
showRefreshButton={true}
viewPageRoute={Navigation.getCurrentRoute()}
filters={[
{
field: {
name: true,
},
title: 'Full Name',
type: FieldType.Text,
},
{
field: {
email: true,
},
title: 'Email',
type: FieldType.Email,
},
{
field: {
createdAt: true,
},
title: 'Created At',
type: FieldType.DateTime,
},
]}
columns={[
{
field: {
name: true,
},
title: 'Full Name',
type: FieldType.Text,
},
{
field: {
email: true,
},
title: 'Email',
type: FieldType.Email,
},
{
field: {
isEmailVerified: true,
},
title: 'Email Verified',
type: FieldType.Boolean,
},
{
field: {
createdAt: true,
},
title: 'Created At',
type: FieldType.DateTime,
},
]}
/>
{error ? (
<ConfirmModal
title={`Error`}
description={error}
submitButtonText={'Close'}
onSubmit={async () => {
setError(null);
}}
submitButtonType={ButtonStyleType.NORMAL}
/>
) : (
<></>
)}
{showConfirmVerifyEmailModal && selectedUser ? (
<ConfirmModal
title={`Verify Email`}
description={`Are you sure you want to verify the email - ${selectedUser.email}?`}
isLoading={isConfimModalLoading}
submitButtonText={'Verify Email'}
onClose={async () => {
setShowConfirmVerifyEmailModal(false);
setSelectedUser(null);
}}
onSubmit={async () => {
try {
setIsConfirmModalLoading(true);
await ModelAPI.updateById<User>({
modelType: User,
id: selectedUser.id!,
data: {
isEmailVerified: true,
},
});
} catch (err) {
setError(API.getFriendlyMessage(err as Error));
}
setRefreshItemsTrigger(!refreshItemsTrigger);
setIsConfirmModalLoading(false);
setShowConfirmVerifyEmailModal(false);
}}
/>
) : (
<></>
)}
</Page>
);
setRefreshItemsTrigger(!refreshItemsTrigger);
setIsConfirmModalLoading(false);
setShowConfirmVerifyEmailModal(false);
}}
/>
) : (
<></>
)}
</Page>
);
};
export default Users;

View File

@@ -1,8 +1,8 @@
import Dictionary from 'Common/Types/Dictionary';
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import Dictionary from "Common/Types/Dictionary";
import ModelAPI from "CommonUI/src/Utils/ModelAPI/ModelAPI";
export default class AdminModelAPI extends ModelAPI {
public static override getCommonHeaders(): Dictionary<string> {
return {};
}
public static override getCommonHeaders(): Dictionary<string> {
return {};
}
}

View File

@@ -1,17 +1,17 @@
enum PageMap {
INIT = 'INIT',
HOME = 'HOME',
LOGOUT = 'LOGOUT',
SETTINGS = 'SETTINGS',
USERS = 'USERS',
PROJECTS = 'PROJECTS',
INIT = "INIT",
HOME = "HOME",
LOGOUT = "LOGOUT",
SETTINGS = "SETTINGS",
USERS = "USERS",
PROJECTS = "PROJECTS",
SETTINGS_HOST = 'SETTINGS_HOST',
SETTINGS_SMTP = 'SETTINGS_SMTP',
SETTINGS_CALL_AND_SMS = 'SETTINGS_CALL_AND_SMS',
SETTINGS_PROBES = 'SETTINGS_PROBES',
SETTINGS_AUTHENTICATION = 'SETTINGS_AUTHENTICATION',
SETTINGS_API_KEY = 'SETTINGS_API_KEY',
SETTINGS_HOST = "SETTINGS_HOST",
SETTINGS_SMTP = "SETTINGS_SMTP",
SETTINGS_CALL_AND_SMS = "SETTINGS_CALL_AND_SMS",
SETTINGS_PROBES = "SETTINGS_PROBES",
SETTINGS_AUTHENTICATION = "SETTINGS_AUTHENTICATION",
SETTINGS_API_KEY = "SETTINGS_API_KEY",
}
export default PageMap;

View File

@@ -1,54 +1,54 @@
import PageMap from './PageMap';
import RouteParams from './RouteParams';
import Route from 'Common/Types/API/Route';
import Dictionary from 'Common/Types/Dictionary';
import ObjectID from 'Common/Types/ObjectID';
import PageMap from "./PageMap";
import RouteParams from "./RouteParams";
import Route from "Common/Types/API/Route";
import Dictionary from "Common/Types/Dictionary";
import ObjectID from "Common/Types/ObjectID";
const RouteMap: Dictionary<Route> = {
[PageMap.INIT]: new Route(`/admin`),
[PageMap.HOME]: new Route(`/admin`),
[PageMap.LOGOUT]: new Route(`/admin/logout`),
[PageMap.SETTINGS]: new Route(`/admin/settings/host`),
[PageMap.PROJECTS]: new Route(`/admin/projects`),
[PageMap.USERS]: new Route(`/admin/users`),
[PageMap.SETTINGS_HOST]: new Route(`/admin/settings/host`),
[PageMap.SETTINGS_SMTP]: new Route(`/admin/settings/smtp`),
[PageMap.SETTINGS_CALL_AND_SMS]: new Route(`/admin/settings/call-and-sms`),
[PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`),
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
`/admin/settings/authentication`
),
[PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`),
[PageMap.INIT]: new Route(`/admin`),
[PageMap.HOME]: new Route(`/admin`),
[PageMap.LOGOUT]: new Route(`/admin/logout`),
[PageMap.SETTINGS]: new Route(`/admin/settings/host`),
[PageMap.PROJECTS]: new Route(`/admin/projects`),
[PageMap.USERS]: new Route(`/admin/users`),
[PageMap.SETTINGS_HOST]: new Route(`/admin/settings/host`),
[PageMap.SETTINGS_SMTP]: new Route(`/admin/settings/smtp`),
[PageMap.SETTINGS_CALL_AND_SMS]: new Route(`/admin/settings/call-and-sms`),
[PageMap.SETTINGS_PROBES]: new Route(`/admin/settings/probes`),
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
`/admin/settings/authentication`,
),
[PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`),
};
export class RouteUtil {
public static populateRouteParams(
route: Route,
props?: {
modelId?: ObjectID;
subModelId?: ObjectID;
}
): Route {
// populate projectid
public static populateRouteParams(
route: Route,
props?: {
modelId?: ObjectID;
subModelId?: ObjectID;
},
): Route {
// populate projectid
const tempRoute: Route = new Route(route.toString());
const tempRoute: Route = new Route(route.toString());
if (props && props.modelId) {
route = tempRoute.addRouteParam(
RouteParams.ModelID,
props.modelId.toString()
);
}
if (props && props.subModelId) {
route = tempRoute.addRouteParam(
RouteParams.SubModelID,
props.subModelId.toString()
);
}
return tempRoute;
if (props && props.modelId) {
route = tempRoute.addRouteParam(
RouteParams.ModelID,
props.modelId.toString(),
);
}
if (props && props.subModelId) {
route = tempRoute.addRouteParam(
RouteParams.SubModelID,
props.subModelId.toString(),
);
}
return tempRoute;
}
}
export default RouteMap;

View File

@@ -1,6 +1,6 @@
enum RouteParams {
ModelID = ':id',
SubModelID = ':subModelId',
ModelID = ":id",
SubModelID = ":subModelId",
}
export default RouteParams;

View File

@@ -1,82 +1,84 @@
require('ts-loader');
require('file-loader');
require('style-loader');
require('css-loader');
require('sass-loader');
require("ts-loader");
require("file-loader");
require("style-loader");
require("css-loader");
require("sass-loader");
const path = require("path");
const webpack = require("webpack");
const dotenv = require('dotenv');
const express = require('express');
const dotenv = require("dotenv");
const express = require("express");
const readEnvFile = (pathToFile) => {
const parsed = dotenv.config({ path: pathToFile }).parsed;
const parsed = dotenv.config({ path: pathToFile }).parsed;
const env = {};
const env = {};
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
}
return env;
}
return env;
};
module.exports = {
entry: "./src/Index.tsx",
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/admin/dist/",
entry: "./src/Index.tsx",
mode: "development",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "public", "dist"),
publicPath: "/admin/dist/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".css", ".scss"],
alias: {
react: path.resolve("./node_modules/react"),
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.css', '.scss'],
alias: {
react: path.resolve('./node_modules/react'),
}
},
externals: {
'react-native-sqlite-storage': 'react-native-sqlite-storage'
},
plugins: [
new webpack.DefinePlugin({
'process': {
'env': {
...readEnvFile('/usr/src/app/dev-env/.env')
}
}
}),
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader'
},
{
test: /\.s[ac]ss$/i,
use: ['style-loader', 'css-loader', "sass-loader"]
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader']
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: 'file-loader'
}
],
},
devServer: {
historyApiFallback: true,
devMiddleware: {
writeToDisk: true,
},
externals: {
"react-native-sqlite-storage": "react-native-sqlite-storage",
},
plugins: [
new webpack.DefinePlugin({
process: {
env: {
...readEnvFile("/usr/src/app/dev-env/.env"),
},
allowedHosts: "all",
setupMiddlewares: (middlewares, devServer) => {
devServer.app.use('/admin/assets', express.static(path.resolve(__dirname, 'public', 'assets')));
return middlewares;
}
},
}),
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: "ts-loader",
},
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loader: "file-loader",
},
],
},
devServer: {
historyApiFallback: true,
devMiddleware: {
writeToDisk: true,
},
devtool: 'eval-source-map',
}
allowedHosts: "all",
setupMiddlewares: (middlewares, devServer) => {
devServer.app.use(
"/admin/assets",
express.static(path.resolve(__dirname, "public", "assets")),
);
return middlewares;
},
},
devtool: "eval-source-map",
};

View File

@@ -1,89 +1,83 @@
import AuthenticationServiceHandler from './Service/Authentication';
import DataTypeServiceHandler from './Service/DataType';
import ErrorServiceHandler from './Service/Errors';
import IntroductionServiceHandler from './Service/Introduction';
import ModelServiceHandler from './Service/Model';
import PageNotFoundServiceHandler from './Service/PageNotFound';
import PaginationServiceHandler from './Service/Pagination';
import PermissionServiceHandler from './Service/Permissions';
import StatusServiceHandler from './Service/Status';
import { StaticPath } from './Utils/Config';
import ResourceUtil, { ModelDocumentation } from './Utils/Resources';
import Dictionary from 'Common/Types/Dictionary';
import FeatureSet from 'CommonServer/Types/FeatureSet';
import AuthenticationServiceHandler from "./Service/Authentication";
import DataTypeServiceHandler from "./Service/DataType";
import ErrorServiceHandler from "./Service/Errors";
import IntroductionServiceHandler from "./Service/Introduction";
import ModelServiceHandler from "./Service/Model";
import PageNotFoundServiceHandler from "./Service/PageNotFound";
import PaginationServiceHandler from "./Service/Pagination";
import PermissionServiceHandler from "./Service/Permissions";
import StatusServiceHandler from "./Service/Status";
import { StaticPath } from "./Utils/Config";
import ResourceUtil, { ModelDocumentation } from "./Utils/Resources";
import Dictionary from "Common/Types/Dictionary";
import FeatureSet from "CommonServer/Types/FeatureSet";
import Express, {
ExpressApplication,
ExpressRequest,
ExpressResponse,
ExpressStatic,
} from 'CommonServer/Utils/Express';
ExpressApplication,
ExpressRequest,
ExpressResponse,
ExpressStatic,
} from "CommonServer/Utils/Express";
const APIReferenceFeatureSet: FeatureSet = {
init: async (): Promise<void> => {
const ResourceDictionary: Dictionary<ModelDocumentation> =
ResourceUtil.getResourceDictionaryByPath();
init: async (): Promise<void> => {
const ResourceDictionary: Dictionary<ModelDocumentation> =
ResourceUtil.getResourceDictionaryByPath();
const app: ExpressApplication = Express.getExpressApp();
const app: ExpressApplication = Express.getExpressApp();
app.use('/reference', ExpressStatic(StaticPath, { maxAge: 2592000 }));
app.use("/reference", ExpressStatic(StaticPath, { maxAge: 2592000 }));
// Index page
app.get(
['/reference'],
(_req: ExpressRequest, res: ExpressResponse) => {
return res.redirect('/reference/introduction');
}
);
// Index page
app.get(["/reference"], (_req: ExpressRequest, res: ExpressResponse) => {
return res.redirect("/reference/introduction");
});
app.get(
['/reference/page-not-found'],
(req: ExpressRequest, res: ExpressResponse) => {
return PageNotFoundServiceHandler.executeResponse(req, res);
}
);
app.get(
["/reference/page-not-found"],
(req: ExpressRequest, res: ExpressResponse) => {
return PageNotFoundServiceHandler.executeResponse(req, res);
},
);
// All Pages
app.get(
['/reference/:page'],
(req: ExpressRequest, res: ExpressResponse) => {
const page: string | undefined = req.params['page'];
// All Pages
app.get(
["/reference/:page"],
(req: ExpressRequest, res: ExpressResponse) => {
const page: string | undefined = req.params["page"];
if (!page) {
return PageNotFoundServiceHandler.executeResponse(req, res);
}
if (!page) {
return PageNotFoundServiceHandler.executeResponse(req, res);
}
const currentResource: ModelDocumentation | undefined =
ResourceDictionary[page];
const currentResource: ModelDocumentation | undefined =
ResourceDictionary[page];
if (req.params['page'] === 'permissions') {
return PermissionServiceHandler.executeResponse(req, res);
} else if (req.params['page'] === 'authentication') {
return AuthenticationServiceHandler.executeResponse(
req,
res
);
} else if (req.params['page'] === 'pagination') {
return PaginationServiceHandler.executeResponse(req, res);
} else if (req.params['page'] === 'errors') {
return ErrorServiceHandler.executeResponse(req, res);
} else if (req.params['page'] === 'introduction') {
return IntroductionServiceHandler.executeResponse(req, res);
} else if (req.params['page'] === 'status') {
return StatusServiceHandler.executeResponse(req, res);
} else if (req.params['page'] === 'data-types') {
return DataTypeServiceHandler.executeResponse(req, res);
} else if (currentResource) {
return ModelServiceHandler.executeResponse(req, res);
}
// page not found
return PageNotFoundServiceHandler.executeResponse(req, res);
}
);
if (req.params["page"] === "permissions") {
return PermissionServiceHandler.executeResponse(req, res);
} else if (req.params["page"] === "authentication") {
return AuthenticationServiceHandler.executeResponse(req, res);
} else if (req.params["page"] === "pagination") {
return PaginationServiceHandler.executeResponse(req, res);
} else if (req.params["page"] === "errors") {
return ErrorServiceHandler.executeResponse(req, res);
} else if (req.params["page"] === "introduction") {
return IntroductionServiceHandler.executeResponse(req, res);
} else if (req.params["page"] === "status") {
return StatusServiceHandler.executeResponse(req, res);
} else if (req.params["page"] === "data-types") {
return DataTypeServiceHandler.executeResponse(req, res);
} else if (currentResource) {
return ModelServiceHandler.executeResponse(req, res);
}
// page not found
return PageNotFoundServiceHandler.executeResponse(req, res);
},
);
app.get('/reference/*', (req: ExpressRequest, res: ExpressResponse) => {
return PageNotFoundServiceHandler.executeResponse(req, res);
});
},
app.get("/reference/*", (req: ExpressRequest, res: ExpressResponse) => {
return PageNotFoundServiceHandler.executeResponse(req, res);
});
},
};
export default APIReferenceFeatureSet;

View File

@@ -1,29 +1,28 @@
import { ViewsPath } from '../Utils/Config';
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
import { ViewsPath } from "../Utils/Config";
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
export default class ServiceHandler {
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
let pageTitle: string = '';
let pageDescription: string = '';
const page: string | undefined = req.params['page'];
const pageData: any = {};
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse,
): Promise<void> {
let pageTitle: string = "";
let pageDescription: string = "";
const page: string | undefined = req.params["page"];
const pageData: any = {};
pageTitle = 'Authentication';
pageDescription =
'Learn how to authenticate requests with OneUptime API';
pageTitle = "Authentication";
pageDescription = "Learn how to authenticate requests with OneUptime API";
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
}

View File

@@ -1,136 +1,126 @@
import { CodeExamplesPath, ViewsPath } from '../Utils/Config';
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
import LocalFile from 'CommonServer/Utils/LocalFile';
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
import LocalCache from "CommonServer/Infrastructure/LocalCache";
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
import LocalFile from "CommonServer/Utils/LocalFile";
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
export default class ServiceHandler {
public static async executeResponse(
_req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
const pageData: any = {};
public static async executeResponse(
_req: ExpressRequest,
res: ExpressResponse,
): Promise<void> {
const pageData: any = {};
pageData.selectCode = await LocalCache.getOrSetString(
'data-type',
'select',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/Select.md`
);
}
pageData.selectCode = await LocalCache.getOrSetString(
"data-type",
"select",
async () => {
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/Select.md`);
},
);
pageData.sortCode = await LocalCache.getOrSetString(
"data-type",
"sort",
async () => {
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/Sort.md`);
},
);
pageData.equalToCode = await LocalCache.getOrSetString(
"data-type",
"equal-to",
async () => {
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/EqualTo.md`);
},
);
pageData.equalToOrNullCode = await LocalCache.getOrSetString(
"data-type",
"equal-to-or-null",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/EqualToOrNull.md`,
);
},
);
pageData.sortCode = await LocalCache.getOrSetString(
'data-type',
'sort',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/Sort.md`
);
}
pageData.greaterThanCode = await LocalCache.getOrSetString(
"data-type",
"greater-than",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/GreaterThan.md`,
);
},
);
pageData.equalToCode = await LocalCache.getOrSetString(
'data-type',
'equal-to',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/EqualTo.md`
);
}
pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString(
"data-type",
"greater-than-or-equal",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/GreaterThanOrEqual.md`,
);
},
);
pageData.equalToOrNullCode = await LocalCache.getOrSetString(
'data-type',
'equal-to-or-null',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/EqualToOrNull.md`
);
}
pageData.lessThanCode = await LocalCache.getOrSetString(
"data-type",
"less-than",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/LessThan.md`,
);
},
);
pageData.greaterThanCode = await LocalCache.getOrSetString(
'data-type',
'greater-than',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/GreaterThan.md`
);
}
pageData.lessThanOrEqualCode = await LocalCache.getOrSetString(
"data-type",
"less-than-or-equal",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/LessThanOrEqual.md`,
);
},
);
pageData.greaterThanOrEqualCode = await LocalCache.getOrSetString(
'data-type',
'greater-than-or-equal',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/GreaterThanOrEqual.md`
);
}
pageData.isNullCode = await LocalCache.getOrSetString(
"data-type",
"is-null",
async () => {
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/IsNull.md`);
},
);
pageData.notNullCode = await LocalCache.getOrSetString(
"data-type",
"not-null",
async () => {
return await LocalFile.read(`${CodeExamplesPath}/DataTypes/NotNull.md`);
},
);
pageData.notEqualToCode = await LocalCache.getOrSetString(
"data-type",
"not-equals",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/NotEqualTo.md`,
);
},
);
pageData.lessThanCode = await LocalCache.getOrSetString(
'data-type',
'less-than',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/LessThan.md`
);
}
);
pageData.lessThanOrEqualCode = await LocalCache.getOrSetString(
'data-type',
'less-than-or-equal',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/LessThanOrEqual.md`
);
}
);
pageData.isNullCode = await LocalCache.getOrSetString(
'data-type',
'is-null',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/IsNull.md`
);
}
);
pageData.notNullCode = await LocalCache.getOrSetString(
'data-type',
'not-null',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/NotNull.md`
);
}
);
pageData.notEqualToCode = await LocalCache.getOrSetString(
'data-type',
'not-equals',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/DataTypes/NotEqualTo.md`
);
}
);
res.status(200);
return res.render(`${ViewsPath}/pages/index`, {
page: 'data-types',
pageTitle: 'Data Types',
pageDescription:
'Data Types that can be used to interact with OneUptime API',
resources: Resources,
pageData: pageData,
});
}
res.status(200);
return res.render(`${ViewsPath}/pages/index`, {
page: "data-types",
pageTitle: "Data Types",
pageDescription:
"Data Types that can be used to interact with OneUptime API",
resources: Resources,
pageData: pageData,
});
}
}

View File

@@ -1,28 +1,28 @@
import { ViewsPath } from '../Utils/Config';
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
import { ViewsPath } from "../Utils/Config";
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
export default class ServiceHandler {
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
let pageTitle: string = '';
let pageDescription: string = '';
const page: string | undefined = req.params['page'];
const pageData: any = {};
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse,
): Promise<void> {
let pageTitle: string = "";
let pageDescription: string = "";
const page: string | undefined = req.params["page"];
const pageData: any = {};
pageTitle = 'Errors';
pageDescription = 'Learn more about how we return errors from API';
pageTitle = "Errors";
pageDescription = "Learn more about how we return errors from API";
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
}

View File

@@ -1,31 +1,31 @@
import { ViewsPath } from '../Utils/Config';
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
import { ViewsPath } from "../Utils/Config";
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
const FeaturedResources: Array<ModelDocumentation> =
ResourceUtil.getFeaturedResources();
ResourceUtil.getFeaturedResources();
export default class ServiceHandler {
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
let pageTitle: string = '';
let pageDescription: string = '';
const page: string | undefined = req.params['page'];
const pageData: any = {};
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse,
): Promise<void> {
let pageTitle: string = "";
let pageDescription: string = "";
const page: string | undefined = req.params["page"];
const pageData: any = {};
pageData.featuredResources = FeaturedResources;
pageTitle = 'Introduction';
pageDescription = 'API Reference for OneUptime';
pageData.featuredResources = FeaturedResources;
pageTitle = "Introduction";
pageDescription = "API Reference for OneUptime";
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
}

View File

@@ -1,244 +1,238 @@
import { CodeExamplesPath, ViewsPath } from '../Utils/Config';
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
import PageNotFoundServiceHandler from './PageNotFound';
import { AppApiRoute } from 'Common/ServiceRoute';
import { ColumnAccessControl } from 'Common/Types/BaseDatabase/AccessControl';
import { getTableColumns } from 'Common/Types/Database/TableColumn';
import Dictionary from 'Common/Types/Dictionary';
import ObjectID from 'Common/Types/ObjectID';
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
import PageNotFoundServiceHandler from "./PageNotFound";
import { AppApiRoute } from "Common/ServiceRoute";
import { ColumnAccessControl } from "Common/Types/BaseDatabase/AccessControl";
import { getTableColumns } from "Common/Types/Database/TableColumn";
import Dictionary from "Common/Types/Dictionary";
import ObjectID from "Common/Types/ObjectID";
import Permission, {
PermissionHelper,
PermissionProps,
} from 'Common/Types/Permission';
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
import LocalFile from 'CommonServer/Utils/LocalFile';
PermissionHelper,
PermissionProps,
} from "Common/Types/Permission";
import LocalCache from "CommonServer/Infrastructure/LocalCache";
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
import LocalFile from "CommonServer/Utils/LocalFile";
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
const ResourceDictionary: Dictionary<ModelDocumentation> =
ResourceUtil.getResourceDictionaryByPath();
ResourceUtil.getResourceDictionaryByPath();
const PermissionDictionary: Dictionary<PermissionProps> =
PermissionHelper.getAllPermissionPropsAsDictionary();
PermissionHelper.getAllPermissionPropsAsDictionary();
export default class ServiceHandler {
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
let pageTitle: string = '';
let pageDescription: string = '';
let page: string | undefined = req.params['page'];
const pageData: any = {};
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse,
): Promise<void> {
let pageTitle: string = "";
let pageDescription: string = "";
let page: string | undefined = req.params["page"];
const pageData: any = {};
if (!page) {
return PageNotFoundServiceHandler.executeResponse(req, res);
}
const currentResource: ModelDocumentation | undefined =
ResourceDictionary[page];
if (!currentResource) {
return PageNotFoundServiceHandler.executeResponse(req, res);
}
// Resource Page.
pageTitle = currentResource.name;
pageDescription = currentResource.description;
page = 'model';
const tableColumns: any = getTableColumns(currentResource.model);
for (const key in tableColumns) {
const accessControl: ColumnAccessControl | null =
currentResource.model.getColumnAccessControlFor(key);
if (!accessControl) {
// remove columns with no access
delete tableColumns[key];
continue;
}
if (
accessControl?.create.length === 0 &&
accessControl?.read.length === 0 &&
accessControl?.update.length === 0
) {
// remove columns with no access
delete tableColumns[key];
continue;
}
tableColumns[key].permissions = accessControl;
}
delete tableColumns['deletedAt'];
delete tableColumns['deletedByUserId'];
delete tableColumns['deletedByUser'];
delete tableColumns['version'];
pageData.title = currentResource.model.singularName;
pageData.description = currentResource.model.tableDescription;
pageData.columns = tableColumns;
pageData.tablePermissions = {
read: currentResource.model.readRecordPermissions.map(
(permission: Permission) => {
return PermissionDictionary[permission];
}
),
update: currentResource.model.updateRecordPermissions.map(
(permission: Permission) => {
return PermissionDictionary[permission];
}
),
delete: currentResource.model.deleteRecordPermissions.map(
(permission: Permission) => {
return PermissionDictionary[permission];
}
),
create: currentResource.model.createRecordPermissions.map(
(permission: Permission) => {
return PermissionDictionary[permission];
}
),
};
pageData.listRequest = await LocalCache.getOrSetString(
'model',
'list-request',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/ListRequest.md`
);
}
);
pageData.itemRequest = await LocalCache.getOrSetString(
'model',
'item-request',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/ItemRequest.md`
);
}
);
pageData.itemResponse = await LocalCache.getOrSetString(
'model',
'item-response',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/ItemResponse.md`
);
}
);
pageData.countRequest = await LocalCache.getOrSetString(
'model',
'count-request',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/CountRequest.md`
);
}
);
pageData.countResponse = await LocalCache.getOrSetString(
'model',
'count-response',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/CountResponse.md`
);
}
);
pageData.updateRequest = await LocalCache.getOrSetString(
'model',
'update-request',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/UpdateRequest.md`
);
}
);
pageData.updateResponse = await LocalCache.getOrSetString(
'model',
'update-response',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/UpdateResponse.md`
);
}
);
pageData.createRequest = await LocalCache.getOrSetString(
'model',
'create-request',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/CreateRequest.md`
);
}
);
pageData.createResponse = await LocalCache.getOrSetString(
'model',
'create-response',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/CreateResponse.md`
);
}
);
pageData.deleteRequest = await LocalCache.getOrSetString(
'model',
'delete-request',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/DeleteRequest.md`
);
}
);
pageData.deleteResponse = await LocalCache.getOrSetString(
'model',
'delete-response',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/DeleteResponse.md`
);
}
);
pageData.listResponse = await LocalCache.getOrSetString(
'model',
'list-response',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/ListResponse.md`
);
}
);
pageData.exampleObjectID = ObjectID.generate();
pageData.apiPath =
AppApiRoute.toString() +
currentResource.model.crudApiPath?.toString();
pageData.isMasterAdminApiDocs =
currentResource.model.isMasterAdminApiDocs;
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
if (!page) {
return PageNotFoundServiceHandler.executeResponse(req, res);
}
const currentResource: ModelDocumentation | undefined =
ResourceDictionary[page];
if (!currentResource) {
return PageNotFoundServiceHandler.executeResponse(req, res);
}
// Resource Page.
pageTitle = currentResource.name;
pageDescription = currentResource.description;
page = "model";
const tableColumns: any = getTableColumns(currentResource.model);
for (const key in tableColumns) {
const accessControl: ColumnAccessControl | null =
currentResource.model.getColumnAccessControlFor(key);
if (!accessControl) {
// remove columns with no access
delete tableColumns[key];
continue;
}
if (
accessControl?.create.length === 0 &&
accessControl?.read.length === 0 &&
accessControl?.update.length === 0
) {
// remove columns with no access
delete tableColumns[key];
continue;
}
tableColumns[key].permissions = accessControl;
}
delete tableColumns["deletedAt"];
delete tableColumns["deletedByUserId"];
delete tableColumns["deletedByUser"];
delete tableColumns["version"];
pageData.title = currentResource.model.singularName;
pageData.description = currentResource.model.tableDescription;
pageData.columns = tableColumns;
pageData.tablePermissions = {
read: currentResource.model.readRecordPermissions.map(
(permission: Permission) => {
return PermissionDictionary[permission];
},
),
update: currentResource.model.updateRecordPermissions.map(
(permission: Permission) => {
return PermissionDictionary[permission];
},
),
delete: currentResource.model.deleteRecordPermissions.map(
(permission: Permission) => {
return PermissionDictionary[permission];
},
),
create: currentResource.model.createRecordPermissions.map(
(permission: Permission) => {
return PermissionDictionary[permission];
},
),
};
pageData.listRequest = await LocalCache.getOrSetString(
"model",
"list-request",
async () => {
return await LocalFile.read(`${CodeExamplesPath}/Model/ListRequest.md`);
},
);
pageData.itemRequest = await LocalCache.getOrSetString(
"model",
"item-request",
async () => {
return await LocalFile.read(`${CodeExamplesPath}/Model/ItemRequest.md`);
},
);
pageData.itemResponse = await LocalCache.getOrSetString(
"model",
"item-response",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/ItemResponse.md`,
);
},
);
pageData.countRequest = await LocalCache.getOrSetString(
"model",
"count-request",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/CountRequest.md`,
);
},
);
pageData.countResponse = await LocalCache.getOrSetString(
"model",
"count-response",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/CountResponse.md`,
);
},
);
pageData.updateRequest = await LocalCache.getOrSetString(
"model",
"update-request",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/UpdateRequest.md`,
);
},
);
pageData.updateResponse = await LocalCache.getOrSetString(
"model",
"update-response",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/UpdateResponse.md`,
);
},
);
pageData.createRequest = await LocalCache.getOrSetString(
"model",
"create-request",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/CreateRequest.md`,
);
},
);
pageData.createResponse = await LocalCache.getOrSetString(
"model",
"create-response",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/CreateResponse.md`,
);
},
);
pageData.deleteRequest = await LocalCache.getOrSetString(
"model",
"delete-request",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/DeleteRequest.md`,
);
},
);
pageData.deleteResponse = await LocalCache.getOrSetString(
"model",
"delete-response",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/DeleteResponse.md`,
);
},
);
pageData.listResponse = await LocalCache.getOrSetString(
"model",
"list-response",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Model/ListResponse.md`,
);
},
);
pageData.exampleObjectID = ObjectID.generate();
pageData.apiPath =
AppApiRoute.toString() + currentResource.model.crudApiPath?.toString();
pageData.isMasterAdminApiDocs = currentResource.model.isMasterAdminApiDocs;
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
}

View File

@@ -1,21 +1,21 @@
import { ViewsPath } from '../Utils/Config';
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
import { ViewsPath } from "../Utils/Config";
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
export default class ServiceHandler {
public static async executeResponse(
_req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
res.status(404);
return res.render(`${ViewsPath}/pages/index`, {
page: '404',
pageTitle: 'Page Not Found',
pageDescription: "Page you're looking for is not found.",
resources: Resources,
pageData: {},
});
}
public static async executeResponse(
_req: ExpressRequest,
res: ExpressResponse,
): Promise<void> {
res.status(404);
return res.render(`${ViewsPath}/pages/index`, {
page: "404",
pageTitle: "Page Not Found",
pageDescription: "Page you're looking for is not found.",
resources: Resources,
pageData: {},
});
}
}

View File

@@ -1,50 +1,50 @@
import { CodeExamplesPath, ViewsPath } from '../Utils/Config';
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
import LocalFile from 'CommonServer/Utils/LocalFile';
import { CodeExamplesPath, ViewsPath } from "../Utils/Config";
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
import LocalCache from "CommonServer/Infrastructure/LocalCache";
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
import LocalFile from "CommonServer/Utils/LocalFile";
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
export default class ServiceHandler {
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
let pageTitle: string = '';
let pageDescription: string = '';
const page: string | undefined = req.params['page'];
const pageData: any = {};
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse,
): Promise<void> {
let pageTitle: string = "";
let pageDescription: string = "";
const page: string | undefined = req.params["page"];
const pageData: any = {};
pageTitle = 'Pagination';
pageDescription = 'Learn how to paginate requests with OneUptime API';
pageTitle = "Pagination";
pageDescription = "Learn how to paginate requests with OneUptime API";
pageData.responseCode = await LocalCache.getOrSetString(
'pagination',
'response',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Pagination/Response.md`
);
}
pageData.responseCode = await LocalCache.getOrSetString(
"pagination",
"response",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Pagination/Response.md`,
);
},
);
pageData.requestCode = await LocalCache.getOrSetString(
'pagination',
'request',
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Pagination/Request.md`
);
}
pageData.requestCode = await LocalCache.getOrSetString(
"pagination",
"request",
async () => {
return await LocalFile.read(
`${CodeExamplesPath}/Pagination/Request.md`,
);
},
);
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
}

View File

@@ -1,35 +1,35 @@
import { ViewsPath } from '../Utils/Config';
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
import { PermissionHelper, PermissionProps } from 'Common/Types/Permission';
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
import { ViewsPath } from "../Utils/Config";
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
import { PermissionHelper, PermissionProps } from "Common/Types/Permission";
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
export default class ServiceHandler {
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
let pageTitle: string = '';
let pageDescription: string = '';
const page: string | undefined = req.params['page'];
const pageData: any = {};
public static async executeResponse(
req: ExpressRequest,
res: ExpressResponse,
): Promise<void> {
let pageTitle: string = "";
let pageDescription: string = "";
const page: string | undefined = req.params["page"];
const pageData: any = {};
pageTitle = 'Permissions';
pageDescription = 'Learn how permissions work with OneUptime';
pageTitle = "Permissions";
pageDescription = "Learn how permissions work with OneUptime";
pageData.permissions = PermissionHelper.getAllPermissionProps().filter(
(i: PermissionProps) => {
return i.isAssignableToTenant;
}
);
pageData.permissions = PermissionHelper.getAllPermissionProps().filter(
(i: PermissionProps) => {
return i.isAssignableToTenant;
},
);
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
return res.render(`${ViewsPath}/pages/index`, {
page: page,
resources: Resources,
pageTitle: pageTitle,
pageDescription: pageDescription,
pageData: pageData,
});
}
}

View File

@@ -1,21 +1,21 @@
import { ViewsPath } from '../Utils/Config';
import ResourceUtil, { ModelDocumentation } from '../Utils/Resources';
import { ExpressRequest, ExpressResponse } from 'CommonServer/Utils/Express';
import { ViewsPath } from "../Utils/Config";
import ResourceUtil, { ModelDocumentation } from "../Utils/Resources";
import { ExpressRequest, ExpressResponse } from "CommonServer/Utils/Express";
const Resources: Array<ModelDocumentation> = ResourceUtil.getResources();
export default class ServiceHandler {
public static async executeResponse(
_req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
res.status(200);
return res.render(`${ViewsPath}/pages/index`, {
page: 'status',
pageTitle: 'Status',
pageDescription: '200 - Success',
resources: Resources,
pageData: {},
});
}
public static async executeResponse(
_req: ExpressRequest,
res: ExpressResponse,
): Promise<void> {
res.status(200);
return res.render(`${ViewsPath}/pages/index`, {
page: "status",
pageTitle: "Status",
pageDescription: "200 - Success",
resources: Resources,
pageData: {},
});
}
}

View File

@@ -1,4 +1,4 @@
export const ViewsPath: string = '/usr/src/app/FeatureSet/ApiReference/views';
export const StaticPath: string = '/usr/src/app/FeatureSet/ApiReference/Static';
export const ViewsPath: string = "/usr/src/app/FeatureSet/ApiReference/views";
export const StaticPath: string = "/usr/src/app/FeatureSet/ApiReference/Static";
export const CodeExamplesPath: string =
'/usr/src/app/FeatureSet/ApiReference/CodeExamples';
"/usr/src/app/FeatureSet/ApiReference/CodeExamples";

View File

@@ -1,74 +1,73 @@
import BaseModel from 'Common/Models/BaseModel';
import ArrayUtil from 'Common/Types/ArrayUtil';
import Dictionary from 'Common/Types/Dictionary';
import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig';
import Models from 'Model/Models/Index';
import BaseModel from "Common/Models/BaseModel";
import ArrayUtil from "Common/Types/ArrayUtil";
import Dictionary from "Common/Types/Dictionary";
import { IsBillingEnabled } from "CommonServer/EnvironmentConfig";
import Models from "Model/Models/Index";
export interface ModelDocumentation {
name: string;
path: string;
model: BaseModel;
description: string;
name: string;
path: string;
model: BaseModel;
description: string;
}
export default class ResourceUtil {
public static getResources(): Array<ModelDocumentation> {
const resources: Array<ModelDocumentation> = Models.filter(
(model: typeof BaseModel) => {
const modelInstance: BaseModel = new model();
let showDocs: boolean = modelInstance.enableDocumentation;
public static getResources(): Array<ModelDocumentation> {
const resources: Array<ModelDocumentation> = Models.filter(
(model: typeof BaseModel) => {
const modelInstance: BaseModel = new model();
let showDocs: boolean = modelInstance.enableDocumentation;
if (modelInstance.isMasterAdminApiDocs && IsBillingEnabled) {
showDocs = false;
}
return showDocs;
}
)
.map((model: typeof BaseModel) => {
const modelInstance: BaseModel = new model();
return {
name: modelInstance.singularName!,
path: modelInstance.getAPIDocumentationPath(),
model: modelInstance,
description: modelInstance.tableDescription!,
};
})
.sort(ArrayUtil.sortByFieldName('name'));
return resources;
}
public static getFeaturedResources(): Array<ModelDocumentation> {
const featuredResources: Array<string> = [
'Monitor',
'Scheduled Maintenance Event',
'Status Page',
'Incident',
'Team',
'On-Call Duty',
'Label',
'Team Member',
];
return ResourceUtil.getResources().filter(
(resource: ModelDocumentation) => {
return featuredResources.includes(resource.name);
}
);
}
public static getResourceDictionaryByPath(): Dictionary<ModelDocumentation> {
const dict: Dictionary<ModelDocumentation> = {};
const resources: Array<ModelDocumentation> =
ResourceUtil.getResources();
for (const resource of resources) {
dict[resource.path] = resource;
if (modelInstance.isMasterAdminApiDocs && IsBillingEnabled) {
showDocs = false;
}
return dict;
return showDocs;
},
)
.map((model: typeof BaseModel) => {
const modelInstance: BaseModel = new model();
return {
name: modelInstance.singularName!,
path: modelInstance.getAPIDocumentationPath(),
model: modelInstance,
description: modelInstance.tableDescription!,
};
})
.sort(ArrayUtil.sortByFieldName("name"));
return resources;
}
public static getFeaturedResources(): Array<ModelDocumentation> {
const featuredResources: Array<string> = [
"Monitor",
"Scheduled Maintenance Event",
"Status Page",
"Incident",
"Team",
"On-Call Duty",
"Label",
"Team Member",
];
return ResourceUtil.getResources().filter(
(resource: ModelDocumentation) => {
return featuredResources.includes(resource.name);
},
);
}
public static getResourceDictionaryByPath(): Dictionary<ModelDocumentation> {
const dict: Dictionary<ModelDocumentation> = {};
const resources: Array<ModelDocumentation> = ResourceUtil.getResources();
for (const resource of resources) {
dict[resource.path] = resource;
}
return dict;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,6 @@ We use OpenTelemetry to collect application logs. OneUptime currently supports l
- [JavaScript / Typescript / NodeJS / Browser](https://opentelemetry.io/docs/instrumentation/js/)
- [Python](https://opentelemetry.io/docs/instrumentation/python/)
- [Ruby](https://opentelemetry.io/docs/instrumentation/ruby/)
- [Swift](https://opentelemetry.io/docs/instrumentation/swift/)
- [PHP](https://opentelemetry.io/docs/instrumentation/php/)
- [Erlang](https://opentelemetry.io/docs/instrumentation/erlang/)
- [Rust](https://opentelemetry.io/docs/instrumentation/rust/)

View File

@@ -1,93 +1,85 @@
import { ContentPath, StaticPath, ViewsPath } from './Utils/Config';
import DocsNav, { NavGroup, NavLink } from './Utils/Nav';
import DocsRender from './Utils/Render';
import FeatureSet from 'CommonServer/Types/FeatureSet';
import { ContentPath, StaticPath, ViewsPath } from "./Utils/Config";
import DocsNav, { NavGroup, NavLink } from "./Utils/Nav";
import DocsRender from "./Utils/Render";
import FeatureSet from "CommonServer/Types/FeatureSet";
import Express, {
ExpressApplication,
ExpressRequest,
ExpressResponse,
ExpressStatic,
} from 'CommonServer/Utils/Express';
import LocalFile from 'CommonServer/Utils/LocalFile';
import logger from 'CommonServer/Utils/Logger';
import 'ejs';
ExpressApplication,
ExpressRequest,
ExpressResponse,
ExpressStatic,
} from "CommonServer/Utils/Express";
import LocalFile from "CommonServer/Utils/LocalFile";
import logger from "CommonServer/Utils/Logger";
import "ejs";
const DocsFeatureSet: FeatureSet = {
init: async (): Promise<void> => {
const app: ExpressApplication = Express.getExpressApp();
init: async (): Promise<void> => {
const app: ExpressApplication = Express.getExpressApp();
app.get('/docs', (_req: ExpressRequest, res: ExpressResponse) => {
res.redirect('/docs/introduction/getting-started');
});
app.get("/docs", (_req: ExpressRequest, res: ExpressResponse) => {
res.redirect("/docs/introduction/getting-started");
});
app.get(
'/docs/:categorypath/:pagepath',
async (_req: ExpressRequest, res: ExpressResponse) => {
try {
const fullPath: string =
`${_req.params['categorypath']}/${_req.params['pagepath']}`.toLowerCase();
app.get(
"/docs/:categorypath/:pagepath",
async (_req: ExpressRequest, res: ExpressResponse) => {
try {
const fullPath: string =
`${_req.params["categorypath"]}/${_req.params["pagepath"]}`.toLowerCase();
// read file from Content folder.
let contentInMarkdown: string = await LocalFile.read(
`${ContentPath}/${fullPath}.md`
);
// read file from Content folder.
let contentInMarkdown: string = await LocalFile.read(
`${ContentPath}/${fullPath}.md`,
);
// remove first line from content because we dont want to show title in content. Title is already in nav.
// remove first line from content because we dont want to show title in content. Title is already in nav.
contentInMarkdown = contentInMarkdown
.split('\n')
.slice(1)
.join('\n');
contentInMarkdown = contentInMarkdown.split("\n").slice(1).join("\n");
const renderedContent: string = await DocsRender.render(
contentInMarkdown
);
const renderedContent: string =
await DocsRender.render(contentInMarkdown);
const currentCategory: NavGroup | undefined = DocsNav.find(
(category: NavGroup) => {
return category.links.find((link: NavLink) => {
return link.url
.toLocaleLowerCase()
.includes(fullPath);
});
}
);
const currentCategory: NavGroup | undefined = DocsNav.find(
(category: NavGroup) => {
return category.links.find((link: NavLink) => {
return link.url.toLocaleLowerCase().includes(fullPath);
});
},
);
const currrentNavLink: NavLink | undefined =
currentCategory?.links.find((link: NavLink) => {
return link.url
.toLocaleLowerCase()
.includes(fullPath);
});
const currrentNavLink: NavLink | undefined =
currentCategory?.links.find((link: NavLink) => {
return link.url.toLocaleLowerCase().includes(fullPath);
});
if (!currentCategory || !currrentNavLink) {
// render not found.
if (!currentCategory || !currrentNavLink) {
// render not found.
res.status(404);
return res.render(`${ViewsPath}/NotFound`, {
nav: DocsNav,
});
}
res.status(404);
return res.render(`${ViewsPath}/NotFound`, {
nav: DocsNav,
});
}
res.render(`${ViewsPath}/Index`, {
nav: DocsNav,
content: renderedContent,
category: currentCategory,
link: currrentNavLink,
githubPath: fullPath,
});
} catch (err) {
logger.error(err);
res.status(500);
return res.render(`${ViewsPath}/ServerError`, {
nav: DocsNav,
});
}
}
);
res.render(`${ViewsPath}/Index`, {
nav: DocsNav,
content: renderedContent,
category: currentCategory,
link: currrentNavLink,
githubPath: fullPath,
});
} catch (err) {
logger.error(err);
res.status(500);
return res.render(`${ViewsPath}/ServerError`, {
nav: DocsNav,
});
}
},
);
app.use('/docs/static', ExpressStatic(StaticPath));
},
app.use("/docs/static", ExpressStatic(StaticPath));
},
};
export default DocsFeatureSet;

View File

@@ -1,3 +1,3 @@
export const ViewsPath: string = '/usr/src/app/FeatureSet/Docs/Views';
export const StaticPath: string = '/usr/src/app/FeatureSet/Docs/Static';
export const ContentPath: string = '/usr/src/app/FeatureSet/Docs/Content';
export const ViewsPath: string = "/usr/src/app/FeatureSet/Docs/Views";
export const StaticPath: string = "/usr/src/app/FeatureSet/Docs/Static";
export const ContentPath: string = "/usr/src/app/FeatureSet/Docs/Content";

View File

@@ -1,75 +1,75 @@
export interface NavLink {
title: string;
url: string;
title: string;
url: string;
}
export interface NavGroup {
title: string;
links: NavLink[];
title: string;
links: NavLink[];
}
const DocsNav: NavGroup[] = [
{
title: 'Introduction',
links: [
{
title: 'Getting Started',
url: '/docs/introduction/getting-started',
},
],
},
{
title: 'Installation',
links: [
{
title: 'Local Development',
url: '/docs/installation/local-development',
},
{
title: 'Docker Compose',
url: '/docs/installation/docker-compose',
},
{
title: 'Kubernetes and Helm',
url: 'https://artifacthub.io/packages/helm/oneuptime/oneuptime',
},
],
},
{
title: 'Monitor',
links: [
{
title: 'Custom Code Monitor',
url: '/docs/monitor/custom-code-monitor',
},
{
title: 'Synthetic Monitor',
url: '/docs/monitor/synthetic-monitor',
},
{
title: 'JavaScript Expressions',
url: '/docs/monitor/javascript-expression',
},
{
title: 'Monitor Secrets',
url: '/docs/monitor/monitor-secrets',
},
],
},
{
title: 'Probe',
links: [
{ title: 'Custom Probes', url: '/docs/probe/custom-probe' },
{ title: 'IP Addresses', url: '/docs/probe/ip-address' },
],
},
{
title: 'Telemetry',
links: [
{ title: 'OpenTelemetry', url: '/docs/telemetry/open-telemetry' },
{ title: 'Fluentd', url: '/docs/telemetry/fluentd' },
],
},
{
title: "Introduction",
links: [
{
title: "Getting Started",
url: "/docs/introduction/getting-started",
},
],
},
{
title: "Installation",
links: [
{
title: "Local Development",
url: "/docs/installation/local-development",
},
{
title: "Docker Compose",
url: "/docs/installation/docker-compose",
},
{
title: "Kubernetes and Helm",
url: "https://artifacthub.io/packages/helm/oneuptime/oneuptime",
},
],
},
{
title: "Monitor",
links: [
{
title: "Custom Code Monitor",
url: "/docs/monitor/custom-code-monitor",
},
{
title: "Synthetic Monitor",
url: "/docs/monitor/synthetic-monitor",
},
{
title: "JavaScript Expressions",
url: "/docs/monitor/javascript-expression",
},
{
title: "Monitor Secrets",
url: "/docs/monitor/monitor-secrets",
},
],
},
{
title: "Probe",
links: [
{ title: "Custom Probes", url: "/docs/probe/custom-probe" },
{ title: "IP Addresses", url: "/docs/probe/ip-address" },
],
},
{
title: "Telemetry",
links: [
{ title: "OpenTelemetry", url: "/docs/telemetry/open-telemetry" },
{ title: "Fluentd", url: "/docs/telemetry/fluentd" },
],
},
];
export default DocsNav;

View File

@@ -1,10 +1,7 @@
import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown';
import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown";
export default class DocsRender {
public static async render(markdownContent: string): Promise<string> {
return Markdown.convertToHTML(
markdownContent,
MarkdownContentType.Docs
);
}
public static async render(markdownContent: string): Promise<string> {
return Markdown.convertToHTML(markdownContent, MarkdownContentType.Docs);
}
}

View File

@@ -1,89 +1,88 @@
import BlogPostUtil, { BlogPost, BlogPostHeader } from '../Utils/BlogPost';
import { ViewsPath } from '../Utils/Config';
import NotFoundUtil from '../Utils/NotFound';
import ServerErrorUtil from '../Utils/ServerError';
import Text from 'Common/Types/Text';
import BlogPostUtil, { BlogPost, BlogPostHeader } from "../Utils/BlogPost";
import { ViewsPath } from "../Utils/Config";
import NotFoundUtil from "../Utils/NotFound";
import ServerErrorUtil from "../Utils/ServerError";
import Text from "Common/Types/Text";
import Express, {
ExpressApplication,
ExpressRequest,
ExpressResponse,
} from 'CommonServer/Utils/Express';
import logger from 'CommonServer/Utils/Logger';
ExpressApplication,
ExpressRequest,
ExpressResponse,
} from "CommonServer/Utils/Express";
import logger from "CommonServer/Utils/Logger";
const app: ExpressApplication = Express.getExpressApp();
app.get(
'/blog/post/:file',
async (req: ExpressRequest, res: ExpressResponse) => {
try {
const fileName: string = req.params['file'] as string;
"/blog/post/:file",
async (req: ExpressRequest, res: ExpressResponse) => {
try {
const fileName: string = req.params["file"] as string;
const blogPost: BlogPost | null = await BlogPostUtil.getBlogPost(
fileName
);
const blogPost: BlogPost | null =
await BlogPostUtil.getBlogPost(fileName);
if (!blogPost) {
return NotFoundUtil.renderNotFound(res);
}
if (!blogPost) {
return NotFoundUtil.renderNotFound(res);
}
res.render(`${ViewsPath}/Blog/Post`, {
support: false,
footerCards: true,
cta: true,
blackLogo: false,
requestDemoCta: false,
blogPost: blogPost,
});
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
}
res.render(`${ViewsPath}/Blog/Post`, {
support: false,
footerCards: true,
cta: true,
blackLogo: false,
requestDemoCta: false,
blogPost: blogPost,
});
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
}
},
);
// List all blog posts with tag
app.get(
'/blog/tag/:tagName',
async (req: ExpressRequest, res: ExpressResponse) => {
try {
const tagName: string = req.params['tagName'] as string;
"/blog/tag/:tagName",
async (req: ExpressRequest, res: ExpressResponse) => {
try {
const tagName: string = req.params["tagName"] as string;
const blogPosts: Array<BlogPostHeader> =
await BlogPostUtil.getBlogPostList(tagName);
const blogPosts: Array<BlogPostHeader> =
await BlogPostUtil.getBlogPostList(tagName);
res.render(`${ViewsPath}/Blog/ListByTag`, {
support: false,
footerCards: true,
cta: true,
blackLogo: false,
requestDemoCta: false,
blogPosts: blogPosts,
tagName: Text.fromDashesToPascalCase(tagName),
});
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
}
res.render(`${ViewsPath}/Blog/ListByTag`, {
support: false,
footerCards: true,
cta: true,
blackLogo: false,
requestDemoCta: false,
blogPosts: blogPosts,
tagName: Text.fromDashesToPascalCase(tagName),
});
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
}
},
);
// main blog page
app.get('/blog', async (_req: ExpressRequest, res: ExpressResponse) => {
try {
const blogPosts: Array<BlogPostHeader> =
await BlogPostUtil.getBlogPostList();
app.get("/blog", async (_req: ExpressRequest, res: ExpressResponse) => {
try {
const blogPosts: Array<BlogPostHeader> =
await BlogPostUtil.getBlogPostList();
res.render(`${ViewsPath}/Blog/List`, {
support: false,
footerCards: true,
cta: true,
blackLogo: false,
requestDemoCta: false,
blogPosts: blogPosts,
});
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
}
res.render(`${ViewsPath}/Blog/List`, {
support: false,
footerCards: true,
cta: true,
blackLogo: false,
requestDemoCta: false,
blogPosts: blogPosts,
});
} catch (e) {
logger.error(e);
return ServerErrorUtil.renderServerError(res);
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="302" height="135" viewBox="0 0 302 135" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M225.829 109.243C225.862 105.573 225.426 101.97 223.866 98.6024C221.081 92.5075 215.51 91.2447 211.014 92.8274C208.028 93.8713 206.149 96.1779 204.84 98.9559C202.894 103.098 202.508 107.509 202.894 112.021C203.179 115.203 203.967 118.234 205.662 120.961C206.954 123.032 208.682 124.649 211.031 125.44C216.232 127.208 221.366 124.901 223.782 119.716C225.325 116.399 225.795 112.863 225.795 109.243M202.004 127.932C202.004 129.178 202.021 131.164 202.004 132.41C201.971 133.825 201.619 134.195 200.31 134.212C198.364 134.246 196.417 134.246 194.471 134.212C193.246 134.195 192.961 133.892 192.844 132.68C192.81 132.242 192.827 131.804 192.827 131.367V67.0336C192.827 64.2219 192.861 64.1882 195.595 64.1882C197.323 64.1882 199.052 64.1714 200.78 64.1882C202.474 64.2219 202.726 64.4576 202.726 66.2086C202.726 73.2295 202.726 80.2504 202.726 87.2713V89.056C203.951 88.0121 204.89 87.0524 205.981 86.2948C211.886 82.1698 218.883 82.4392 224.352 85.2677C228.882 87.6922 232.355 92.1876 234.067 97.4238C236.801 105.825 236.768 114.26 233.312 122.477C230.828 128.386 226.701 132.73 220.359 134.313C213.615 135.997 207.608 134.582 202.894 129.127C202.407 128.555 202.155 127.831 202.004 127.915" fill="#a5b4fc"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M151.135 109.344C151.252 112.964 151.47 116.601 153.014 119.985C154.071 122.275 155.564 124.211 157.98 125.103C161.084 126.518 165.882 126.265 169.12 123.15C170.261 122.039 171.301 120.709 172.023 119.278C174.539 114.227 174.942 108.873 173.868 103.384C173.315 100.572 172.09 98.0636 170.194 95.858C165.932 90.908 156.587 90.4534 153.165 98.3161C151.621 101.852 151.269 105.539 151.135 109.344ZM174.841 89.2412C174.841 88.231 174.841 87.2208 174.841 86.2274C174.858 84.3922 175.093 84.1397 176.905 84.1228C178.851 84.106 180.797 84.0892 182.744 84.1228C184.153 84.1565 184.421 84.4596 184.438 85.8907C184.472 87.7427 184.438 131.602 184.438 131.602C184.438 134.094 184.321 134.195 181.905 134.212C180.227 134.212 178.549 134.229 176.871 134.212C175.613 134.178 175.345 133.959 175.227 132.697C175.093 131.367 175.076 129.733 174.992 128.386C174.992 128.386 174.422 128.942 174.019 129.363C171.1 132.73 167.409 134.515 163.013 134.885C153.651 135.677 146.923 130.39 143.635 122.359C141.387 116.904 140.967 111.179 141.471 105.371C141.856 100.993 142.947 96.8008 145.128 92.9621C147.93 88.0626 151.873 84.5943 157.493 83.5167C163.6 82.3381 169.087 83.6514 173.516 88.332C173.835 88.6687 174.17 88.9886 174.489 89.3254C174.489 89.3254 174.741 89.6284 174.791 89.6116C174.808 89.6116 174.791 89.2412 174.791 89.2412" fill="#a5b4fc"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M74.6115 80.2504L81.8427 87.0692L66.0046 104.209C66.7093 105.438 77.145 123.47 81.8931 131.451C83.0339 133.37 82.5977 134.38 80.2488 134.246C78.1516 134.111 76.0376 134.145 73.9237 134.246C72.6821 134.296 71.9607 133.841 71.3399 132.764C67.6656 126.164 59.1761 111.348 59.1761 111.348C59.1761 111.348 53.5388 117.729 52.2134 119.177C51.5926 119.85 51.3074 120.574 51.3241 121.517C51.3745 124.884 51.3577 132.511 51.3074 132.949C51.2235 133.774 50.7537 134.229 49.9148 134.229C47.5324 134.229 45.1667 134.229 42.7843 134.229C41.9454 134.229 41.4756 133.791 41.4085 132.949V66.7811C41.3918 64.2724 41.4756 64.1882 43.9084 64.1882C45.8043 64.1882 47.7002 64.1545 49.5793 64.1882C50.955 64.2219 51.257 64.5586 51.3241 65.9561C51.3409 66.3938 51.3241 104.781 51.3241 105.354L74.6115 80.2335V80.2504Z" fill="#a5b4fc"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M270.675 104.142H292.436C292.218 100.875 291.53 97.8784 289.416 95.4371C286.514 92.0866 282.722 91.6657 278.729 92.6254C274.249 93.7029 270.625 99.0064 270.692 104.142M270.407 112.998C271.011 117.342 272.185 121.197 275.759 123.891C277.789 125.423 280.121 126.012 282.604 125.979C285.977 125.945 289.114 125.036 291.849 122.982C292.067 122.813 292.302 122.679 292.637 122.46C295.003 124.177 297.402 125.911 299.902 127.73C297.302 130.323 294.517 132.293 291.211 133.471C286.178 135.239 281.027 135.525 275.91 134.06C268.813 132.023 264.25 127.124 262.001 120.187C259.334 111.954 259.451 103.653 262.907 95.6391C265.793 88.9718 270.81 84.6616 278.041 83.4494C283.98 82.4392 289.584 83.4157 294.366 87.406C298.426 90.7901 300.506 95.3024 301.462 100.438C302.134 104.041 302.066 107.694 301.848 111.331C301.764 112.661 301.462 112.93 300.187 112.981C299.801 112.981 271.128 112.981 270.424 112.981" fill="#a5b4fc"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.523 90.4702C33.8754 89.881 33.1371 89.1402 32.9861 89.0055C26.7281 83.9713 19.5976 82.0688 11.6785 83.6851C7.0143 84.6279 3.22255 87.6417 1.36023 91.7667C-0.602759 96.3968 -0.585981 100.976 2.36689 105.253C3.89366 107.458 6.05798 108.957 8.47396 109.95C12.3999 111.482 21.4767 114.648 23.7416 115.759C24.7147 116.18 25.4697 116.904 25.8389 117.931C26.812 120.473 26.5267 124.514 22.2652 125.743C19.866 126.433 17.45 126.316 15.034 125.895C11.3262 125.255 9.12829 124.362 6.25931 121.921C5.90698 121.618 5.25265 121.601 4.93387 121.921C4.56476 122.292 0.135458 127.141 0.135458 127.141C-0.23365 127.915 0.252902 128.37 1.17567 129.195C1.89711 129.834 4.4641 131.602 5.60498 132.141C11.3597 134.852 17.3997 135.609 23.6242 134.498C30.2346 133.32 34.731 129.38 35.6035 122.426C36.1236 118.234 35.5699 114.26 32.6003 110.994C30.8554 109.075 28.6911 107.711 26.3086 106.802C23.2886 105.64 14.7656 102.946 12.3328 101.633C10.5879 100.875 9.69873 99.5284 9.71551 97.5417C9.71551 95.4371 10.5208 93.8207 12.3664 92.8611C13.1717 92.457 14.0945 92.1876 14.9837 92.0697C19.262 91.5478 23.1209 92.6759 26.5771 95.2014C26.812 95.3697 27.0301 95.5381 27.265 95.7233C27.7683 96.1105 28.4729 96.0432 28.9092 95.5886C28.9092 95.5886 33.3217 90.8407 33.523 90.5039" fill="#a5b4fc"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M242.975 99.0906V66.7811C242.975 64.3229 243.11 64.1882 245.542 64.1882C247.438 64.1882 249.317 64.1545 251.213 64.1882C252.539 64.2219 252.824 64.5249 252.874 65.8214C252.891 66.1413 252.874 110.001 252.874 131.585C252.874 134.094 252.774 134.195 250.358 134.195C248.462 134.195 246.583 134.212 244.687 134.195C243.412 134.178 243.059 133.808 242.992 132.545C242.959 132.107 242.992 109.816 242.992 99.0906" fill="#a5b4fc"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M124.089 134.178H134.005V62.3362H124.089V134.178Z" fill="#a5b4fc"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M105.717 134.178H115.616V82.3381H105.717V134.178Z" fill="#a5b4fc"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M87.3458 134.178H97.2279V96.9019H87.3458V134.178Z" fill="#a5b4fc"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M146.705 0L90.834 63.307L83.1227 56.057L72 68.585L90.748 87L158 11.136L146.705 0Z" fill="#a5b4fc"></path>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -1,47 +0,0 @@
(function (e, t) {
var n = e.amplitude || { _q: [], _iq: {} }; var r = t.createElement("script")
; r.type = "text/javascript"
; r.integrity = "sha384-vYYnQ3LPdp/RkQjoKBTGSq0X5F73gXU3G2QopHaIfna0Ct1JRWzwrmEz115NzOta"
; r.crossOrigin = "anonymous"; r.async = true
; r.src = "https://cdn.amplitude.com/libs/amplitude-5.8.0-min.gz.js"
; r.onload = function () {
if (!e.amplitude.runQueuedFunctions) {
console.log("[Amplitude] Error: could not load SDK")
}
}
; var i = t.getElementsByTagName("script")[0]; i.parentNode.insertBefore(r, i)
; function s(e, t) {
e.prototype[t] = function () {
this._q.push([t].concat(Array.prototype.slice.call(arguments, 0))); return this
}
}
var o = function () { this._q = []; return this }
; var a = ["add", "append", "clearAll", "prepend", "set", "setOnce", "unset"]
; for (var u = 0; u < a.length; u++) { s(o, a[u]) } n.Identify = o; var c = function () {
this._q = []
; return this
}
; var l = ["setProductId", "setQuantity", "setPrice", "setRevenueType", "setEventProperties"]
; for (var p = 0; p < l.length; p++) { s(c, l[p]) } n.Revenue = c
; var d = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "enableTracking", "setGlobalUserProperties", "identify", "clearUserProperties", "setGroup", "logRevenueV2", "regenerateDeviceId", "groupIdentify", "onInit", "logEventWithTimestamp", "logEventWithGroups", "setSessionId", "resetSessionId"]
; function v(e) {
function t(t) {
e[t] = function () {
e._q.push([t].concat(Array.prototype.slice.call(arguments, 0)))
}
}
for (var n = 0; n < d.length; n++) { t(d[n]) }
} v(n); n.getInstance = function (e) {
e = (!e || e.length === 0 ? "$default_instance" : e).toLowerCase()
; if (!n._iq.hasOwnProperty(e)) { n._iq[e] = { _q: [] }; v(n._iq[e]) } return n._iq[e]
}
; e.amplitude = n
})(window, document);
amplitude.getInstance().init("802d95003af23aad17ed068b6cfdeb2b", null, {
// include referrer information in amplitude.
saveEvents: true,
includeUtm: true,
includeReferrer: true,
includeGclid: true
});

View File

@@ -1,94 +1,95 @@
function openTab(evt, tabName) {
// Declare all variables
let i;
// Declare all variables
let i;
// Get all elements with class="tabcontent" and hide them
const tabcontent = document.getElementsByClassName('tabcontent');
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].className = tabcontent[i].className.replace(' active', '');
}
// Get all elements with class="tabcontent" and hide them
const tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].className = tabcontent[i].className.replace(" active", "");
}
// Get all elements with class="tablinks" and remove the class "active"
const tablinks = document.getElementsByClassName('tablinks');
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(' active', '');
}
// Get all elements with class="tablinks" and remove the class "active"
const tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
// Show the current tab, and add an "active" class to the link that opened the tab
// Show the current tab, and add an "active" class to the link that opened the tab
document.getElementById(tabName).className += ' active';
evt.currentTarget.className += ' active';
document.getElementById(tabName).className += " active";
evt.currentTarget.className += " active";
setTimeout(() => document.getElementById(tabName + '1').parentNode.click(), 200);
setTimeout(
() => document.getElementById(tabName + "1").parentNode.click(),
200,
);
}
function openTooltip(name) {
// Declare all variables
let i;
const element = document.getElementById(name);
// Declare all variables
let i;
const element = document.getElementById(name);
const elclass = element.className;
const elclass = element.className;
const tooltip = document.getElementsByClassName('tooltiptext');
for (i = 0; i < tooltip.length; i++) {
tooltip[i].className = tooltip[i].className.replace(' active', '');
}
if (elclass.indexOf('active') > -1) {
element.className = element.className.replace(' active', '');
}
else {
element.classList.add('active');
}
const tooltip = document.getElementsByClassName("tooltiptext");
for (i = 0; i < tooltip.length; i++) {
tooltip[i].className = tooltip[i].className.replace(" active", "");
}
if (elclass.indexOf("active") > -1) {
element.className = element.className.replace(" active", "");
} else {
element.classList.add("active");
}
}
window.onload = function () {
animateHTML().init();
const tooltext = document.getElementsByClassName('tooltiptext');
for (let i = 0; i < tooltext.length; i++) {
animateHTML().init();
const tooltext = document.getElementsByClassName("tooltiptext");
for (let i = 0; i < tooltext.length; i++) {
tooltext[i].onclick = function (e) {
e.stopPropagation();
};
}
tooltext[i].onclick = function (e) {
e.stopPropagation();
}
document.getElementsByTagName("body")[0].onclick = function (e) {
if (
e.target.className !== "popover-dot" &&
e.target.className !== "tooltiptext" &&
e.target.className !== "tablinks active"
) {
const tooltip = document.getElementsByClassName("tooltiptext");
for (let i = 0; i < tooltip.length; i++) {
tooltip[i].className = tooltip[i].className.replace(" active", "");
}
}
document.getElementsByTagName('body')[0].onclick = function (e) {
if (e.target.className !== 'popover-dot' && e.target.className !== 'tooltiptext' && e.target.className !== 'tablinks active') {
const tooltip = document.getElementsByClassName('tooltiptext');
for (let i = 0; i < tooltip.length; i++) {
tooltip[i].className = tooltip[i].className.replace(' active', '');
}
}
}
}
};
};
const animateHTML = function () {
let elem, windowHeight;
const init = function () {
elem = document.getElementById('Statuspage');
windowHeight = window.innerHeight;
_addEventHandlers();
let elem, windowHeight;
const init = function () {
elem = document.getElementById("Statuspage");
windowHeight = window.innerHeight;
_addEventHandlers();
};
const _addEventHandlers = function () {
window.addEventListener("scroll", _checkPosition);
window.addEventListener("resize", init);
};
const _checkPosition = function () {
if (!elem) {
return;
}
const _addEventHandlers = function () {
window.addEventListener('scroll', _checkPosition)
window.addEventListener('resize', init)
}
const _checkPosition = function () {
if (!elem) {
return;
}
const posFromTop = elem.getBoundingClientRect().top;
const posFromTop = elem.getBoundingClientRect().top;
if (posFromTop - windowHeight <= -400) {
document.getElementById('Statuspage1').parentNode.click();
window.removeEventListener('scroll', _checkPosition);
window.removeEventListener('resize', init);
return;
}
if (posFromTop - windowHeight <= -400) {
document.getElementById("Statuspage1").parentNode.click();
window.removeEventListener("scroll", _checkPosition);
window.removeEventListener("resize", init);
return;
}
return {
init: init
}
}
};
return {
init: init,
};
};

View File

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

View File

@@ -1,69 +1,49 @@
!(function () {
function n(n, e) {
$(".hidden", n)
.eq(e)
.css({
transitionDelay: Math.random() + Math.random() + "s",
transitionDuration: 2 * Math.random() + 0.2 + "s",
}),
$(".hidden", n).eq(e).attr("class", "shown");
}
! function () {
function n(n, e) {
$('.hidden', n)
.eq(e)
.css({
transitionDelay: Math.random() + Math.random() + 's',
transitionDuration: 2 * Math.random() + .2 + 's'
}), $('.hidden', n)
.eq(e)
.attr('class', 'shown')
function e(n, e) {
if (n.hasClass("is-visible")) {
const a = $(".shown", n).eq(e);
a.attr("class", "hidden"),
setTimeout(function () {
a.attr("class", "shown");
}, 3e3);
}
function e(n, e) {
if (n.hasClass('is-visible')) {
const a = $('.shown', n)
.eq(e);
a.attr('class', 'hidden'), setTimeout(function () {
a.attr('class', 'shown')
}, 3e3)
}
}
$(".card").each(function (e, a) {
if (window.IntersectionObserver)
(a.observer = new IntersectionObserver((e) => {
e.forEach((e) => {
if (e.isIntersecting || e.intersectionRatio > 0) {
$(a).addClass("is-visible");
for (let t = $(".hidden", a).length; t >= 0; t--) n(a, t);
} else $(a).removeClass("is-visible");
});
})),
a.observer.observe(a);
else {
$(a).addClass("is-visible");
for (let t = $(".hidden", a).length; t >= 0; t--) n(a, t);
}
$('.card')
.each(function (e, a) {
if (window.IntersectionObserver) a.observer = new IntersectionObserver(e => {
e.forEach(e => {
if (e.isIntersecting || e.intersectionRatio > 0) {
$(a)
.addClass('is-visible');
for (let t = $('.hidden', a)
.length; t >= 0; t--) n(a, t)
} else $(a)
.removeClass('is-visible')
})
}), a.observer.observe(a);
else {
$(a)
.addClass('is-visible');
for (let t = $('.hidden', a)
.length; t >= 0; t--) n(a, t)
}
}), setInterval(function () {
let n = $('.card')
.eq(Math.floor(Math.random() * $('.card')
.length));
e(n, Math.floor(Math.random() * $('.shown', n)
.length));
n = $('.card')
.eq(Math.floor(Math.random() * $('.card')
.length));
e(n, Math.floor(Math.random() * $('.shown', n)
.length))
}, 600)
}();
}),
setInterval(function () {
let n = $(".card").eq(Math.floor(Math.random() * $(".card").length));
e(n, Math.floor(Math.random() * $(".shown", n).length));
n = $(".card").eq(Math.floor(Math.random() * $(".card").length));
e(n, Math.floor(Math.random() * $(".shown", n).length));
}, 600);
})();

View File

@@ -1,27 +1,26 @@
let accountsUrl = window.location.origin+'/accounts';
let backendUrl = window.location.hostname==='localhost'? 'http://localhost:3002': window.location.origin+'/api'
let accountsUrl = window.location.origin + "/accounts";
let backendUrl =
window.location.hostname === "localhost"
? "http://localhost:3002"
: window.location.origin + "/api";
//eslint-disable-next-line
function loginUrl(extra) {
if (extra) {
window.location.href = `${accountsUrl}/login${extra}`;
}
else {
window.location.href = `${accountsUrl}/login`;
}
if (extra) {
window.location.href = `${accountsUrl}/login${extra}`;
} else {
window.location.href = `${accountsUrl}/login`;
}
}
//eslint-disable-next-line
function registerUrl(params) {
if (params) {
window.location.href = `${accountsUrl}/register${params}`;
}
else {
window.location.href = `${accountsUrl}/register`;
}
if (params) {
window.location.href = `${accountsUrl}/register${params}`;
} else {
window.location.href = `${accountsUrl}/register`;
}
}
//eslint-disable-next-line
function formUrl() {
return `${backendUrl}/lead/`;
return `${backendUrl}/lead/`;
}

View File

@@ -1,3 +1,3 @@
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
test("two plus two is four", () => {
expect(2 + 2).toBe(4);
});

View File

@@ -1,453 +1,447 @@
import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel';
import BaseModel from 'Common/Models/BaseModel';
import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import URL from 'Common/Types/API/URL';
import OneUptimeDate from 'Common/Types/Date';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { JSONArray, JSONObject, JSONObjectOrArray } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Text from 'Common/Types/Text';
import API from 'Common/Utils/API';
import LocalCache from 'CommonServer/Infrastructure/LocalCache';
import Markdown, { MarkdownContentType } from 'CommonServer/Types/Markdown';
import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel";
import BaseModel from "Common/Models/BaseModel";
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
import HTTPResponse from "Common/Types/API/HTTPResponse";
import URL from "Common/Types/API/URL";
import OneUptimeDate from "Common/Types/Date";
import BadDataException from "Common/Types/Exception/BadDataException";
import { JSONArray, JSONObject, JSONObjectOrArray } from "Common/Types/JSON";
import JSONFunctions from "Common/Types/JSONFunctions";
import Text from "Common/Types/Text";
import API from "Common/Utils/API";
import LocalCache from "CommonServer/Infrastructure/LocalCache";
import Markdown, { MarkdownContentType } from "CommonServer/Types/Markdown";
export interface BlogPostAuthor {
username: string;
githubUrl: string;
profileImageUrl: string;
name: string;
username: string;
githubUrl: string;
profileImageUrl: string;
name: string;
}
export interface BlogPostBaseProps {
title: string;
description: string;
title: string;
description: string;
formattedPostDate: string;
fileName: string;
tags: string[];
postDate: string;
blogUrl: string;
formattedPostDate: string;
fileName: string;
tags: string[];
postDate: string;
blogUrl: string;
}
export interface BlogPostHeader extends BlogPostBaseProps {
authorGitHubUsername: string;
authorGitHubUsername: string;
}
export interface BlogPost extends BlogPostBaseProps {
htmlBody: string;
markdownBody: string;
socialMediaImageUrl: string;
author: BlogPostAuthor | null;
htmlBody: string;
markdownBody: string;
socialMediaImageUrl: string;
author: BlogPostAuthor | null;
}
const GitHubRawUrl: string =
'https://raw.githubusercontent.com/oneuptime/blog/master';
"https://raw.githubusercontent.com/oneuptime/blog/master";
export default class BlogPostUtil {
public static async getBlogPostList(
tagName?: string | undefined
): Promise<BlogPostHeader[]> {
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/Blogs.json`);
public static async getBlogPostList(
tagName?: string | undefined,
): Promise<BlogPostHeader[]> {
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/Blogs.json`);
const fileData:
| HTTPResponse<
| JSONObjectOrArray
| BaseModel
| BaseModel[]
| AnalyticsBaseModel
| AnalyticsBaseModel[]
>
| HTTPErrorResponse = await API.get(fileUrl);
const fileData:
| HTTPResponse<
| JSONObjectOrArray
| BaseModel
| BaseModel[]
| AnalyticsBaseModel
| AnalyticsBaseModel[]
>
| HTTPErrorResponse = await API.get(fileUrl);
if (fileData.isFailure()) {
throw fileData as HTTPErrorResponse;
}
let jsonContent: string | JSONArray =
(fileData.data as string | JSONArray) || [];
if (typeof jsonContent === 'string') {
jsonContent = JSONFunctions.parseJSONArray(jsonContent);
}
const blogs: Array<JSONObject> = JSONFunctions.deserializeArray(
jsonContent as Array<JSONObject>
).reverse(); // reverse so new content comes first
const resultList: Array<BlogPostHeader> = [];
for (const blog of blogs) {
const fileName: string = blog['post'] as string;
const formattedPostDate: string =
this.getFormattedPostDateFromFileName(fileName);
const postDate: string = this.getPostDateFromFileName(fileName);
resultList.push({
title: blog['title'] as string,
description: blog['description'] as string,
fileName,
formattedPostDate,
postDate,
tags: blog['tags'] as string[],
authorGitHubUsername: blog['authorGitHubUsername'] as string,
blogUrl: `/blog/post/${fileName}`,
});
}
if (tagName) {
return resultList.filter((blog: BlogPostHeader) => {
return blog.tags
.map((item: string) => {
return Text.replaceAll(item.toLowerCase(), ' ', '-');
})
.includes(tagName);
});
}
return resultList;
if (fileData.isFailure()) {
throw fileData as HTTPErrorResponse;
}
public static async getBlogPost(
fileName: string
): Promise<BlogPost | null> {
let blogPost: BlogPost | null = this.getBlogPostFromCache(fileName);
let jsonContent: string | JSONArray =
(fileData.data as string | JSONArray) || [];
// if (blogPost) {
// return Promise.resolve(blogPost);
// }
blogPost = await this.getBlogPostFromGitHub(fileName);
// save this to cache
LocalCache.setJSON(
'blog',
fileName,
JSONFunctions.serialize(blogPost as any)
);
return blogPost;
if (typeof jsonContent === "string") {
jsonContent = JSONFunctions.parseJSONArray(jsonContent);
}
public static async getNameOfGitHubUser(username: string): Promise<string> {
const fileUrl: URL = URL.fromString(
`https://api.github.com/users/${username}`
);
const blogs: Array<JSONObject> = JSONFunctions.deserializeArray(
jsonContent as Array<JSONObject>,
).reverse(); // reverse so new content comes first
const fileData:
| HTTPResponse<
| JSONObjectOrArray
| BaseModel
| BaseModel[]
| AnalyticsBaseModel
| AnalyticsBaseModel[]
>
| HTTPErrorResponse = await API.get(fileUrl);
const resultList: Array<BlogPostHeader> = [];
if (fileData.isFailure()) {
throw fileData as HTTPErrorResponse;
}
for (const blog of blogs) {
const fileName: string = blog["post"] as string;
const formattedPostDate: string =
this.getFormattedPostDateFromFileName(fileName);
const postDate: string = this.getPostDateFromFileName(fileName);
const name: string =
(fileData.data as JSONObject)?.['name']?.toString() || '';
return name;
resultList.push({
title: blog["title"] as string,
description: blog["description"] as string,
fileName,
formattedPostDate,
postDate,
tags: blog["tags"] as string[],
authorGitHubUsername: blog["authorGitHubUsername"] as string,
blogUrl: `/blog/post/${fileName}`,
});
}
public static async getGitHubMarkdownFileContent(
githubPath: string
): Promise<string | null> {
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/${githubPath}`);
const fileData:
| HTTPResponse<
| JSONObjectOrArray
| BaseModel
| BaseModel[]
| AnalyticsBaseModel
| AnalyticsBaseModel[]
>
| HTTPErrorResponse = await API.get(fileUrl);
if (fileData.isFailure()) {
if ((fileData as HTTPErrorResponse).statusCode === 404) {
return null;
}
throw fileData as HTTPErrorResponse;
}
const markdownContent: string =
(fileData.data as JSONObject)?.['data']?.toString() || '';
return markdownContent;
if (tagName) {
return resultList.filter((blog: BlogPostHeader) => {
return blog.tags
.map((item: string) => {
return Text.replaceAll(item.toLowerCase(), " ", "-");
})
.includes(tagName);
});
}
public static async getTags(): Promise<string[]> {
// check if tags are in cache
let tags: string[] = LocalCache.getJSON(
'blog-tags',
'tags'
) as string[];
return resultList;
}
if (tags && tags.length > 0) {
return tags;
}
public static async getBlogPost(fileName: string): Promise<BlogPost | null> {
let blogPost: BlogPost | null = this.getBlogPostFromCache(fileName);
tags = await this.getAllTagsFromGitHub();
// if (blogPost) {
// return Promise.resolve(blogPost);
// }
// save this to cache
blogPost = await this.getBlogPostFromGitHub(fileName);
LocalCache.setJSON(
'blog-tags',
'tags',
JSONFunctions.serialize(tags as any)
);
// save this to cache
LocalCache.setJSON(
"blog",
fileName,
JSONFunctions.serialize(blogPost as any),
);
return tags;
return blogPost;
}
public static async getNameOfGitHubUser(username: string): Promise<string> {
const fileUrl: URL = URL.fromString(
`https://api.github.com/users/${username}`,
);
const fileData:
| HTTPResponse<
| JSONObjectOrArray
| BaseModel
| BaseModel[]
| AnalyticsBaseModel
| AnalyticsBaseModel[]
>
| HTTPErrorResponse = await API.get(fileUrl);
if (fileData.isFailure()) {
throw fileData as HTTPErrorResponse;
}
public static async getAllTagsFromGitHub(): Promise<string[]> {
const tagsMarkdownContent: string | null =
await this.getGitHubMarkdownFileContent('Tags.md');
const name: string =
(fileData.data as JSONObject)?.["name"]?.toString() || "";
return name;
}
if (!tagsMarkdownContent) {
return [];
}
public static async getGitHubMarkdownFileContent(
githubPath: string,
): Promise<string | null> {
const fileUrl: URL = URL.fromString(`${GitHubRawUrl}/${githubPath}`);
const tags: Array<string> = tagsMarkdownContent
.split('\n')
.map((tag: string) => {
return tag.trim();
})
.filter((tag: string) => {
return tag.startsWith('-');
})
.map((tag: string) => {
return tag.replace('-', '').trim();
});
const fileData:
| HTTPResponse<
| JSONObjectOrArray
| BaseModel
| BaseModel[]
| AnalyticsBaseModel
| AnalyticsBaseModel[]
>
| HTTPErrorResponse = await API.get(fileUrl);
return tags;
if (fileData.isFailure()) {
if ((fileData as HTTPErrorResponse).statusCode === 404) {
return null;
}
throw fileData as HTTPErrorResponse;
}
public static async getBlogPostFromGitHub(
fileName: string
): Promise<BlogPost | null> {
const fileUrl: URL = URL.fromString(
`${GitHubRawUrl}/posts/${fileName}/README.md`
);
const markdownContent: string =
(fileData.data as JSONObject)?.["data"]?.toString() || "";
return markdownContent;
}
const postDate: string = this.getPostDateFromFileName(fileName);
const formattedPostDate: string =
this.getFormattedPostDateFromFileName(fileName);
public static async getTags(): Promise<string[]> {
// check if tags are in cache
let tags: string[] = LocalCache.getJSON("blog-tags", "tags") as string[];
const fileData:
| HTTPResponse<
| JSONObjectOrArray
| BaseModel
| BaseModel[]
| AnalyticsBaseModel
| AnalyticsBaseModel[]
>
| HTTPErrorResponse = await API.get(fileUrl);
if (fileData.isFailure()) {
if ((fileData as HTTPErrorResponse).statusCode === 404) {
return null;
}
throw fileData as HTTPErrorResponse;
}
let markdownContent: string =
(fileData.data as JSONObject)?.['data']?.toString() || '';
const blogPostAuthor: BlogPostAuthor | null =
await this.getAuthorFromFileContent(markdownContent);
const title: string = this.getTitleFromFileContent(markdownContent);
const description: string =
this.getDescriptionFromFileContent(markdownContent);
const tags: Array<string> =
this.getTagsFromFileContent(markdownContent);
markdownContent = this.getPostFromMarkdown(markdownContent);
const htmlBody: string = await Markdown.convertToHTML(
markdownContent,
MarkdownContentType.Blog
);
const blogPost: BlogPost = {
title,
description,
author: blogPostAuthor,
htmlBody,
markdownBody: markdownContent,
fileName,
tags,
postDate,
formattedPostDate,
socialMediaImageUrl: `${GitHubRawUrl}/posts/${fileName}/social-media.png`,
blogUrl: `https://oneuptime.com/blog/post/${fileName}`, // this has to be oneuptime.com because its used in twitter cards and faceboomk cards. Please dont change this.
};
return blogPost;
if (tags && tags.length > 0) {
return tags;
}
private static getPostDateFromFileName(fileName: string): string {
const year: string | undefined = fileName.split('-')[0];
const month: string | undefined = fileName.split('-')[1];
const day: string | undefined = fileName.split('-')[2];
tags = await this.getAllTagsFromGitHub();
if (!year || !month || !day) {
throw new BadDataException('Invalid file name');
}
// save this to cache
return `${year}-${month}-${day}`;
LocalCache.setJSON(
"blog-tags",
"tags",
JSONFunctions.serialize(tags as any),
);
return tags;
}
public static async getAllTagsFromGitHub(): Promise<string[]> {
const tagsMarkdownContent: string | null =
await this.getGitHubMarkdownFileContent("Tags.md");
if (!tagsMarkdownContent) {
return [];
}
private static getFormattedPostDateFromFileName(fileName: string): string {
// file name is of the format YYYY-MM-DD-Title.md
const year: string | undefined = fileName.split('-')[0];
const month: string | undefined = fileName.split('-')[1];
const day: string | undefined = fileName.split('-')[2];
const tags: Array<string> = tagsMarkdownContent
.split("\n")
.map((tag: string) => {
return tag.trim();
})
.filter((tag: string) => {
return tag.startsWith("-");
})
.map((tag: string) => {
return tag.replace("-", "").trim();
});
if (!year || !month || !day) {
throw new BadDataException('Invalid file name');
}
return tags;
}
const date: Date = OneUptimeDate.getDateFromYYYYMMDD(year, month, day);
return OneUptimeDate.getDateAsLocalFormattedString(date, true);
public static async getBlogPostFromGitHub(
fileName: string,
): Promise<BlogPost | null> {
const fileUrl: URL = URL.fromString(
`${GitHubRawUrl}/posts/${fileName}/README.md`,
);
const postDate: string = this.getPostDateFromFileName(fileName);
const formattedPostDate: string =
this.getFormattedPostDateFromFileName(fileName);
const fileData:
| HTTPResponse<
| JSONObjectOrArray
| BaseModel
| BaseModel[]
| AnalyticsBaseModel
| AnalyticsBaseModel[]
>
| HTTPErrorResponse = await API.get(fileUrl);
if (fileData.isFailure()) {
if ((fileData as HTTPErrorResponse).statusCode === 404) {
return null;
}
throw fileData as HTTPErrorResponse;
}
private static getPostFromMarkdown(markdownContent: string): string {
const authorLine: string | undefined = markdownContent
.split('\n')
.find((line: string) => {
return line.startsWith('Author:');
});
const titleLine: string | undefined = markdownContent
.split('\n')
.find((line: string) => {
return line.startsWith('#');
});
const descriptionLine: string | undefined =
markdownContent.split('\n').find((line: string) => {
return line.startsWith('Description:');
}) || '';
let markdownContent: string =
(fileData.data as JSONObject)?.["data"]?.toString() || "";
const tagsLine: string | undefined =
markdownContent.split('\n').find((line: string) => {
return line.startsWith('Tags:');
}) || '';
const blogPostAuthor: BlogPostAuthor | null =
await this.getAuthorFromFileContent(markdownContent);
if (!authorLine && !titleLine && !descriptionLine && !tagsLine) {
return markdownContent;
}
const title: string = this.getTitleFromFileContent(markdownContent);
const description: string =
this.getDescriptionFromFileContent(markdownContent);
const tags: Array<string> = this.getTagsFromFileContent(markdownContent);
const lines: string[] = markdownContent.split('\n');
markdownContent = this.getPostFromMarkdown(markdownContent);
if (authorLine) {
const authorLineIndex: number = lines.indexOf(authorLine);
lines.splice(authorLineIndex, 1);
}
const htmlBody: string = await Markdown.convertToHTML(
markdownContent,
MarkdownContentType.Blog,
);
if (titleLine) {
const titleLineIndex: number = lines.indexOf(titleLine);
lines.splice(titleLineIndex, 1);
}
const blogPost: BlogPost = {
title,
description,
author: blogPostAuthor,
htmlBody,
markdownBody: markdownContent,
fileName,
tags,
postDate,
formattedPostDate,
socialMediaImageUrl: `${GitHubRawUrl}/posts/${fileName}/social-media.png`,
blogUrl: `https://oneuptime.com/blog/post/${fileName}`, // this has to be oneuptime.com because its used in twitter cards and faceboomk cards. Please dont change this.
};
if (descriptionLine) {
const descriptionLineIndex: number = lines.indexOf(descriptionLine);
lines.splice(descriptionLineIndex, 1);
}
return blogPost;
}
if (tagsLine) {
const tagsLineIndex: number = lines.indexOf(tagsLine);
lines.splice(tagsLineIndex, 1);
}
private static getPostDateFromFileName(fileName: string): string {
const year: string | undefined = fileName.split("-")[0];
const month: string | undefined = fileName.split("-")[1];
const day: string | undefined = fileName.split("-")[2];
return lines.join('\n').trim();
if (!year || !month || !day) {
throw new BadDataException("Invalid file name");
}
public static getBlogPostFromCache(fileName: string): BlogPost | null {
const blogPost: BlogPost | null = LocalCache.getJSON(
'blog',
fileName
) as BlogPost | null;
return blogPost;
return `${year}-${month}-${day}`;
}
private static getFormattedPostDateFromFileName(fileName: string): string {
// file name is of the format YYYY-MM-DD-Title.md
const year: string | undefined = fileName.split("-")[0];
const month: string | undefined = fileName.split("-")[1];
const day: string | undefined = fileName.split("-")[2];
if (!year || !month || !day) {
throw new BadDataException("Invalid file name");
}
public static getTitleFromFileContent(fileContent: string): string {
// title is the first line that stars with "#"
const date: Date = OneUptimeDate.getDateFromYYYYMMDD(year, month, day);
return OneUptimeDate.getDateAsLocalFormattedString(date, true);
}
const titleLine: string =
fileContent
.split('\n')
.find((line: string) => {
return line.startsWith('#');
})
?.replace('#', '') || 'OneUptime Blog';
private static getPostFromMarkdown(markdownContent: string): string {
const authorLine: string | undefined = markdownContent
.split("\n")
.find((line: string) => {
return line.startsWith("Author:");
});
const titleLine: string | undefined = markdownContent
.split("\n")
.find((line: string) => {
return line.startsWith("#");
});
const descriptionLine: string | undefined =
markdownContent.split("\n").find((line: string) => {
return line.startsWith("Description:");
}) || "";
return titleLine;
const tagsLine: string | undefined =
markdownContent.split("\n").find((line: string) => {
return line.startsWith("Tags:");
}) || "";
if (!authorLine && !titleLine && !descriptionLine && !tagsLine) {
return markdownContent;
}
public static getTagsFromFileContent(fileContent: string): string[] {
// tags is the first line that starts with "Tags:"
const lines: string[] = markdownContent.split("\n");
const tagsLine: string | undefined =
fileContent
.split('\n')
.find((line: string) => {
return line.startsWith('Tags:');
})
?.replace('Tags:', '') || '';
return tagsLine.split(',').map((tag: string) => {
return tag.trim();
});
if (authorLine) {
const authorLineIndex: number = lines.indexOf(authorLine);
lines.splice(authorLineIndex, 1);
}
public static getDescriptionFromFileContent(fileContent: string): string {
// description is the first line that starts with ">"
const descriptionLine: string | undefined =
fileContent
.split('\n')
.find((line: string) => {
return line.startsWith('Description:');
})
?.replace('Description:', '') || '';
return descriptionLine;
if (titleLine) {
const titleLineIndex: number = lines.indexOf(titleLine);
lines.splice(titleLineIndex, 1);
}
public static async getAuthorFromFileContent(
fileContent: string
): Promise<BlogPostAuthor | null> {
// author line is in this format: Author: [username](githubUrl)
const authorLine: string | undefined = fileContent
.split('\n')
.find((line: string) => {
return line.startsWith('Author:');
});
const authorUsername: string | undefined = authorLine
?.split('[')[1]
?.split(']')[0];
const authorGitHubUrl: string | undefined = authorLine
?.split('(')[1]
?.split(')')[0];
const authorProfileImageUrl: string = `https://avatars.githubusercontent.com/${authorUsername}`;
if (!authorUsername || !authorGitHubUrl) {
return null;
}
return {
username: authorUsername,
githubUrl: authorGitHubUrl,
profileImageUrl: authorProfileImageUrl,
name: await this.getNameOfGitHubUser(authorUsername),
};
if (descriptionLine) {
const descriptionLineIndex: number = lines.indexOf(descriptionLine);
lines.splice(descriptionLineIndex, 1);
}
if (tagsLine) {
const tagsLineIndex: number = lines.indexOf(tagsLine);
lines.splice(tagsLineIndex, 1);
}
return lines.join("\n").trim();
}
public static getBlogPostFromCache(fileName: string): BlogPost | null {
const blogPost: BlogPost | null = LocalCache.getJSON(
"blog",
fileName,
) as BlogPost | null;
return blogPost;
}
public static getTitleFromFileContent(fileContent: string): string {
// title is the first line that stars with "#"
const titleLine: string =
fileContent
.split("\n")
.find((line: string) => {
return line.startsWith("#");
})
?.replace("#", "") || "OneUptime Blog";
return titleLine;
}
public static getTagsFromFileContent(fileContent: string): string[] {
// tags is the first line that starts with "Tags:"
const tagsLine: string | undefined =
fileContent
.split("\n")
.find((line: string) => {
return line.startsWith("Tags:");
})
?.replace("Tags:", "") || "";
return tagsLine.split(",").map((tag: string) => {
return tag.trim();
});
}
public static getDescriptionFromFileContent(fileContent: string): string {
// description is the first line that starts with ">"
const descriptionLine: string | undefined =
fileContent
.split("\n")
.find((line: string) => {
return line.startsWith("Description:");
})
?.replace("Description:", "") || "";
return descriptionLine;
}
public static async getAuthorFromFileContent(
fileContent: string,
): Promise<BlogPostAuthor | null> {
// author line is in this format: Author: [username](githubUrl)
const authorLine: string | undefined = fileContent
.split("\n")
.find((line: string) => {
return line.startsWith("Author:");
});
const authorUsername: string | undefined = authorLine
?.split("[")[1]
?.split("]")[0];
const authorGitHubUrl: string | undefined = authorLine
?.split("(")[1]
?.split(")")[0];
const authorProfileImageUrl: string = `https://avatars.githubusercontent.com/${authorUsername}`;
if (!authorUsername || !authorGitHubUrl) {
return null;
}
return {
username: authorUsername,
githubUrl: authorGitHubUrl,
profileImageUrl: authorProfileImageUrl,
name: await this.getNameOfGitHubUser(authorUsername),
};
}
}

View File

@@ -1,2 +1,2 @@
export const ViewsPath: string = '/usr/src/app/FeatureSet/Home/Views';
export const StaticPath: string = '/usr/src/app/FeatureSet/Home/Static';
export const ViewsPath: string = "/usr/src/app/FeatureSet/Home/Views";
export const StaticPath: string = "/usr/src/app/FeatureSet/Home/Static";

View File

@@ -1,15 +1,15 @@
import { ViewsPath } from './Config';
import { ExpressResponse } from 'CommonServer/Utils/Express';
import { ViewsPath } from "./Config";
import { ExpressResponse } from "CommonServer/Utils/Express";
export default class NotFoundUtil {
public static renderNotFound(res: ExpressResponse): void {
res.status(404);
res.render(`${ViewsPath}/not-found.ejs`, {
footerCards: false,
support: false,
cta: false,
blackLogo: false,
requestDemoCta: false,
});
}
public static renderNotFound(res: ExpressResponse): void {
res.status(404);
res.render(`${ViewsPath}/not-found.ejs`, {
footerCards: false,
support: false,
cta: false,
blackLogo: false,
requestDemoCta: false,
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,15 @@
import { ViewsPath } from './Config';
import { ExpressResponse } from 'CommonServer/Utils/Express';
import { ViewsPath } from "./Config";
import { ExpressResponse } from "CommonServer/Utils/Express";
export default class ServerErrorUtil {
public static renderServerError(res: ExpressResponse): void {
res.status(500);
res.render(`${ViewsPath}/server-error.ejs`, {
footerCards: false,
support: false,
cta: false,
blackLogo: false,
requestDemoCta: false,
});
}
public static renderServerError(res: ExpressResponse): void {
res.status(500);
res.render(`${ViewsPath}/server-error.ejs`, {
footerCards: false,
support: false,
cta: false,
blackLogo: false,
requestDemoCta: false,
});
}
}

View File

@@ -11,7 +11,7 @@
<img class="h-6 mt-2" src="/img/viewsonic.svg" alt="ViewSonic">
</div>
<div class="mt-4 ml-8 flex justify-center flex-shrink-0 flex-grow lg:ml-4 lg:flex-grow-0">
<img class="h-9 mt-1" src="/img/Siemens-logo.svg" alt="Siemens">
<img class="h-12 -mt-4" src="/img/skillable-logo.svg" alt="Skillable">
</div>
<div class="mt-4 ml-8 flex justify-center flex-shrink-0 flex-grow lg:ml-4 lg:flex-grow-0">
<img class="h-11 -mt-2" src="/img/sodexo.svg" alt="Sodexo">

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +1,79 @@
import OneUptimeDate from 'Common/Types/Date';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ResellerService from 'CommonServer/Services/ResellerService';
import OneUptimeDate from "Common/Types/Date";
import BadDataException from "Common/Types/Exception/BadDataException";
import ResellerService from "CommonServer/Services/ResellerService";
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from 'CommonServer/Utils/Express';
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
import Response from 'CommonServer/Utils/Response';
import Reseller from 'Model/Models/Reseller';
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from "CommonServer/Utils/Express";
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
import Response from "CommonServer/Utils/Response";
import Reseller from "Model/Models/Reseller";
const router: ExpressRouter = Express.getRouter();
router.post(
'/reseller/auth/:resellerid',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
const resellerId: string | undefined = req.params['resellerid'];
"/reseller/auth/:resellerid",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
const resellerId: string | undefined = req.params["resellerid"];
if (!resellerId) {
throw new BadDataException('Reseller ID not found');
}
if (!resellerId) {
throw new BadDataException("Reseller ID not found");
}
const username: string = req.body['username'];
const password: string = req.body['password'];
const username: string = req.body["username"];
const password: string = req.body["password"];
if (!username) {
throw new BadDataException('Username not found');
}
if (!username) {
throw new BadDataException("Username not found");
}
if (!password) {
throw new BadDataException('Password not found');
}
if (!password) {
throw new BadDataException("Password not found");
}
// get the reseller user.
const reseller: Reseller | null = await ResellerService.findOneBy({
query: {
resellerId: resellerId,
username: username,
password: password,
},
select: {
_id: true,
resellerId: true,
},
props: {
isRoot: true,
},
});
// get the reseller user.
const reseller: Reseller | null = await ResellerService.findOneBy({
query: {
resellerId: resellerId,
username: username,
password: password,
},
select: {
_id: true,
resellerId: true,
},
props: {
isRoot: true,
},
});
if (!reseller) {
throw new BadDataException(
'Reseller not found or username and password is incorrect'
);
}
if (!reseller) {
throw new BadDataException(
"Reseller not found or username and password is incorrect",
);
}
// if found then generate a token and return it.
// if found then generate a token and return it.
const token: string = JSONWebToken.sign({
data: { resellerId: resellerId },
expiresInSeconds: OneUptimeDate.getDayInSeconds(365),
});
const token: string = JSONWebToken.sign({
data: { resellerId: resellerId },
expiresInSeconds: OneUptimeDate.getDayInSeconds(365),
});
return Response.sendJsonObjectResponse(req, res, {
access: token,
});
} catch (err) {
return next(err);
}
return Response.sendJsonObjectResponse(req, res, {
access: token,
});
} catch (err) {
return next(err);
}
},
);
export default router;

View File

@@ -1,471 +1,527 @@
import AuthenticationEmail from '../Utils/AuthenticationEmail';
import SSOUtil from '../Utils/SSO';
import { DashboardRoute } from 'Common/ServiceRoute';
import Hostname from 'Common/Types/API/Hostname';
import Protocol from 'Common/Types/API/Protocol';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import OneUptimeDate from 'Common/Types/Date';
import Email from 'Common/Types/Email';
import BadRequestException from 'Common/Types/Exception/BadRequestException';
import Exception from 'Common/Types/Exception/Exception';
import ServerException from 'Common/Types/Exception/ServerException';
import { JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import PositiveNumber from 'Common/Types/PositiveNumber';
import DatabaseConfig from 'CommonServer/DatabaseConfig';
import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig';
import AccessTokenService from 'CommonServer/Services/AccessTokenService';
import ProjectSSOService from 'CommonServer/Services/ProjectSsoService';
import TeamMemberService from 'CommonServer/Services/TeamMemberService';
import UserService from 'CommonServer/Services/UserService';
import CookieUtil from 'CommonServer/Utils/Cookie';
import AuthenticationEmail from "../Utils/AuthenticationEmail";
import SSOUtil from "../Utils/SSO";
import { DashboardRoute } from "Common/ServiceRoute";
import Hostname from "Common/Types/API/Hostname";
import Protocol from "Common/Types/API/Protocol";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
import OneUptimeDate from "Common/Types/Date";
import Email from "Common/Types/Email";
import BadRequestException from "Common/Types/Exception/BadRequestException";
import Exception from "Common/Types/Exception/Exception";
import ServerException from "Common/Types/Exception/ServerException";
import { JSONObject } from "Common/Types/JSON";
import ObjectID from "Common/Types/ObjectID";
import PositiveNumber from "Common/Types/PositiveNumber";
import DatabaseConfig from "CommonServer/DatabaseConfig";
import { Host, HttpProtocol } from "CommonServer/EnvironmentConfig";
import AccessTokenService from "CommonServer/Services/AccessTokenService";
import ProjectSSOService from "CommonServer/Services/ProjectSsoService";
import TeamMemberService from "CommonServer/Services/TeamMemberService";
import UserService from "CommonServer/Services/UserService";
import QueryHelper from "CommonServer/Types/Database/QueryHelper";
import CookieUtil from "CommonServer/Utils/Cookie";
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from 'CommonServer/Utils/Express';
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
import logger from 'CommonServer/Utils/Logger';
import Response from 'CommonServer/Utils/Response';
import ProjectSSO from 'Model/Models/ProjectSso';
import TeamMember from 'Model/Models/TeamMember';
import User from 'Model/Models/User';
import xml2js from 'xml2js';
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from "CommonServer/Utils/Express";
import logger from "CommonServer/Utils/Logger";
import Response from "CommonServer/Utils/Response";
import ProjectSSO from "Model/Models/ProjectSso";
import TeamMember from "Model/Models/TeamMember";
import User from "Model/Models/User";
import xml2js from "xml2js";
const router: ExpressRouter = Express.getRouter();
// This route is used to get the SSO config for the user.
// when the user logs in from OneUptime and not from the IDP.
router.get(
'/sso/:projectId/:projectSsoId',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
if (!req.params['projectId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Project ID not found')
);
}
if (!req.params['projectSsoId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Project SSO ID not found')
);
}
const projectSSO: ProjectSSO | null =
await ProjectSSOService.findOneBy({
query: {
projectId: new ObjectID(req.params['projectId']),
_id: req.params['projectSsoId'],
isEnabled: true,
},
select: {
_id: true,
signOnURL: true,
issuerURL: true,
projectId: true,
},
props: {
isRoot: true,
},
});
if (!projectSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SSO Config not found')
);
}
// redirect to Identity Provider.
if (!projectSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Sign On URL not found')
);
}
if (!projectSSO.issuerURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Issuer not found')
);
}
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
acsUrl: URL.fromString(
`${HttpProtocol}${Host}/identity/idp-login/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`
),
signOnUrl: projectSSO.signOnURL!,
issuerUrl: URL.fromString(
`${HttpProtocol}${Host}/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`
),
});
return Response.redirect(req, res, samlRequestUrl);
} catch (err) {
return next(err);
}
"/service-provider-login",
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
if (!req.query["email"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Email is required"),
);
}
const email: Email = new Email(req.query["email"] as string);
if (!email) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Email is required"),
);
}
// get sso config for this user.
const user: User | null = await UserService.findOneBy({
query: { email: email },
select: {
_id: true,
},
props: {
isRoot: true,
},
});
if (!user) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("No SSO config found for this user"),
);
}
const userId: ObjectID = user.id!;
if (!userId) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("No SSO config found for this user"),
);
}
const projectUserBelongsTo: Array<ObjectID> = (
await TeamMemberService.findBy({
query: { userId: userId },
select: {
projectId: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
props: {
isRoot: true,
},
})
).map((teamMember: TeamMember) => {
return teamMember.projectId!;
});
if (projectUserBelongsTo.length === 0) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("No SSO config found for this user"),
);
}
const projectSSOList: Array<ProjectSSO> = await ProjectSSOService.findBy({
query: {
projectId: QueryHelper.any(projectUserBelongsTo),
isEnabled: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
select: {
name: true,
description: true,
_id: true,
projectId: true,
project: {
name: true,
},
},
props: {
isRoot: true,
},
});
return Response.sendEntityArrayResponse(
req,
res,
projectSSOList,
projectSSOList.length,
ProjectSSO,
);
},
);
router.get(
'/idp-login/:projectId/:projectSsoId',
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
return await loginUserWithSso(req, res);
"/sso/:projectId/:projectSsoId",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
if (!req.params["projectId"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Project ID not found"),
);
}
if (!req.params["projectSsoId"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Project SSO ID not found"),
);
}
const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy({
query: {
projectId: new ObjectID(req.params["projectId"]),
_id: req.params["projectSsoId"],
isEnabled: true,
},
select: {
_id: true,
signOnURL: true,
issuerURL: true,
projectId: true,
},
props: {
isRoot: true,
},
});
if (!projectSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("SSO Config not found"),
);
}
// redirect to Identity Provider.
if (!projectSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Sign On URL not found"),
);
}
if (!projectSSO.issuerURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Issuer not found"),
);
}
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
acsUrl: URL.fromString(
`${HttpProtocol}${Host}/identity/idp-login/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`,
),
signOnUrl: projectSSO.signOnURL!,
issuerUrl: URL.fromString(
`${HttpProtocol}${Host}/${projectSSO.projectId?.toString()}/${projectSSO.id?.toString()}`,
),
});
return Response.redirect(req, res, samlRequestUrl);
} catch (err) {
return next(err);
}
},
);
router.get(
"/idp-login/:projectId/:projectSsoId",
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
return await loginUserWithSso(req, res);
},
);
router.post(
'/idp-login/:projectId/:projectSsoId',
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
return await loginUserWithSso(req, res);
}
"/idp-login/:projectId/:projectSsoId",
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
return await loginUserWithSso(req, res);
},
);
type LoginUserWithSsoFunction = (
req: ExpressRequest,
res: ExpressResponse
req: ExpressRequest,
res: ExpressResponse,
) => Promise<void>;
const loginUserWithSso: LoginUserWithSsoFunction = async (
req: ExpressRequest,
res: ExpressResponse
req: ExpressRequest,
res: ExpressResponse,
): Promise<void> => {
try {
const samlResponseBase64: string = req.body.SAMLResponse;
try {
const samlResponseBase64: string = req.body.SAMLResponse;
if (!samlResponseBase64) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SAMLResponse not found')
);
}
const samlResponse: string = Buffer.from(
samlResponseBase64,
'base64'
).toString();
const response: JSONObject = await xml2js.parseStringPromise(
samlResponse
);
let issuerUrl: string = '';
let email: Email | null = null;
if (!req.params['projectId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Project ID not found')
);
}
if (!req.params['projectSsoId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Project SSO ID not found')
);
}
const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy(
{
query: {
projectId: new ObjectID(req.params['projectId']),
_id: req.params['projectSsoId'],
isEnabled: true,
},
select: {
signOnURL: true,
issuerURL: true,
publicCertificate: true,
teams: {
_id: true,
},
},
props: {
isRoot: true,
},
}
);
if (!projectSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SSO Config not found')
);
}
// redirect to Identity Provider.
if (!projectSSO.issuerURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Issuer URL not found')
);
}
// redirect to Identity Provider.
if (!projectSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Sign on URL not found')
);
}
if (!projectSSO.publicCertificate) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Public Certificate not found')
);
}
try {
SSOUtil.isPayloadValid(response);
if (
!SSOUtil.isSignatureValid(
samlResponse,
projectSSO.publicCertificate
)
) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException(
'Signature is not valid or Public Certificate configured with this SSO provider is not valid'
)
);
}
issuerUrl = SSOUtil.getIssuer(response);
email = SSOUtil.getEmail(response);
} catch (err: unknown) {
if (err instanceof Exception) {
return Response.sendErrorResponse(req, res, err);
}
return Response.sendErrorResponse(req, res, new ServerException());
}
if (projectSSO.issuerURL.toString() !== issuerUrl) {
logger.error(
'Issuer URL does not match. It should be ' +
projectSSO.issuerURL.toString() +
' but it is ' +
issuerUrl.toString()
);
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Issuer URL does not match')
);
}
// Check if he already belongs to the project, If he does - then log in.
let alreadySavedUser: User | null = await UserService.findOneBy({
query: { email: email },
select: {
_id: true,
name: true,
email: true,
isMasterAdmin: true,
isEmailVerified: true,
profilePictureId: true,
},
props: {
isRoot: true,
},
});
let isNewUser: boolean = false;
if (!alreadySavedUser) {
// this should never happen because user is logged in before he signs in with SSO UNLESS he initiates the login though the IDP.
/// Create a user.
alreadySavedUser = await UserService.createByEmail({
email,
isEmailVerified: true,
generateRandomPassword: true,
props: {
isRoot: true,
},
});
isNewUser = true;
}
// If he does not then add him to teams that he should belong and log in.
// This should never happen because email is verified before he logs in with SSO.
if (!alreadySavedUser.isEmailVerified && !isNewUser) {
await AuthenticationEmail.sendVerificationEmail(alreadySavedUser!);
return Response.render(
req,
res,
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
{
title: 'Email not verified.',
message:
'Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.',
}
);
}
// check if the user already belongs to the project
const teamMemberCount: PositiveNumber = await TeamMemberService.countBy(
{
query: {
projectId: new ObjectID(req.params['projectId'] as string),
userId: alreadySavedUser!.id!,
},
props: {
isRoot: true,
},
}
);
if (teamMemberCount.toNumber() === 0) {
// user not in project, add him to default teams.
if (!projectSSO.teams || projectSSO.teams.length === 0) {
return Response.render(
req,
res,
'/usr/src/app/FeatureSet/Identity/Views/Message.ejs',
{
title: 'No teams added.',
message:
'No teams have been added to this SSO config. Please contact your admin and have default teams added.',
}
);
}
for (const team of projectSSO.teams) {
// add user to team
let teamMember: TeamMember = new TeamMember();
teamMember.projectId = new ObjectID(
req.params['projectId'] as string
);
teamMember.userId = alreadySavedUser.id!;
teamMember.hasAcceptedInvitation = true;
teamMember.invitationAcceptedAt =
OneUptimeDate.getCurrentDate();
teamMember.teamId = team.id!;
teamMember = await TeamMemberService.create({
data: teamMember,
props: {
isRoot: true,
ignoreHooks: true,
},
});
}
}
const projectId: ObjectID = new ObjectID(
req.params['projectId'] as string
);
const ssoToken: string = JSONWebToken.sign({
data: {
userId: alreadySavedUser.id!,
projectId: projectId,
name: alreadySavedUser.name!,
email: email,
isMasterAdmin: false,
isGeneralLogin: false,
},
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30)
),
});
const oneUptimeToken: string = JSONWebToken.signUserLoginToken({
tokenData: {
userId: alreadySavedUser.id!,
email: alreadySavedUser.email!,
name: alreadySavedUser.name!,
isMasterAdmin: alreadySavedUser.isMasterAdmin!,
isGlobalLogin: false, // This is a general login without SSO. So, we will set this to false. This will give access to all the projects that dont require SSO.
},
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30)
),
});
// Set a cookie with token.
CookieUtil.setCookie(
res,
CookieUtil.getUserTokenKey(),
oneUptimeToken,
{
maxAge: OneUptimeDate.getMillisecondsInDays(
new PositiveNumber(30)
),
httpOnly: true,
}
);
CookieUtil.setCookie(
res,
CookieUtil.getUserSSOKey(projectId),
ssoToken,
{
maxAge: OneUptimeDate.getMillisecondsInDays(
new PositiveNumber(30)
),
httpOnly: true,
}
);
// Refresh Permissions for this user here.
await AccessTokenService.refreshUserAllPermissions(
alreadySavedUser.id!
);
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
logger.info('User logged in with SSO' + email.toString());
return Response.redirect(
req,
res,
new URL(
httpProtocol,
host,
new Route(DashboardRoute.toString()).addRoute(
'/' + req.params['projectId']
)
)
);
} catch (err) {
logger.error(err);
Response.sendErrorResponse(req, res, new ServerException());
if (!samlResponseBase64) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("SAMLResponse not found"),
);
}
const samlResponse: string = Buffer.from(
samlResponseBase64,
"base64",
).toString();
const response: JSONObject = await xml2js.parseStringPromise(samlResponse);
let issuerUrl: string = "";
let email: Email | null = null;
if (!req.params["projectId"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Project ID not found"),
);
}
if (!req.params["projectSsoId"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Project SSO ID not found"),
);
}
const projectSSO: ProjectSSO | null = await ProjectSSOService.findOneBy({
query: {
projectId: new ObjectID(req.params["projectId"]),
_id: req.params["projectSsoId"],
isEnabled: true,
},
select: {
signOnURL: true,
issuerURL: true,
publicCertificate: true,
teams: {
_id: true,
},
},
props: {
isRoot: true,
},
});
if (!projectSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("SSO Config not found"),
);
}
// redirect to Identity Provider.
if (!projectSSO.issuerURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Issuer URL not found"),
);
}
// redirect to Identity Provider.
if (!projectSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Sign on URL not found"),
);
}
if (!projectSSO.publicCertificate) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Public Certificate not found"),
);
}
try {
SSOUtil.isPayloadValid(response);
if (
!SSOUtil.isSignatureValid(samlResponse, projectSSO.publicCertificate)
) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException(
"Signature is not valid or Public Certificate configured with this SSO provider is not valid",
),
);
}
issuerUrl = SSOUtil.getIssuer(response);
email = SSOUtil.getEmail(response);
} catch (err: unknown) {
if (err instanceof Exception) {
return Response.sendErrorResponse(req, res, err);
}
return Response.sendErrorResponse(req, res, new ServerException());
}
if (projectSSO.issuerURL.toString() !== issuerUrl) {
logger.error(
"Issuer URL does not match. It should be " +
projectSSO.issuerURL.toString() +
" but it is " +
issuerUrl.toString(),
);
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Issuer URL does not match"),
);
}
// Check if he already belongs to the project, If he does - then log in.
let alreadySavedUser: User | null = await UserService.findOneBy({
query: { email: email },
select: {
_id: true,
name: true,
email: true,
isMasterAdmin: true,
isEmailVerified: true,
profilePictureId: true,
timezone: true,
},
props: {
isRoot: true,
},
});
let isNewUser: boolean = false;
if (!alreadySavedUser) {
// this should never happen because user is logged in before he signs in with SSO UNLESS he initiates the login though the IDP.
/// Create a user.
alreadySavedUser = await UserService.createByEmail({
email,
isEmailVerified: true,
generateRandomPassword: true,
props: {
isRoot: true,
},
});
isNewUser = true;
}
// If he does not then add him to teams that he should belong and log in.
// This should never happen because email is verified before he logs in with SSO.
if (!alreadySavedUser.isEmailVerified && !isNewUser) {
await AuthenticationEmail.sendVerificationEmail(alreadySavedUser!);
return Response.render(
req,
res,
"/usr/src/app/FeatureSet/Identity/Views/Message.ejs",
{
title: "Email not verified.",
message:
"Email is not verified. We have sent you an email with the verification link. Please do not forget to check spam.",
},
);
}
// check if the user already belongs to the project
const teamMemberCount: PositiveNumber = await TeamMemberService.countBy({
query: {
projectId: new ObjectID(req.params["projectId"] as string),
userId: alreadySavedUser!.id!,
},
props: {
isRoot: true,
},
});
if (teamMemberCount.toNumber() === 0) {
// user not in project, add him to default teams.
if (!projectSSO.teams || projectSSO.teams.length === 0) {
return Response.render(
req,
res,
"/usr/src/app/FeatureSet/Identity/Views/Message.ejs",
{
title: "No teams added.",
message:
"No teams have been added to this SSO config. Please contact your admin and have default teams added.",
},
);
}
for (const team of projectSSO.teams) {
// add user to team
let teamMember: TeamMember = new TeamMember();
teamMember.projectId = new ObjectID(req.params["projectId"] as string);
teamMember.userId = alreadySavedUser.id!;
teamMember.hasAcceptedInvitation = true;
teamMember.invitationAcceptedAt = OneUptimeDate.getCurrentDate();
teamMember.teamId = team.id!;
teamMember = await TeamMemberService.create({
data: teamMember,
props: {
isRoot: true,
ignoreHooks: true,
},
});
}
}
const projectId: ObjectID = new ObjectID(req.params["projectId"] as string);
alreadySavedUser.email = email;
CookieUtil.setSSOCookie({
user: alreadySavedUser,
projectId: projectId,
expressResponse: res,
});
CookieUtil.setUserCookie({
expressResponse: res,
user: alreadySavedUser,
isGlobalLogin: false,
});
// Refresh Permissions for this user here.
await AccessTokenService.refreshUserAllPermissions(alreadySavedUser.id!);
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
logger.info("User logged in with SSO" + email.toString());
return Response.redirect(
req,
res,
new URL(
httpProtocol,
host,
new Route(DashboardRoute.toString()).addRoute(
"/" + req.params["projectId"],
),
),
);
} catch (err) {
logger.error(err);
Response.sendErrorResponse(req, res, new ServerException());
}
};
export default router;

View File

@@ -1,432 +1,422 @@
import BaseModel from 'Common/Models/BaseModel';
import { FileRoute } from 'Common/ServiceRoute';
import Hostname from 'Common/Types/API/Hostname';
import Protocol from 'Common/Types/API/Protocol';
import URL from 'Common/Types/API/URL';
import OneUptimeDate from 'Common/Types/Date';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import ObjectID from 'Common/Types/ObjectID';
import PositiveNumber from 'Common/Types/PositiveNumber';
import DatabaseConfig from 'CommonServer/DatabaseConfig';
import { EncryptionSecret } from 'CommonServer/EnvironmentConfig';
import MailService from 'CommonServer/Services/MailService';
import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivateUserService';
import StatusPageService from 'CommonServer/Services/StatusPageService';
import CookieUtil from 'CommonServer/Utils/Cookie';
import BaseModel from "Common/Models/BaseModel";
import { FileRoute } from "Common/ServiceRoute";
import Hostname from "Common/Types/API/Hostname";
import Protocol from "Common/Types/API/Protocol";
import URL from "Common/Types/API/URL";
import OneUptimeDate from "Common/Types/Date";
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
import BadDataException from "Common/Types/Exception/BadDataException";
import { JSONObject } from "Common/Types/JSON";
import JSONFunctions from "Common/Types/JSONFunctions";
import ObjectID from "Common/Types/ObjectID";
import PositiveNumber from "Common/Types/PositiveNumber";
import DatabaseConfig from "CommonServer/DatabaseConfig";
import { EncryptionSecret } from "CommonServer/EnvironmentConfig";
import MailService from "CommonServer/Services/MailService";
import StatusPagePrivateUserService from "CommonServer/Services/StatusPagePrivateUserService";
import StatusPageService from "CommonServer/Services/StatusPageService";
import CookieUtil from "CommonServer/Utils/Cookie";
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from 'CommonServer/Utils/Express';
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
import logger from 'CommonServer/Utils/Logger';
import Response from 'CommonServer/Utils/Response';
import StatusPage from 'Model/Models/StatusPage';
import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser';
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from "CommonServer/Utils/Express";
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
import logger from "CommonServer/Utils/Logger";
import Response from "CommonServer/Utils/Response";
import StatusPage from "Model/Models/StatusPage";
import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser";
const router: ExpressRouter = Express.getRouter();
router.post(
'/logout/:statuspageid',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
if (!req.params['statuspageid']) {
throw new BadDataException('Status Page ID is required.');
}
"/logout/:statuspageid",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
if (!req.params["statuspageid"]) {
throw new BadDataException("Status Page ID is required.");
}
const statusPageId: ObjectID = new ObjectID(
req.params['statuspageid'].toString()
);
const statusPageId: ObjectID = new ObjectID(
req.params["statuspageid"].toString(),
);
CookieUtil.removeCookie(
res,
CookieUtil.getUserTokenKey(statusPageId)
); // remove the cookie.
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId)); // remove the cookie.
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
},
);
router.post(
'/forgot-password',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
const data: JSONObject = req.body['data'];
"/forgot-password",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
const data: JSONObject = req.body["data"];
if (!data['email']) {
throw new BadDataException('Email is required.');
}
if (!data["email"]) {
throw new BadDataException("Email is required.");
}
const user: StatusPagePrivateUser = BaseModel.fromJSON(
data as JSONObject,
StatusPagePrivateUser
) as StatusPagePrivateUser;
const user: StatusPagePrivateUser = BaseModel.fromJSON(
data as JSONObject,
StatusPagePrivateUser,
) as StatusPagePrivateUser;
if (!user.statusPageId) {
throw new BadDataException('Status Page ID is required.');
}
if (!user.statusPageId) {
throw new BadDataException("Status Page ID is required.");
}
const statusPage: StatusPage | null =
await StatusPageService.findOneById({
id: user.statusPageId!,
props: {
isRoot: true,
ignoreHooks: true,
},
select: {
_id: true,
name: true,
pageTitle: true,
logoFileId: true,
requireSsoForLogin: true,
projectId: true,
},
});
const statusPage: StatusPage | null = await StatusPageService.findOneById(
{
id: user.statusPageId!,
props: {
isRoot: true,
ignoreHooks: true,
},
select: {
_id: true,
name: true,
pageTitle: true,
logoFileId: true,
requireSsoForLogin: true,
projectId: true,
},
},
);
if (!statusPage) {
throw new BadDataException('Status Page not found');
}
if (!statusPage) {
throw new BadDataException("Status Page not found");
}
if (statusPage.requireSsoForLogin) {
throw new BadDataException(
'Status Page supports authentication by SSO. You cannot use email and password for authentication.'
);
}
if (statusPage.requireSsoForLogin) {
throw new BadDataException(
"Status Page supports authentication by SSO. You cannot use email and password for authentication.",
);
}
const statusPageName: string | undefined =
statusPage.pageTitle || statusPage.name;
const statusPageName: string | undefined =
statusPage.pageTitle || statusPage.name;
const statusPageURL: string =
await StatusPageService.getStatusPageURL(statusPage.id!);
const statusPageURL: string = await StatusPageService.getStatusPageURL(
statusPage.id!,
);
const alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: {
email: user.email!,
statusPageId: user.statusPageId!,
},
select: {
_id: true,
password: true,
email: true,
},
props: {
isRoot: true,
},
});
const alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: {
email: user.email!,
statusPageId: user.statusPageId!,
},
select: {
_id: true,
password: true,
email: true,
},
props: {
isRoot: true,
},
});
if (alreadySavedUser) {
const token: string = ObjectID.generate().toString();
await StatusPagePrivateUserService.updateOneBy({
query: {
_id: alreadySavedUser._id!,
},
data: {
resetPasswordToken: token,
resetPasswordExpires: OneUptimeDate.getOneDayAfter(),
},
props: {
isRoot: true,
},
});
if (alreadySavedUser) {
const token: string = ObjectID.generate().toString();
await StatusPagePrivateUserService.updateOneBy({
query: {
_id: alreadySavedUser._id!,
},
data: {
resetPasswordToken: token,
resetPasswordExpires: OneUptimeDate.getOneDayAfter(),
},
props: {
isRoot: true,
},
});
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol =
await DatabaseConfig.getHttpProtocol();
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
MailService.sendMail(
{
toEmail: user.email!,
subject: 'Password Reset Request for ' + statusPageName,
templateType:
EmailTemplateType.StatusPageForgotPassword,
vars: {
statusPageName: statusPageName!,
logoUrl: statusPage.logoFileId
? new URL(httpProtocol, host)
.addRoute(FileRoute)
.addRoute(
'/image/' + statusPage.logoFileId
)
.toString()
: '',
homeURL: statusPageURL,
tokenVerifyUrl: URL.fromString(statusPageURL)
.addRoute('/reset-password/' + token)
.toString(),
},
},
{
projectId: statusPage.projectId!,
}
).catch((err: Error) => {
logger.error(err);
});
MailService.sendMail(
{
toEmail: user.email!,
subject: "Password Reset Request for " + statusPageName,
templateType: EmailTemplateType.StatusPageForgotPassword,
vars: {
statusPageName: statusPageName!,
logoUrl: statusPage.logoFileId
? new URL(httpProtocol, host)
.addRoute(FileRoute)
.addRoute("/image/" + statusPage.logoFileId)
.toString()
: "",
homeURL: statusPageURL,
tokenVerifyUrl: URL.fromString(statusPageURL)
.addRoute("/reset-password/" + token)
.toString(),
},
},
{
projectId: statusPage.projectId!,
},
).catch((err: Error) => {
logger.error(err);
});
return Response.sendEmptySuccessResponse(req, res);
}
return Response.sendEmptySuccessResponse(req, res);
}
throw new BadDataException(
`No user is registered with ${user.email?.toString()}`
);
} catch (err) {
return next(err);
}
throw new BadDataException(
`No user is registered with ${user.email?.toString()}`,
);
} catch (err) {
return next(err);
}
},
);
router.post(
'/reset-password',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
const data: JSONObject = JSONFunctions.deserialize(
req.body['data']
);
"/reset-password",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
const data: JSONObject = JSONFunctions.deserialize(req.body["data"]);
if (!data['statusPageId']) {
throw new BadDataException('Status Page ID is required.');
}
if (!data["statusPageId"]) {
throw new BadDataException("Status Page ID is required.");
}
const user: StatusPagePrivateUser = BaseModel.fromJSON(
data as JSONObject,
StatusPagePrivateUser
) as StatusPagePrivateUser;
const user: StatusPagePrivateUser = BaseModel.fromJSON(
data as JSONObject,
StatusPagePrivateUser,
) as StatusPagePrivateUser;
await user.password?.hashValue(EncryptionSecret);
await user.password?.hashValue(EncryptionSecret);
const alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: {
statusPageId: new ObjectID(
data['statusPageId'].toString()
),
resetPasswordToken:
(user.resetPasswordToken as string) || '',
},
select: {
_id: true,
password: true,
email: true,
resetPasswordExpires: true,
},
props: {
isRoot: true,
},
});
const alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: {
statusPageId: new ObjectID(data["statusPageId"].toString()),
resetPasswordToken: (user.resetPasswordToken as string) || "",
},
select: {
_id: true,
password: true,
email: true,
resetPasswordExpires: true,
},
props: {
isRoot: true,
},
});
if (!alreadySavedUser) {
throw new BadDataException(
'Invalid link. Please go to forgot password page again and request a new link.'
);
}
if (!alreadySavedUser) {
throw new BadDataException(
"Invalid link. Please go to forgot password page again and request a new link.",
);
}
if (
alreadySavedUser &&
OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!)
) {
throw new BadDataException(
'Expired link. Please go to forgot password page again and request a new link.'
);
}
if (
alreadySavedUser &&
OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!)
) {
throw new BadDataException(
"Expired link. Please go to forgot password page again and request a new link.",
);
}
const statusPage: StatusPage | null =
await StatusPageService.findOneById({
id: new ObjectID(data['statusPageId'].toString()),
props: {
isRoot: true,
ignoreHooks: true,
},
select: {
_id: true,
name: true,
pageTitle: true,
logoFileId: true,
requireSsoForLogin: true,
projectId: true,
},
});
const statusPage: StatusPage | null = await StatusPageService.findOneById(
{
id: new ObjectID(data["statusPageId"].toString()),
props: {
isRoot: true,
ignoreHooks: true,
},
select: {
_id: true,
name: true,
pageTitle: true,
logoFileId: true,
requireSsoForLogin: true,
projectId: true,
},
},
);
if (!statusPage) {
throw new BadDataException('Status Page not found');
}
if (!statusPage) {
throw new BadDataException("Status Page not found");
}
if (statusPage.requireSsoForLogin) {
throw new BadDataException(
'Status Page supports authentication by SSO. You cannot use email and password for authentication.'
);
}
if (statusPage.requireSsoForLogin) {
throw new BadDataException(
"Status Page supports authentication by SSO. You cannot use email and password for authentication.",
);
}
const statusPageName: string | undefined =
statusPage.pageTitle || statusPage.name;
const statusPageName: string | undefined =
statusPage.pageTitle || statusPage.name;
const statusPageURL: string =
await StatusPageService.getStatusPageURL(statusPage.id!);
const statusPageURL: string = await StatusPageService.getStatusPageURL(
statusPage.id!,
);
await StatusPagePrivateUserService.updateOneById({
id: alreadySavedUser.id!,
data: {
password: user.password!,
resetPasswordToken: null!,
resetPasswordExpires: null!,
},
props: {
isRoot: true,
},
});
await StatusPagePrivateUserService.updateOneById({
id: alreadySavedUser.id!,
data: {
password: user.password!,
resetPasswordToken: null!,
resetPasswordExpires: null!,
},
props: {
isRoot: true,
},
});
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol =
await DatabaseConfig.getHttpProtocol();
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
MailService.sendMail(
{
toEmail: alreadySavedUser.email!,
subject: 'Password Changed.',
templateType: EmailTemplateType.StatusPagePasswordChanged,
vars: {
homeURL: statusPageURL,
statusPageName: statusPageName || '',
logoUrl: statusPage.logoFileId
? new URL(httpProtocol, host)
.addRoute(FileRoute)
.addRoute('/image/' + statusPage.logoFileId)
.toString()
: '',
},
},
{
projectId: statusPage.projectId!,
}
).catch((err: Error) => {
logger.error(err);
});
MailService.sendMail(
{
toEmail: alreadySavedUser.email!,
subject: "Password Changed.",
templateType: EmailTemplateType.StatusPagePasswordChanged,
vars: {
homeURL: statusPageURL,
statusPageName: statusPageName || "",
logoUrl: statusPage.logoFileId
? new URL(httpProtocol, host)
.addRoute(FileRoute)
.addRoute("/image/" + statusPage.logoFileId)
.toString()
: "",
},
},
{
projectId: statusPage.projectId!,
},
).catch((err: Error) => {
logger.error(err);
});
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);
}
},
);
router.post(
'/login',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
const data: JSONObject = req.body['data'];
"/login",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
const data: JSONObject = req.body["data"];
const user: StatusPagePrivateUser = BaseModel.fromJSON(
data as JSONObject,
StatusPagePrivateUser
) as StatusPagePrivateUser;
const user: StatusPagePrivateUser = BaseModel.fromJSON(
data as JSONObject,
StatusPagePrivateUser,
) as StatusPagePrivateUser;
if (!user.statusPageId) {
throw new BadDataException('Status Page ID not found');
}
if (!user.statusPageId) {
throw new BadDataException("Status Page ID not found");
}
const statusPage: StatusPage | null =
await StatusPageService.findOneById({
id: user.statusPageId,
props: {
isRoot: true,
ignoreHooks: true,
},
select: {
requireSsoForLogin: true,
},
});
const statusPage: StatusPage | null = await StatusPageService.findOneById(
{
id: user.statusPageId,
props: {
isRoot: true,
ignoreHooks: true,
},
select: {
requireSsoForLogin: true,
},
},
);
if (!statusPage) {
throw new BadDataException('Status Page not found');
}
if (!statusPage) {
throw new BadDataException("Status Page not found");
}
if (statusPage.requireSsoForLogin) {
throw new BadDataException(
'Status Page supports authentication by SSO. You cannot use email and password for authentication.'
);
}
if (statusPage.requireSsoForLogin) {
throw new BadDataException(
"Status Page supports authentication by SSO. You cannot use email and password for authentication.",
);
}
await user.password?.hashValue(EncryptionSecret);
await user.password?.hashValue(EncryptionSecret);
const alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: {
email: user.email!,
password: user.password!,
statusPageId: user.statusPageId!,
},
select: {
_id: true,
password: true,
email: true,
statusPageId: true,
},
props: {
isRoot: true,
},
});
const alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: {
email: user.email!,
password: user.password!,
statusPageId: user.statusPageId!,
},
select: {
_id: true,
password: true,
email: true,
statusPageId: true,
},
props: {
isRoot: true,
},
});
if (alreadySavedUser) {
const token: string = JSONWebToken.sign({
data: alreadySavedUser,
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30)
),
});
if (alreadySavedUser) {
const token: string = JSONWebToken.sign({
data: alreadySavedUser,
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30),
),
});
CookieUtil.setCookie(
res,
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
token,
{
httpOnly: true,
maxAge: OneUptimeDate.getMillisecondsInDays(
new PositiveNumber(30)
),
}
);
CookieUtil.setCookie(
res,
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
token,
{
httpOnly: true,
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
},
);
return Response.sendEntityResponse(
req,
res,
alreadySavedUser,
StatusPagePrivateUser,
{
miscData: {
token: token,
},
}
);
}
throw new BadDataException(
'Invalid login: Email or password does not match.'
);
} catch (err) {
return next(err);
}
return Response.sendEntityResponse(
req,
res,
alreadySavedUser,
StatusPagePrivateUser,
{
miscData: {
token: token,
},
},
);
}
throw new BadDataException(
"Invalid login: Email or password does not match.",
);
} catch (err) {
return next(err);
}
},
);
export default router;

View File

@@ -1,323 +1,312 @@
import SSOUtil from '../Utils/SSO';
import URL from 'Common/Types/API/URL';
import OneUptimeDate from 'Common/Types/Date';
import Email from 'Common/Types/Email';
import BadRequestException from 'Common/Types/Exception/BadRequestException';
import Exception from 'Common/Types/Exception/Exception';
import ServerException from 'Common/Types/Exception/ServerException';
import HashedString from 'Common/Types/HashedString';
import { JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import PositiveNumber from 'Common/Types/PositiveNumber';
import { Host, HttpProtocol } from 'CommonServer/EnvironmentConfig';
import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivateUserService';
import StatusPageService from 'CommonServer/Services/StatusPageService';
import StatusPageSsoService from 'CommonServer/Services/StatusPageSsoService';
import CookieUtil from 'CommonServer/Utils/Cookie';
import SSOUtil from "../Utils/SSO";
import URL from "Common/Types/API/URL";
import OneUptimeDate from "Common/Types/Date";
import Email from "Common/Types/Email";
import BadRequestException from "Common/Types/Exception/BadRequestException";
import Exception from "Common/Types/Exception/Exception";
import ServerException from "Common/Types/Exception/ServerException";
import HashedString from "Common/Types/HashedString";
import { JSONObject } from "Common/Types/JSON";
import ObjectID from "Common/Types/ObjectID";
import PositiveNumber from "Common/Types/PositiveNumber";
import { Host, HttpProtocol } from "CommonServer/EnvironmentConfig";
import StatusPagePrivateUserService from "CommonServer/Services/StatusPagePrivateUserService";
import StatusPageService from "CommonServer/Services/StatusPageService";
import StatusPageSsoService from "CommonServer/Services/StatusPageSsoService";
import CookieUtil from "CommonServer/Utils/Cookie";
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from 'CommonServer/Utils/Express';
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
import logger from 'CommonServer/Utils/Logger';
import Response from 'CommonServer/Utils/Response';
import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser';
import StatusPageSSO from 'Model/Models/StatusPageSso';
import xml2js from 'xml2js';
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from "CommonServer/Utils/Express";
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
import logger from "CommonServer/Utils/Logger";
import Response from "CommonServer/Utils/Response";
import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser";
import StatusPageSSO from "Model/Models/StatusPageSso";
import xml2js from "xml2js";
const router: ExpressRouter = Express.getRouter();
router.get(
'/status-page-sso/:statusPageId/:statusPageSsoId',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
if (!req.params['statusPageId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Status Page ID not found')
);
}
"/status-page-sso/:statusPageId/:statusPageSsoId",
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction,
): Promise<void> => {
try {
if (!req.params["statusPageId"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Status Page ID not found"),
);
}
if (!req.params['statusPageSsoId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Status Page SSO ID not found')
);
}
if (!req.params["statusPageSsoId"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Status Page SSO ID not found"),
);
}
const statusPageId: ObjectID = new ObjectID(
req.params['statusPageId']
);
const statusPageId: ObjectID = new ObjectID(req.params["statusPageId"]);
const statusPageSSO: StatusPageSSO | null =
await StatusPageSsoService.findOneBy({
query: {
statusPageId: statusPageId,
_id: req.params['statusPageSsoId'],
isEnabled: true,
},
select: {
signOnURL: true,
statusPageId: true,
_id: true,
},
props: {
isRoot: true,
},
});
const statusPageSSO: StatusPageSSO | null =
await StatusPageSsoService.findOneBy({
query: {
statusPageId: statusPageId,
_id: req.params["statusPageSsoId"],
isEnabled: true,
},
select: {
signOnURL: true,
statusPageId: true,
_id: true,
},
props: {
isRoot: true,
},
});
if (!statusPageSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SSO Config not found')
);
}
if (!statusPageSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("SSO Config not found"),
);
}
// redirect to Identity Provider.
// redirect to Identity Provider.
if (!statusPageSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Sign On URL not found')
);
}
if (!statusPageSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Sign On URL not found"),
);
}
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
acsUrl: URL.fromString(
`${HttpProtocol}${Host}/identity/status-page-idp-login/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`
),
signOnUrl: statusPageSSO.signOnURL!,
issuerUrl: URL.fromString(
`${HttpProtocol}${Host}/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`
),
});
const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
acsUrl: URL.fromString(
`${HttpProtocol}${Host}/identity/status-page-idp-login/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`,
),
signOnUrl: statusPageSSO.signOnURL!,
issuerUrl: URL.fromString(
`${HttpProtocol}${Host}/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`,
),
});
return Response.redirect(req, res, samlRequestUrl);
} catch (err) {
return next(err);
}
return Response.redirect(req, res, samlRequestUrl);
} catch (err) {
return next(err);
}
},
);
router.post(
'/status-page-idp-login/:statusPageId/:statusPageSsoId',
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
try {
const samlResponseBase64: string = req.body.SAMLResponse;
"/status-page-idp-login/:statusPageId/:statusPageSsoId",
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
try {
const samlResponseBase64: string = req.body.SAMLResponse;
const samlResponse: string = Buffer.from(
samlResponseBase64,
'base64'
).toString();
const samlResponse: string = Buffer.from(
samlResponseBase64,
"base64",
).toString();
const response: JSONObject = await xml2js.parseStringPromise(
samlResponse
);
const response: JSONObject =
await xml2js.parseStringPromise(samlResponse);
let issuerUrl: string = '';
let email: Email | null = null;
let issuerUrl: string = "";
let email: Email | null = null;
if (!req.params['statusPageId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Status Page ID not found')
);
}
if (!req.params["statusPageId"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Status Page ID not found"),
);
}
if (!req.params['statusPageSsoId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Status Page SSO ID not found')
);
}
if (!req.params["statusPageSsoId"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Status Page SSO ID not found"),
);
}
const statusPageId: ObjectID = new ObjectID(
req.params['statusPageId']
);
const statusPageId: ObjectID = new ObjectID(req.params["statusPageId"]);
const statusPageSSO: StatusPageSSO | null =
await StatusPageSsoService.findOneBy({
query: {
statusPageId: statusPageId,
_id: req.params['statusPageSsoId'],
isEnabled: true,
},
select: {
signOnURL: true,
issuerURL: true,
publicCertificate: true,
projectId: true,
},
props: {
isRoot: true,
},
});
const statusPageSSO: StatusPageSSO | null =
await StatusPageSsoService.findOneBy({
query: {
statusPageId: statusPageId,
_id: req.params["statusPageSsoId"],
isEnabled: true,
},
select: {
signOnURL: true,
issuerURL: true,
publicCertificate: true,
projectId: true,
},
props: {
isRoot: true,
},
});
if (!statusPageSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SSO Config not found')
);
}
if (!statusPageSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("SSO Config not found"),
);
}
if (!statusPageSSO.projectId) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SSO Config Project ID not found')
);
}
if (!statusPageSSO.projectId) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("SSO Config Project ID not found"),
);
}
const projectId: ObjectID = statusPageSSO.projectId;
const projectId: ObjectID = statusPageSSO.projectId;
// redirect to Identity Provider.
// redirect to Identity Provider.
if (!statusPageSSO.issuerURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Issuer URL not found')
);
}
if (!statusPageSSO.issuerURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Issuer URL not found"),
);
}
// redirect to Identity Provider.
// redirect to Identity Provider.
if (!statusPageSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Sign on URL not found')
);
}
if (!statusPageSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Sign on URL not found"),
);
}
if (!statusPageSSO.publicCertificate) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Public Certificate not found')
);
}
if (!statusPageSSO.publicCertificate) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Public Certificate not found"),
);
}
try {
SSOUtil.isPayloadValid(response);
try {
SSOUtil.isPayloadValid(response);
if (
!SSOUtil.isSignatureValid(
samlResponse,
statusPageSSO.publicCertificate
)
) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Signature is not valid')
);
}
issuerUrl = SSOUtil.getIssuer(response);
email = SSOUtil.getEmail(response);
} catch (err: unknown) {
if (err instanceof Exception) {
return Response.sendErrorResponse(req, res, err);
}
return Response.sendErrorResponse(
req,
res,
new ServerException()
);
}
if (statusPageSSO.issuerURL.toString() !== issuerUrl) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Issuer URL does not match')
);
}
// Check if he already belongs to the project, If he does - then log in.
let alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: { email: email, statusPageId: statusPageId },
select: {
_id: true,
email: true,
statusPageId: true,
projectId: true,
},
props: {
isRoot: true,
},
});
if (!alreadySavedUser) {
/// Create a user.
alreadySavedUser = new StatusPagePrivateUser();
alreadySavedUser.projectId = projectId;
alreadySavedUser.statusPageId = statusPageId;
alreadySavedUser.email = email;
alreadySavedUser.password = new HashedString(
ObjectID.generate().toString()
);
alreadySavedUser.isSsoUser = true;
alreadySavedUser = await StatusPagePrivateUserService.create({
data: alreadySavedUser,
props: { isRoot: true },
});
}
const token: string = JSONWebToken.sign({
data: alreadySavedUser,
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30)
),
});
CookieUtil.setCookie(
res,
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
token,
{
httpOnly: true,
maxAge: OneUptimeDate.getMillisecondsInDays(
new PositiveNumber(30)
),
}
);
// get status page URL.
const statusPageURL: string =
await StatusPageService.getStatusPageFirstURL(statusPageId);
return Response.redirect(
req,
res,
URL.fromString(statusPageURL).addQueryParams({
token: token,
})
);
} catch (err) {
logger.error(err);
Response.sendErrorResponse(req, res, new ServerException());
if (
!SSOUtil.isSignatureValid(
samlResponse,
statusPageSSO.publicCertificate,
)
) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Signature is not valid"),
);
}
issuerUrl = SSOUtil.getIssuer(response);
email = SSOUtil.getEmail(response);
} catch (err: unknown) {
if (err instanceof Exception) {
return Response.sendErrorResponse(req, res, err);
}
return Response.sendErrorResponse(req, res, new ServerException());
}
if (statusPageSSO.issuerURL.toString() !== issuerUrl) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Issuer URL does not match"),
);
}
// Check if he already belongs to the project, If he does - then log in.
let alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: { email: email, statusPageId: statusPageId },
select: {
_id: true,
email: true,
statusPageId: true,
projectId: true,
},
props: {
isRoot: true,
},
});
if (!alreadySavedUser) {
/// Create a user.
alreadySavedUser = new StatusPagePrivateUser();
alreadySavedUser.projectId = projectId;
alreadySavedUser.statusPageId = statusPageId;
alreadySavedUser.email = email;
alreadySavedUser.password = new HashedString(
ObjectID.generate().toString(),
);
alreadySavedUser.isSsoUser = true;
alreadySavedUser = await StatusPagePrivateUserService.create({
data: alreadySavedUser,
props: { isRoot: true },
});
}
const token: string = JSONWebToken.sign({
data: alreadySavedUser,
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30),
),
});
CookieUtil.setCookie(
res,
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
token,
{
httpOnly: true,
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
},
);
// get status page URL.
const statusPageURL: string =
await StatusPageService.getStatusPageFirstURL(statusPageId);
return Response.redirect(
req,
res,
URL.fromString(statusPageURL).addQueryParams({
token: token,
}),
);
} catch (err) {
logger.error(err);
Response.sendErrorResponse(req, res, new ServerException());
}
},
);
export default router;

View File

@@ -1,31 +1,31 @@
import AuthenticationAPI from './API/Authentication';
import ResellerAPI from './API/Reseller';
import SsoAPI from './API/SSO';
import StatusPageAuthenticationAPI from './API/StatusPageAuthentication';
import StatusPageSsoAPI from './API/StatusPageSSO';
import FeatureSet from 'CommonServer/Types/FeatureSet';
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
import 'ejs';
import AuthenticationAPI from "./API/Authentication";
import ResellerAPI from "./API/Reseller";
import SsoAPI from "./API/SSO";
import StatusPageAuthenticationAPI from "./API/StatusPageAuthentication";
import StatusPageSsoAPI from "./API/StatusPageSSO";
import FeatureSet from "CommonServer/Types/FeatureSet";
import Express, { ExpressApplication } from "CommonServer/Utils/Express";
import "ejs";
const IdentityFeatureSet: FeatureSet = {
init: async (): Promise<void> => {
const app: ExpressApplication = Express.getExpressApp();
init: async (): Promise<void> => {
const app: ExpressApplication = Express.getExpressApp();
const APP_NAME: string = 'api/identity';
const APP_NAME: string = "api/identity";
app.use([`/${APP_NAME}`, '/'], AuthenticationAPI);
app.use([`/${APP_NAME}`, "/"], AuthenticationAPI);
app.use([`/${APP_NAME}`, '/'], ResellerAPI);
app.use([`/${APP_NAME}`, "/"], ResellerAPI);
app.use([`/${APP_NAME}`, '/'], SsoAPI);
app.use([`/${APP_NAME}`, "/"], SsoAPI);
app.use([`/${APP_NAME}`, '/'], StatusPageSsoAPI);
app.use([`/${APP_NAME}`, "/"], StatusPageSsoAPI);
app.use(
[`/${APP_NAME}/status-page`, '/status-page'],
StatusPageAuthenticationAPI
);
},
app.use(
[`/${APP_NAME}/status-page`, "/status-page"],
StatusPageAuthenticationAPI,
);
},
};
export default IdentityFeatureSet;

View File

@@ -1,57 +1,57 @@
import { AccountsRoute } from 'Common/ServiceRoute';
import Hostname from 'Common/Types/API/Hostname';
import Protocol from 'Common/Types/API/Protocol';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import OneUptimeDate from 'Common/Types/Date';
import Email from 'Common/Types/Email';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import ObjectID from 'Common/Types/ObjectID';
import DatabaseConfig from 'CommonServer/DatabaseConfig';
import EmailVerificationTokenService from 'CommonServer/Services/EmailVerificationTokenService';
import MailService from 'CommonServer/Services/MailService';
import logger from 'CommonServer/Utils/Logger';
import EmailVerificationToken from 'Model/Models/EmailVerificationToken';
import User from 'Model/Models/User';
import { AccountsRoute } from "Common/ServiceRoute";
import Hostname from "Common/Types/API/Hostname";
import Protocol from "Common/Types/API/Protocol";
import Route from "Common/Types/API/Route";
import URL from "Common/Types/API/URL";
import OneUptimeDate from "Common/Types/Date";
import Email from "Common/Types/Email";
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
import ObjectID from "Common/Types/ObjectID";
import DatabaseConfig from "CommonServer/DatabaseConfig";
import EmailVerificationTokenService from "CommonServer/Services/EmailVerificationTokenService";
import MailService from "CommonServer/Services/MailService";
import logger from "CommonServer/Utils/Logger";
import EmailVerificationToken from "Model/Models/EmailVerificationToken";
import User from "Model/Models/User";
export default class AuthenticationEmail {
public static async sendVerificationEmail(user: User): Promise<void> {
const generatedToken: ObjectID = ObjectID.generate();
public static async sendVerificationEmail(user: User): Promise<void> {
const generatedToken: ObjectID = ObjectID.generate();
const emailVerificationToken: EmailVerificationToken =
new EmailVerificationToken();
emailVerificationToken.userId = user?.id as ObjectID;
emailVerificationToken.email = user?.email as Email;
emailVerificationToken.token = generatedToken;
emailVerificationToken.expires = OneUptimeDate.getOneDayAfter();
const emailVerificationToken: EmailVerificationToken =
new EmailVerificationToken();
emailVerificationToken.userId = user?.id as ObjectID;
emailVerificationToken.email = user?.email as Email;
emailVerificationToken.token = generatedToken;
emailVerificationToken.expires = OneUptimeDate.getOneDayAfter();
await EmailVerificationTokenService.create({
data: emailVerificationToken,
props: {
isRoot: true,
},
});
await EmailVerificationTokenService.create({
data: emailVerificationToken,
props: {
isRoot: true,
},
});
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
MailService.sendMail({
toEmail: user.email!,
subject: 'Please verify email.',
templateType: EmailTemplateType.SignupWelcomeEmail,
vars: {
name: user.name?.toString() || '',
tokenVerifyUrl: new URL(
httpProtocol,
host,
new Route(AccountsRoute.toString()).addRoute(
'/verify-email/' + generatedToken.toString()
)
).toString(),
homeUrl: new URL(httpProtocol, host).toString(),
},
}).catch((err: Error) => {
logger.error(err);
});
}
MailService.sendMail({
toEmail: user.email!,
subject: "Please verify email.",
templateType: EmailTemplateType.SignupWelcomeEmail,
vars: {
name: user.name?.toString() || "",
tokenVerifyUrl: new URL(
httpProtocol,
host,
new Route(AccountsRoute.toString()).addRoute(
"/verify-email/" + generatedToken.toString(),
),
).toString(),
homeUrl: new URL(httpProtocol, host).toString(),
},
}).catch((err: Error) => {
logger.error(err);
});
}
}

View File

@@ -1,230 +1,220 @@
import URL from 'Common/Types/API/URL';
import OneUptimeDate from 'Common/Types/Date';
import Email from 'Common/Types/Email';
import BadRequestException from 'Common/Types/Exception/BadRequestException';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import Text from 'Common/Types/Text';
import logger from 'CommonServer/Utils/Logger';
import xmlCrypto, { FileKeyInfo } from 'xml-crypto';
import xmldom from 'xmldom';
import zlib from 'zlib';
import URL from "Common/Types/API/URL";
import OneUptimeDate from "Common/Types/Date";
import Email from "Common/Types/Email";
import BadRequestException from "Common/Types/Exception/BadRequestException";
import { JSONArray, JSONObject } from "Common/Types/JSON";
import Text from "Common/Types/Text";
import logger from "CommonServer/Utils/Logger";
import xmlCrypto, { FileKeyInfo } from "xml-crypto";
import xmldom from "xmldom";
import zlib from "zlib";
export default class SSOUtil {
public static createSAMLRequestUrl(data: {
acsUrl: URL;
signOnUrl: URL;
issuerUrl: URL;
}): URL {
const { acsUrl, signOnUrl } = data;
public static createSAMLRequestUrl(data: {
acsUrl: URL;
signOnUrl: URL;
issuerUrl: URL;
}): URL {
const { acsUrl, signOnUrl } = data;
const samlRequest: string = `<samlp:AuthnRequest xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="${Text.generateRandomText(
10
).toUpperCase()}" Version="2.0" IssueInstant="${OneUptimeDate.getCurrentDate().toISOString()}" IsPassive="false" AssertionConsumerServiceURL="${acsUrl.toString()}" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ForceAuthn="false"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">${data.issuerUrl.toString()}</Issuer></samlp:AuthnRequest>`;
const samlRequest: string = `<samlp:AuthnRequest xmlns="urn:oasis:names:tc:SAML:2.0:metadata" ID="${Text.generateRandomText(
10,
).toUpperCase()}" Version="2.0" IssueInstant="${OneUptimeDate.getCurrentDate().toISOString()}" IsPassive="false" AssertionConsumerServiceURL="${acsUrl.toString()}" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ForceAuthn="false"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">${data.issuerUrl.toString()}</Issuer></samlp:AuthnRequest>`;
const deflated: Buffer = zlib.deflateRawSync(samlRequest);
const deflated: Buffer = zlib.deflateRawSync(samlRequest);
const base64Encoded: string = deflated.toString('base64');
const base64Encoded: string = deflated.toString("base64");
return URL.fromString(signOnUrl.toString()).addQueryParam(
'SAMLRequest',
base64Encoded,
true
);
return URL.fromString(signOnUrl.toString()).addQueryParam(
"SAMLRequest",
base64Encoded,
true,
);
}
public static isPayloadValid(payload: JSONObject): void {
if (
!payload["saml2p:Response"] &&
!payload["samlp:Response"] &&
!payload["samlp:Response"]
) {
throw new BadRequestException("SAML Response not found.");
}
public static isPayloadValid(payload: JSONObject): void {
if (
!payload['saml2p:Response'] &&
!payload['samlp:Response'] &&
!payload['samlp:Response']
) {
throw new BadRequestException('SAML Response not found.');
}
payload =
(payload["saml2p:Response"] as JSONObject) ||
(payload["samlp:Response"] as JSONObject) ||
(payload["samlp:Response"] as JSONObject) ||
(payload["Response"] as JSONObject);
payload =
(payload['saml2p:Response'] as JSONObject) ||
(payload['samlp:Response'] as JSONObject) ||
(payload['samlp:Response'] as JSONObject) ||
(payload['Response'] as JSONObject);
const issuers: JSONArray =
(payload["saml2:Issuer"] as JSONArray) ||
(payload["saml:Issuer"] as JSONArray) ||
(payload["Issuer"] as JSONArray);
const issuers: JSONArray =
(payload['saml2:Issuer'] as JSONArray) ||
(payload['saml:Issuer'] as JSONArray) ||
(payload['Issuer'] as JSONArray);
if (issuers.length === 0) {
throw new BadRequestException('Issuers not found');
}
const issuer: JSONObject | string | undefined = issuers[0];
if (typeof issuer === 'string') {
return issuer;
}
if (!issuer) {
throw new BadRequestException('Issuer not found');
}
const issuerUrl: string = issuer['_'] as string;
if (!issuerUrl) {
throw new BadRequestException(
'Issuer URL not found in SAML response'
);
}
const samlAssertion: JSONArray =
(payload['saml2:Assertion'] as JSONArray) ||
(payload['saml:Assertion'] as JSONArray) ||
(payload['Assertion'] as JSONArray);
if (!samlAssertion || samlAssertion.length === 0) {
throw new BadRequestException('SAML Assertion not found');
}
const samlSubject: JSONArray =
((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) ||
((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray) ||
((samlAssertion[0] as JSONObject)['Subject'] as JSONArray);
if (!samlSubject || samlSubject.length === 0) {
throw new BadRequestException('SAML Subject not found');
}
const samlNameId: JSONArray =
((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) ||
((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray) ||
((samlSubject[0] as JSONObject)['NameID'] as JSONArray);
if (!samlNameId || samlNameId.length === 0) {
throw new BadRequestException('SAML NAME ID not found');
}
const emailString: string = (samlNameId[0] as JSONObject)[
'_'
] as string;
if (!emailString) {
if (!samlNameId || samlNameId.length === 0) {
throw new BadRequestException('SAML Email not found');
}
}
if (issuers.length === 0) {
throw new BadRequestException("Issuers not found");
}
public static isSignatureValid(
samlPayload: string,
certificate: string
): boolean {
try {
const dom: Document = new xmldom.DOMParser().parseFromString(
samlPayload
);
const signature: Element | undefined = dom.getElementsByTagNameNS(
'http://www.w3.org/2000/09/xmldsig#',
'Signature'
)[0];
const sig: xmlCrypto.SignedXml = new xmlCrypto.SignedXml();
const issuer: JSONObject | string | undefined = issuers[0];
sig.keyInfoProvider = {
getKeyInfo: function (_key: any) {
return `<X509Data><X509Certificate>${certificate}</X509Certificate></X509Data>`;
},
getKey: function () {
return certificate;
} as any,
} as FileKeyInfo;
sig.loadSignature(signature!.toString());
const res: boolean = sig.checkSignature(samlPayload);
return res;
} catch (err) {
logger.error(err);
return false;
}
if (typeof issuer === "string") {
return issuer;
}
if (!issuer) {
throw new BadRequestException("Issuer not found");
}
public static getEmail(payload: JSONObject): Email {
if (!payload['saml2p:Response'] && !payload['samlp:Response']) {
throw new BadRequestException('SAML Response not found.');
}
const issuerUrl: string = issuer["_"] as string;
payload =
(payload['saml2p:Response'] as JSONObject) ||
(payload['samlp:Response'] as JSONObject) ||
(payload['Response'] as JSONObject);
const samlAssertion: JSONArray =
(payload['saml2:Assertion'] as JSONArray) ||
(payload['saml:Assertion'] as JSONArray) ||
(payload['Assertion'] as JSONArray);
if (!samlAssertion || samlAssertion.length === 0) {
throw new BadRequestException('SAML Assertion not found');
}
const samlSubject: JSONArray =
((samlAssertion[0] as JSONObject)['saml2:Subject'] as JSONArray) ||
((samlAssertion[0] as JSONObject)['saml:Subject'] as JSONArray) ||
((samlAssertion[0] as JSONObject)['Subject'] as JSONArray);
if (!samlSubject || samlSubject.length === 0) {
throw new BadRequestException('SAML Subject not found');
}
const samlNameId: JSONArray =
((samlSubject[0] as JSONObject)['saml2:NameID'] as JSONArray) ||
((samlSubject[0] as JSONObject)['saml:NameID'] as JSONArray) ||
((samlSubject[0] as JSONObject)['NameID'] as JSONArray);
if (!samlNameId || samlNameId.length === 0) {
throw new BadRequestException('SAML NAME ID not found');
}
const emailString: string = (samlNameId[0] as JSONObject)[
'_'
] as string;
return new Email(emailString.trim());
if (!issuerUrl) {
throw new BadRequestException("Issuer URL not found in SAML response");
}
public static getIssuer(payload: JSONObject): string {
if (!payload['saml2p:Response'] && !payload['samlp:Response']) {
throw new BadRequestException('SAML Response not found.');
}
const samlAssertion: JSONArray =
(payload["saml2:Assertion"] as JSONArray) ||
(payload["saml:Assertion"] as JSONArray) ||
(payload["Assertion"] as JSONArray);
payload =
(payload['saml2p:Response'] as JSONObject) ||
(payload['samlp:Response'] as JSONObject) ||
(payload['Response'] as JSONObject);
const issuers: JSONArray =
(payload['saml2:Issuer'] as JSONArray) ||
(payload['saml:Issuer'] as JSONArray) ||
(payload['Issuer'] as JSONArray);
if (issuers.length === 0) {
throw new BadRequestException('Issuers not found');
}
const issuer: JSONObject | string | undefined = issuers[0];
if (typeof issuer === 'string') {
return issuer;
}
if (!issuer) {
throw new BadRequestException('Issuer not found');
}
const issuerUrl: string = issuer['_'] as string;
if (!issuerUrl) {
throw new BadRequestException(
'Issuer URL not found in SAML response'
);
}
return issuerUrl.trim();
if (!samlAssertion || samlAssertion.length === 0) {
throw new BadRequestException("SAML Assertion not found");
}
const samlSubject: JSONArray =
((samlAssertion[0] as JSONObject)["saml2:Subject"] as JSONArray) ||
((samlAssertion[0] as JSONObject)["saml:Subject"] as JSONArray) ||
((samlAssertion[0] as JSONObject)["Subject"] as JSONArray);
if (!samlSubject || samlSubject.length === 0) {
throw new BadRequestException("SAML Subject not found");
}
const samlNameId: JSONArray =
((samlSubject[0] as JSONObject)["saml2:NameID"] as JSONArray) ||
((samlSubject[0] as JSONObject)["saml:NameID"] as JSONArray) ||
((samlSubject[0] as JSONObject)["NameID"] as JSONArray);
if (!samlNameId || samlNameId.length === 0) {
throw new BadRequestException("SAML NAME ID not found");
}
const emailString: string = (samlNameId[0] as JSONObject)["_"] as string;
if (!emailString) {
if (!samlNameId || samlNameId.length === 0) {
throw new BadRequestException("SAML Email not found");
}
}
}
public static isSignatureValid(
samlPayload: string,
certificate: string,
): boolean {
try {
const dom: Document = new xmldom.DOMParser().parseFromString(samlPayload);
const signature: Element | undefined = dom.getElementsByTagNameNS(
"http://www.w3.org/2000/09/xmldsig#",
"Signature",
)[0];
const sig: xmlCrypto.SignedXml = new xmlCrypto.SignedXml();
sig.keyInfoProvider = {
getKeyInfo: function (_key: any) {
return `<X509Data><X509Certificate>${certificate}</X509Certificate></X509Data>`;
},
getKey: function () {
return certificate;
} as any,
} as FileKeyInfo;
sig.loadSignature(signature!.toString());
const res: boolean = sig.checkSignature(samlPayload);
return res;
} catch (err) {
logger.error(err);
return false;
}
}
public static getEmail(payload: JSONObject): Email {
if (!payload["saml2p:Response"] && !payload["samlp:Response"]) {
throw new BadRequestException("SAML Response not found.");
}
payload =
(payload["saml2p:Response"] as JSONObject) ||
(payload["samlp:Response"] as JSONObject) ||
(payload["Response"] as JSONObject);
const samlAssertion: JSONArray =
(payload["saml2:Assertion"] as JSONArray) ||
(payload["saml:Assertion"] as JSONArray) ||
(payload["Assertion"] as JSONArray);
if (!samlAssertion || samlAssertion.length === 0) {
throw new BadRequestException("SAML Assertion not found");
}
const samlSubject: JSONArray =
((samlAssertion[0] as JSONObject)["saml2:Subject"] as JSONArray) ||
((samlAssertion[0] as JSONObject)["saml:Subject"] as JSONArray) ||
((samlAssertion[0] as JSONObject)["Subject"] as JSONArray);
if (!samlSubject || samlSubject.length === 0) {
throw new BadRequestException("SAML Subject not found");
}
const samlNameId: JSONArray =
((samlSubject[0] as JSONObject)["saml2:NameID"] as JSONArray) ||
((samlSubject[0] as JSONObject)["saml:NameID"] as JSONArray) ||
((samlSubject[0] as JSONObject)["NameID"] as JSONArray);
if (!samlNameId || samlNameId.length === 0) {
throw new BadRequestException("SAML NAME ID not found");
}
const emailString: string = (samlNameId[0] as JSONObject)["_"] as string;
return new Email(emailString.trim());
}
public static getIssuer(payload: JSONObject): string {
if (!payload["saml2p:Response"] && !payload["samlp:Response"]) {
throw new BadRequestException("SAML Response not found.");
}
payload =
(payload["saml2p:Response"] as JSONObject) ||
(payload["samlp:Response"] as JSONObject) ||
(payload["Response"] as JSONObject);
const issuers: JSONArray =
(payload["saml2:Issuer"] as JSONArray) ||
(payload["saml:Issuer"] as JSONArray) ||
(payload["Issuer"] as JSONArray);
if (issuers.length === 0) {
throw new BadRequestException("Issuers not found");
}
const issuer: JSONObject | string | undefined = issuers[0];
if (typeof issuer === "string") {
return issuer;
}
if (!issuer) {
throw new BadRequestException("Issuer not found");
}
const issuerUrl: string = issuer["_"] as string;
if (!issuerUrl) {
throw new BadRequestException("Issuer URL not found in SAML response");
}
return issuerUrl.trim();
}
}

View File

@@ -1,144 +1,143 @@
import CallService from '../Services/CallService';
import CallRequest from 'Common/Types/Call/CallRequest';
import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import ObjectID from 'Common/Types/ObjectID';
import Phone from 'Common/Types/Phone';
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService';
import CallService from "../Services/CallService";
import CallRequest from "Common/Types/Call/CallRequest";
import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig";
import BadDataException from "Common/Types/Exception/BadDataException";
import { JSONObject } from "Common/Types/JSON";
import JSONFunctions from "Common/Types/JSONFunctions";
import ObjectID from "Common/Types/ObjectID";
import Phone from "Common/Types/Phone";
import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization";
import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService";
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
} from 'CommonServer/Utils/Express';
import logger from 'CommonServer/Utils/Logger';
import Response from 'CommonServer/Utils/Response';
import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig';
ExpressRequest,
ExpressResponse,
ExpressRouter,
} from "CommonServer/Utils/Express";
import logger from "CommonServer/Utils/Logger";
import Response from "CommonServer/Utils/Response";
import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig";
const router: ExpressRouter = Express.getRouter();
router.post(
'/make-call',
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = JSONFunctions.deserialize(req.body);
"/make-call",
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = JSONFunctions.deserialize(req.body);
await CallService.makeCall(body['callRequest'] as CallRequest, {
projectId: body['projectId'] as ObjectID,
isSensitive: (body['isSensitive'] as boolean) || false,
userOnCallLogTimelineId:
(body['userOnCallLogTimelineId'] as ObjectID) || undefined,
customTwilioConfig: body['customTwilioConfig'] as any,
});
return Response.sendEmptySuccessResponse(req, res);
}
);
router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
const callSMSConfigId: ObjectID = new ObjectID(
body['callSMSConfigId'] as string
);
const config: ProjectCallSMSConfig | null =
await ProjectCallSMSConfigService.findOneById({
id: callSMSConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
projectId: true,
},
});
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'call and sms config not found for id' +
callSMSConfigId.toString()
)
);
}
const toPhone: Phone = new Phone(body['toPhone'] as string);
if (!toPhone) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('toPhone is required')
);
}
// if any of the twilio config is missing, we will not send make the call
if (!config.twilioAccountSID) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('twilioAccountSID is required')
);
}
if (!config.twilioAuthToken) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('twilioAuthToken is required')
);
}
if (!config.twilioPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('twilioPhoneNumber is required')
);
}
const twilioConfig: TwilioConfig | undefined =
ProjectCallSMSConfigService.toTwilioConfig(config);
try {
if (!twilioConfig) {
throw new BadDataException('twilioConfig is undefined');
}
const testCallRequest: CallRequest = {
data: [
{
sayMessage: 'This is a test call from OneUptime.',
},
],
to: toPhone,
};
await CallService.makeCall(testCallRequest, {
projectId: config.projectId,
customTwilioConfig: twilioConfig,
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'Error making test call. Please check the twilio logs for more details'
)
);
}
await CallService.makeCall(body["callRequest"] as CallRequest, {
projectId: body["projectId"] as ObjectID,
isSensitive: (body["isSensitive"] as boolean) || false,
userOnCallLogTimelineId:
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
customTwilioConfig: body["customTwilioConfig"] as any,
});
return Response.sendEmptySuccessResponse(req, res);
},
);
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
const callSMSConfigId: ObjectID = new ObjectID(
body["callSMSConfigId"] as string,
);
const config: ProjectCallSMSConfig | null =
await ProjectCallSMSConfigService.findOneById({
id: callSMSConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
projectId: true,
},
});
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"call and sms config not found for id" + callSMSConfigId.toString(),
),
);
}
const toPhone: Phone = new Phone(body["toPhone"] as string);
if (!toPhone) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("toPhone is required"),
);
}
// if any of the twilio config is missing, we will not send make the call
if (!config.twilioAccountSID) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAccountSID is required"),
);
}
if (!config.twilioAuthToken) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAuthToken is required"),
);
}
if (!config.twilioPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioPhoneNumber is required"),
);
}
const twilioConfig: TwilioConfig | undefined =
ProjectCallSMSConfigService.toTwilioConfig(config);
try {
if (!twilioConfig) {
throw new BadDataException("twilioConfig is undefined");
}
const testCallRequest: CallRequest = {
data: [
{
sayMessage: "This is a test call from OneUptime.",
},
],
to: toPhone,
};
await CallService.makeCall(testCallRequest, {
projectId: config.projectId,
customTwilioConfig: twilioConfig,
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"Error making test call. Please check the twilio logs for more details",
),
);
}
return Response.sendEmptySuccessResponse(req, res);
});
export default router;

View File

@@ -1,65 +1,65 @@
import MailService from '../Services/MailService';
import Dictionary from 'Common/Types/Dictionary';
import Email from 'Common/Types/Email';
import EmailMessage from 'Common/Types/Email/EmailMessage';
import EmailServer from 'Common/Types/Email/EmailServer';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import { JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
import MailService from "../Services/MailService";
import Dictionary from "Common/Types/Dictionary";
import Email from "Common/Types/Email";
import EmailMessage from "Common/Types/Email/EmailMessage";
import EmailServer from "Common/Types/Email/EmailServer";
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
import { JSONObject } from "Common/Types/JSON";
import ObjectID from "Common/Types/ObjectID";
import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization";
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
} from 'CommonServer/Utils/Express';
import Response from 'CommonServer/Utils/Response';
ExpressRequest,
ExpressResponse,
ExpressRouter,
} from "CommonServer/Utils/Express";
import Response from "CommonServer/Utils/Response";
const router: ExpressRouter = Express.getRouter();
router.post(
'/send',
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
"/send",
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
const mail: EmailMessage = {
templateType: body['templateType'] as EmailTemplateType,
toEmail: new Email(body['toEmail'] as string),
subject: body['subject'] as string,
vars: body['vars'] as Dictionary<string>,
body: (body['body'] as string) || '',
};
const mail: EmailMessage = {
templateType: body["templateType"] as EmailTemplateType,
toEmail: new Email(body["toEmail"] as string),
subject: body["subject"] as string,
vars: body["vars"] as Dictionary<string>,
body: (body["body"] as string) || "",
};
let mailServer: EmailServer | undefined = undefined;
let mailServer: EmailServer | undefined = undefined;
if (hasMailServerSettingsInBody(body)) {
mailServer = MailService.getEmailServer(req.body);
}
await MailService.send(mail, {
projectId: body['projectId']
? new ObjectID(body['projectId'] as string)
: undefined,
emailServer: mailServer,
userOnCallLogTimelineId:
(body['userOnCallLogTimelineId'] as ObjectID) || undefined,
});
return Response.sendEmptySuccessResponse(req, res);
if (hasMailServerSettingsInBody(body)) {
mailServer = MailService.getEmailServer(req.body);
}
await MailService.send(mail, {
projectId: body["projectId"]
? new ObjectID(body["projectId"] as string)
: undefined,
emailServer: mailServer,
userOnCallLogTimelineId:
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
});
return Response.sendEmptySuccessResponse(req, res);
},
);
type HasMailServerSettingsInBody = (body: JSONObject) => boolean;
const hasMailServerSettingsInBody: HasMailServerSettingsInBody = (
body: JSONObject
body: JSONObject,
): boolean => {
return (
body &&
Object.keys(body).filter((key: string) => {
return key.startsWith('SMTP_');
}).length > 0
);
return (
body &&
Object.keys(body).filter((key: string) => {
return key.startsWith("SMTP_");
}).length > 0
);
};
export default router;

View File

@@ -1,142 +1,133 @@
import SmsService from '../Services/SmsService';
import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import ObjectID from 'Common/Types/ObjectID';
import Phone from 'Common/Types/Phone';
import ClusterKeyAuthorization from 'CommonServer/Middleware/ClusterKeyAuthorization';
import ProjectCallSMSConfigService from 'CommonServer/Services/ProjectCallSMSConfigService';
import SmsService from "../Services/SmsService";
import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig";
import BadDataException from "Common/Types/Exception/BadDataException";
import { JSONObject } from "Common/Types/JSON";
import JSONFunctions from "Common/Types/JSONFunctions";
import ObjectID from "Common/Types/ObjectID";
import Phone from "Common/Types/Phone";
import ClusterKeyAuthorization from "CommonServer/Middleware/ClusterKeyAuthorization";
import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService";
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
} from 'CommonServer/Utils/Express';
import logger from 'CommonServer/Utils/Logger';
import Response from 'CommonServer/Utils/Response';
import ProjectCallSMSConfig from 'Model/Models/ProjectCallSMSConfig';
ExpressRequest,
ExpressResponse,
ExpressRouter,
} from "CommonServer/Utils/Express";
import logger from "CommonServer/Utils/Logger";
import Response from "CommonServer/Utils/Response";
import ProjectCallSMSConfig from "Model/Models/ProjectCallSMSConfig";
const router: ExpressRouter = Express.getRouter();
router.post(
'/send',
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = JSONFunctions.deserialize(req.body);
"/send",
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = JSONFunctions.deserialize(req.body);
await SmsService.sendSms(
body['to'] as Phone,
body['message'] as string,
{
projectId: body['projectId'] as ObjectID,
isSensitive: (body['isSensitive'] as boolean) || false,
userOnCallLogTimelineId:
(body['userOnCallLogTimelineId'] as ObjectID) || undefined,
customTwilioConfig: body['customTwilioConfig'] as any,
}
);
return Response.sendEmptySuccessResponse(req, res);
}
);
router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
const callSMSConfigId: ObjectID = new ObjectID(
body['callSMSConfigId'] as string
);
const config: ProjectCallSMSConfig | null =
await ProjectCallSMSConfigService.findOneById({
id: callSMSConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
projectId: true,
},
});
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'call and sms config not found for id' +
callSMSConfigId.toString()
)
);
}
const toPhone: Phone = new Phone(body['toPhone'] as string);
if (!toPhone) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('toPhone is required')
);
}
// if any of the twilio config is missing, we will not send make the call
if (!config.twilioAccountSID) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('twilioAccountSID is required')
);
}
if (!config.twilioAuthToken) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('twilioAuthToken is required')
);
}
if (!config.twilioPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('twilioPhoneNumber is required')
);
}
const twilioConfig: TwilioConfig | undefined =
ProjectCallSMSConfigService.toTwilioConfig(config);
try {
if (!twilioConfig) {
throw new BadDataException('twilioConfig is undefined');
}
await SmsService.sendSms(
toPhone,
'This is a test SMS from OneUptime.',
{
projectId: config.projectId,
customTwilioConfig: twilioConfig,
}
);
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'Failed to send test SMS. Please check the twilio logs for more details.'
)
);
}
await SmsService.sendSms(body["to"] as Phone, body["message"] as string, {
projectId: body["projectId"] as ObjectID,
isSensitive: (body["isSensitive"] as boolean) || false,
userOnCallLogTimelineId:
(body["userOnCallLogTimelineId"] as ObjectID) || undefined,
customTwilioConfig: body["customTwilioConfig"] as any,
});
return Response.sendEmptySuccessResponse(req, res);
},
);
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
const callSMSConfigId: ObjectID = new ObjectID(
body["callSMSConfigId"] as string,
);
const config: ProjectCallSMSConfig | null =
await ProjectCallSMSConfigService.findOneById({
id: callSMSConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
projectId: true,
},
});
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"call and sms config not found for id" + callSMSConfigId.toString(),
),
);
}
const toPhone: Phone = new Phone(body["toPhone"] as string);
if (!toPhone) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("toPhone is required"),
);
}
// if any of the twilio config is missing, we will not send make the call
if (!config.twilioAccountSID) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAccountSID is required"),
);
}
if (!config.twilioAuthToken) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioAuthToken is required"),
);
}
if (!config.twilioPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioPhoneNumber is required"),
);
}
const twilioConfig: TwilioConfig | undefined =
ProjectCallSMSConfigService.toTwilioConfig(config);
try {
if (!twilioConfig) {
throw new BadDataException("twilioConfig is undefined");
}
await SmsService.sendSms(toPhone, "This is a test SMS from OneUptime.", {
projectId: config.projectId,
customTwilioConfig: twilioConfig,
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"Failed to send test SMS. Please check the twilio logs for more details.",
),
);
}
return Response.sendEmptySuccessResponse(req, res);
});
export default router;

View File

@@ -1,104 +1,104 @@
import MailService from '../Services/MailService';
import Email from 'Common/Types/Email';
import EmailMessage from 'Common/Types/Email/EmailMessage';
import EmailServer from 'Common/Types/Email/EmailServer';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import ProjectSMTPConfigService from 'CommonServer/Services/ProjectSmtpConfigService';
import MailService from "../Services/MailService";
import Email from "Common/Types/Email";
import EmailMessage from "Common/Types/Email/EmailMessage";
import EmailServer from "Common/Types/Email/EmailServer";
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
import BadDataException from "Common/Types/Exception/BadDataException";
import { JSONObject } from "Common/Types/JSON";
import ObjectID from "Common/Types/ObjectID";
import ProjectSMTPConfigService from "CommonServer/Services/ProjectSmtpConfigService";
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
} from 'CommonServer/Utils/Express';
import logger from 'CommonServer/Utils/Logger';
import Response from 'CommonServer/Utils/Response';
import ProjectSmtpConfig from 'Model/Models/ProjectSmtpConfig';
ExpressRequest,
ExpressResponse,
ExpressRouter,
} from "CommonServer/Utils/Express";
import logger from "CommonServer/Utils/Logger";
import Response from "CommonServer/Utils/Response";
import ProjectSmtpConfig from "Model/Models/ProjectSmtpConfig";
const router: ExpressRouter = Express.getRouter();
router.post('/test', async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
const body: JSONObject = req.body;
const smtpConfigId: ObjectID = new ObjectID(body['smtpConfigId'] as string);
const smtpConfigId: ObjectID = new ObjectID(body["smtpConfigId"] as string);
const config: ProjectSmtpConfig | null =
await ProjectSMTPConfigService.findOneById({
id: smtpConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
hostname: true,
port: true,
username: true,
password: true,
fromEmail: true,
fromName: true,
secure: true,
projectId: true,
},
});
const config: ProjectSmtpConfig | null =
await ProjectSMTPConfigService.findOneById({
id: smtpConfigId,
props: {
isRoot: true,
},
select: {
_id: true,
hostname: true,
port: true,
username: true,
password: true,
fromEmail: true,
fromName: true,
secure: true,
projectId: true,
},
});
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'smtp-config not found for id' + smtpConfigId.toString()
)
);
}
if (!config) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"smtp-config not found for id" + smtpConfigId.toString(),
),
);
}
const toEmail: Email = new Email(body['toEmail'] as string);
const toEmail: Email = new Email(body["toEmail"] as string);
if (!toEmail) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('toEmail is required')
);
}
if (!toEmail) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("toEmail is required"),
);
}
const mail: EmailMessage = {
templateType: EmailTemplateType.SMTPTest,
toEmail: new Email(body['toEmail'] as string),
subject: 'Test Email from OneUptime',
vars: {},
body: '',
};
const mail: EmailMessage = {
templateType: EmailTemplateType.SMTPTest,
toEmail: new Email(body["toEmail"] as string),
subject: "Test Email from OneUptime",
vars: {},
body: "",
};
const mailServer: EmailServer = {
id: config.id!,
host: config.hostname!,
port: config.port!,
username: config.username!,
password: config.password!,
fromEmail: config.fromEmail!,
fromName: config.fromName!,
secure: Boolean(config.secure),
};
const mailServer: EmailServer = {
id: config.id!,
host: config.hostname!,
port: config.port!,
username: config.username!,
password: config.password!,
fromEmail: config.fromEmail!,
fromName: config.fromName!,
secure: Boolean(config.secure),
};
try {
await MailService.send(mail, {
emailServer: mailServer,
projectId: config.projectId!,
timeout: 4000,
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
'Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.'
)
);
}
try {
await MailService.send(mail, {
emailServer: mailServer,
projectId: config.projectId!,
timeout: 4000,
});
} catch (err) {
logger.error(err);
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"Cannot send email. Please check your SMTP config. If you are using Google or Gmail, please dont since it does not support machine access to their mail servers. If you are still having issues, please uncheck SSL/TLS toggle and try again. We recommend using SendGrid or Mailgun or any large volume mail provider for SMTP.",
),
);
}
return Response.sendEmptySuccessResponse(req, res);
return Response.sendEmptySuccessResponse(req, res);
});
export default router;

View File

@@ -1,18 +1,18 @@
import Hostname from 'Common/Types/API/Hostname';
import TwilioConfig from 'Common/Types/CallAndSMS/TwilioConfig';
import Email from 'Common/Types/Email';
import EmailServer from 'Common/Types/Email/EmailServer';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ObjectID from 'Common/Types/ObjectID';
import Port from 'Common/Types/Port';
import GlobalConfigService from 'CommonServer/Services/GlobalConfigService';
import GlobalConfig, { EmailServerType } from 'Model/Models/GlobalConfig';
import Hostname from "Common/Types/API/Hostname";
import TwilioConfig from "Common/Types/CallAndSMS/TwilioConfig";
import Email from "Common/Types/Email";
import EmailServer from "Common/Types/Email/EmailServer";
import BadDataException from "Common/Types/Exception/BadDataException";
import ObjectID from "Common/Types/ObjectID";
import Port from "Common/Types/Port";
import GlobalConfigService from "CommonServer/Services/GlobalConfigService";
import GlobalConfig, { EmailServerType } from "Model/Models/GlobalConfig";
export const InternalSmtpPassword: string =
process.env['INTERNAL_SMTP_PASSWORD'] || '';
process.env["INTERNAL_SMTP_PASSWORD"] || "";
export const InternalSmtpHost: Hostname = new Hostname(
process.env['INTERNAL_SMTP_HOST'] || 'haraka'
process.env["INTERNAL_SMTP_HOST"] || "haraka",
);
export const InternalSmtpPort: Port = new Port(2525);
@@ -20,187 +20,187 @@ export const InternalSmtpPort: Port = new Port(2525);
export const InternalSmtpSecure: boolean = false;
export const InternalSmtpEmail: Email = new Email(
process.env['INTERNAL_SMTP_EMAIL'] || 'noreply@oneuptime.com'
process.env["INTERNAL_SMTP_EMAIL"] || "noreply@oneuptime.com",
);
export const InternalSmtpFromName: string =
process.env['INTERNAL_SMTP_FROM_NAME'] || 'OneUptime';
process.env["INTERNAL_SMTP_FROM_NAME"] || "OneUptime";
type GetGlobalSMTPConfig = () => Promise<EmailServer | null>;
export const getGlobalSMTPConfig: GetGlobalSMTPConfig =
async (): Promise<EmailServer | null> => {
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
},
props: {
isRoot: true,
},
select: {
smtpFromEmail: true,
smtpHost: true,
smtpPort: true,
smtpUsername: true,
smtpPassword: true,
isSMTPSecure: true,
smtpFromName: true,
},
});
async (): Promise<EmailServer | null> => {
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
},
props: {
isRoot: true,
},
select: {
smtpFromEmail: true,
smtpHost: true,
smtpPort: true,
smtpUsername: true,
smtpPassword: true,
isSMTPSecure: true,
smtpFromName: true,
},
});
if (!globalConfig) {
throw new BadDataException('Global Config not found');
}
if (!globalConfig) {
throw new BadDataException("Global Config not found");
}
if (
!globalConfig.smtpFromEmail ||
!globalConfig.smtpHost ||
!globalConfig.smtpPort ||
!globalConfig.smtpUsername ||
!globalConfig.smtpPassword ||
!globalConfig.smtpFromName
) {
return null;
}
if (
!globalConfig.smtpFromEmail ||
!globalConfig.smtpHost ||
!globalConfig.smtpPort ||
!globalConfig.smtpUsername ||
!globalConfig.smtpPassword ||
!globalConfig.smtpFromName
) {
return null;
}
return {
host: globalConfig.smtpHost,
port: globalConfig.smtpPort,
username: globalConfig.smtpUsername,
password: globalConfig.smtpPassword,
secure: globalConfig.isSMTPSecure || false,
fromEmail: globalConfig.smtpFromEmail,
fromName: globalConfig.smtpFromName,
};
return {
host: globalConfig.smtpHost,
port: globalConfig.smtpPort,
username: globalConfig.smtpUsername,
password: globalConfig.smtpPassword,
secure: globalConfig.isSMTPSecure || false,
fromEmail: globalConfig.smtpFromEmail,
fromName: globalConfig.smtpFromName,
};
};
type GetEmailServerTypeFunction = () => Promise<EmailServerType>;
export const getEmailServerType: GetEmailServerTypeFunction =
async (): Promise<EmailServerType> => {
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
},
props: {
isRoot: true,
},
select: {
emailServerType: true,
},
});
async (): Promise<EmailServerType> => {
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
},
props: {
isRoot: true,
},
select: {
emailServerType: true,
},
});
if (!globalConfig) {
return EmailServerType.Internal;
}
if (!globalConfig) {
return EmailServerType.Internal;
}
return globalConfig.emailServerType || EmailServerType.Internal;
};
return globalConfig.emailServerType || EmailServerType.Internal;
};
export interface SendGridConfig {
apiKey: string;
fromName: string;
fromEmail: Email;
apiKey: string;
fromName: string;
fromEmail: Email;
}
type GetSendgridConfigFunction = () => Promise<SendGridConfig | null>;
export const getSendgridConfig: GetSendgridConfigFunction =
async (): Promise<SendGridConfig | null> => {
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
},
props: {
isRoot: true,
},
select: {
sendgridApiKey: true,
sendgridFromEmail: true,
sendgridFromName: true,
},
});
async (): Promise<SendGridConfig | null> => {
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
},
props: {
isRoot: true,
},
select: {
sendgridApiKey: true,
sendgridFromEmail: true,
sendgridFromName: true,
},
});
if (!globalConfig) {
return null;
}
if (!globalConfig) {
return null;
}
if (
globalConfig.sendgridApiKey &&
globalConfig.sendgridFromEmail &&
globalConfig.sendgridFromName
) {
return {
apiKey: globalConfig.sendgridApiKey,
fromName: globalConfig.sendgridFromName,
fromEmail: globalConfig.sendgridFromEmail,
};
}
if (
globalConfig.sendgridApiKey &&
globalConfig.sendgridFromEmail &&
globalConfig.sendgridFromName
) {
return {
apiKey: globalConfig.sendgridApiKey,
fromName: globalConfig.sendgridFromName,
fromEmail: globalConfig.sendgridFromEmail,
};
}
return null;
};
return null;
};
type GetTwilioConfigFunction = () => Promise<TwilioConfig | null>;
export const getTwilioConfig: GetTwilioConfigFunction =
async (): Promise<TwilioConfig | null> => {
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
},
props: {
isRoot: true,
},
select: {
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
},
});
async (): Promise<TwilioConfig | null> => {
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
},
props: {
isRoot: true,
},
select: {
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
},
});
if (!globalConfig) {
throw new BadDataException('Global Config not found');
}
if (!globalConfig) {
throw new BadDataException("Global Config not found");
}
if (
!globalConfig.twilioAccountSID ||
!globalConfig.twilioAuthToken ||
!globalConfig.twilioPhoneNumber
) {
return null;
}
if (
!globalConfig.twilioAccountSID ||
!globalConfig.twilioAuthToken ||
!globalConfig.twilioPhoneNumber
) {
return null;
}
return {
accountSid: globalConfig.twilioAccountSID,
authToken: globalConfig.twilioAuthToken,
phoneNumber: globalConfig.twilioPhoneNumber,
};
return {
accountSid: globalConfig.twilioAccountSID,
authToken: globalConfig.twilioAuthToken,
phoneNumber: globalConfig.twilioPhoneNumber,
};
};
export const SMSDefaultCostInCents: number = process.env[
'SMS_DEFAULT_COST_IN_CENTS'
"SMS_DEFAULT_COST_IN_CENTS"
]
? parseInt(process.env['SMS_DEFAULT_COST_IN_CENTS'])
: 0;
? parseInt(process.env["SMS_DEFAULT_COST_IN_CENTS"])
: 0;
export const SMSHighRiskCostInCents: number = process.env[
'SMS_HIGH_RISK_COST_IN_CENTS'
"SMS_HIGH_RISK_COST_IN_CENTS"
]
? parseInt(process.env['SMS_HIGH_RISK_COST_IN_CENTS'])
: 0;
? parseInt(process.env["SMS_HIGH_RISK_COST_IN_CENTS"])
: 0;
export const CallHighRiskCostInCentsPerMinute: number = process.env[
'CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE'
"CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE"
]
? parseInt(process.env['CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE'])
: 0;
? parseInt(process.env["CALL_HIGH_RISK_COST_IN_CENTS_PER_MINUTE"])
: 0;
export const CallDefaultCostInCentsPerMinute: number = process.env[
'CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE'
"CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE"
]
? parseInt(process.env['CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE'])
: 0;
? parseInt(process.env["CALL_DEFAULT_COST_IN_CENTS_PER_MINUTE"])
: 0;

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