Compare commits

...

123 Commits

Author SHA1 Message Date
Simon Larsen
864d0b3c00 fix fmt 2023-09-27 09:25:41 +01:00
Simon Larsen
14d22e5f12 harden invite emails 2023-09-27 09:24:43 +01:00
Simon Larsen
3dd150692a fix authenticated exception 2023-09-26 16:26:36 +01:00
Simon Larsen
c9b15dcfc7 Merge pull request #774 from OneUptime/new-helm
New helm
2023-09-26 13:42:51 +01:00
Simon Larsen
b039a5a045 fix fmt 2023-09-26 12:42:32 +00:00
Simon Larsen
3040b21484 trim hostname 2023-09-26 12:34:13 +00:00
Simon Larsen
af15c6f5f5 add probe api in helpers 2023-09-26 12:23:57 +00:00
Simon Larsen
4c82c922e2 fix database env vars 2023-09-26 11:37:26 +00:00
Simon Larsen
08f48ad082 fix port serialization 2023-09-26 11:29:24 +00:00
Simon Larsen
34a8ea806d limit_max for subscribers 2023-09-26 11:21:07 +01:00
Simon Larsen
a70e98f802 fix status page reset password 2023-09-26 11:06:59 +01:00
Simon Larsen
ee1ec87781 fix status page private user invites 2023-09-26 10:33:44 +01:00
Simon Larsen
f21de699dd fix helm chart 2023-09-25 15:07:43 +00:00
Simon Larsen
b30f9a472a fix helm env vars 2023-09-25 14:40:11 +00:00
Simon Larsen
e7c54b369d fix env vars 2023-09-25 13:25:50 +00:00
Simon Larsen
53bf92fac0 add size and pvc 2023-09-25 12:52:42 +00:00
Simon Larsen
6da56df5b1 fix env vars 2023-09-25 12:42:21 +00:00
Simon Larsen
931cccf86a add ports 2023-09-25 11:44:57 +00:00
Simon Larsen
027966cae3 add common env vars 2023-09-25 11:15:00 +00:00
Simon Larsen
f568473588 add env to notifications 2023-09-25 11:12:22 +00:00
Simon Larsen
ada26e3cce update readme 2023-09-25 10:47:23 +00:00
Simon Larsen
15e2c9cef2 add ingress and haraka 2023-09-25 10:34:26 +00:00
Simon Larsen
a091cd4faa Add deps 2023-09-25 10:04:07 +00:00
Simon Larsen
1fa5604cdd add deps 2023-09-25 09:08:55 +00:00
Simon Larsen
d9ed5f579e fix issues 2023-09-25 08:32:29 +00:00
Simon Larsen
0138e98506 fix ports 2023-09-24 13:13:24 +00:00
Simon Larsen
2feb024032 add more service files 2023-09-24 13:04:01 +00:00
Simon Larsen
55bf11bfd1 refactor into helpers 2023-09-24 12:28:45 +00:00
Simon Larsen
05d6dd2182 add service template 2023-09-24 12:18:58 +01:00
Simon Larsen
3595f5bf6f add helm lint command 2023-09-24 12:07:31 +01:00
Simon Larsen
398c08854a refactor helm service 2023-09-24 09:14:24 +01:00
Simon Larsen
af8d85f6d2 refactor pod env 2023-09-23 13:49:13 +01:00
Simon Larsen
296dfd15d5 add common ui 2023-09-23 13:46:22 +01:00
Simon Larsen
efc446edf1 add more secrets to helm 2023-09-23 12:35:24 +00:00
Simon Larsen
8453d32a4f add common env to home 2023-09-23 13:03:33 +01:00
Simon Larsen
41a8ddb09a remove deprecated docs 2023-09-23 12:52:41 +01:00
Simon Larsen
9f5fa3542a refactor readme 2023-09-23 12:51:16 +01:00
Simon Larsen
b801aba506 add custom probes to growth plan 2023-09-23 12:44:42 +01:00
Simon Larsen
c6e5d642b5 fix: Monitor remains permanently disabled if you delete an active incident before resolving it 2023-09-23 12:29:41 +01:00
Simon Larsen
002abb7498 add retry logic to register a probe 2023-09-22 21:30:10 +01:00
Simon Larsen
0e141b9b1a add retry logic to register a probe 2023-09-22 21:28:55 +01:00
Simon Larsen
230ccc4144 add template 2023-09-22 17:51:03 +00:00
Simon Larsen
ac4d2cc9ec install oneuptime home 2023-09-22 17:02:34 +00:00
Simon Larsen
a1f12fd14a update readme 2023-09-22 17:34:03 +01:00
Simon Larsen
3e4ad34179 add microk8s readme 2023-09-22 17:19:10 +01:00
Simon Larsen
a21bde486b add readme 2023-09-22 16:55:36 +01:00
Simon Larsen
4adb2b58ca add helm chart readme 2023-09-22 16:54:04 +01:00
Simon Larsen
929c39dea7 remove unneeded files 2023-09-22 16:49:56 +01:00
Simon Larsen
206c7d9bf1 add try catch in cron time parser 2023-09-22 16:36:04 +01:00
Simon Larsen
e16c9cb3b7 fix fmt 2023-09-22 13:04:54 +01:00
Simon Larsen
542fb4355e add cache option for lint 2023-09-22 12:53:26 +01:00
Simon Larsen
f63b910d78 add more probe logs 2023-09-22 12:49:51 +01:00
Simon Larsen
8254b635fb add probe logs 2023-09-22 12:34:02 +01:00
Simon Larsen
dfa85982c2 fix fmt 2023-09-22 09:41:24 +01:00
Simon Larsen
8451f9f90d fix api docs url 2023-09-22 09:19:39 +01:00
Simon Larsen
c447941755 fix tests 2023-09-22 09:15:43 +01:00
Simon Larsen
112e2e4faa add 1 second between retries 2023-09-22 08:15:45 +01:00
Simon Larsen
fed9ab4621 fix database connect 2023-09-21 20:58:20 +01:00
Simon Larsen
18461d58d6 add comment to LIMIT_MAX 2023-09-21 20:34:48 +01:00
Simon Larsen
11095fc0bc add version 2023-09-21 20:31:00 +01:00
Simon Larsen
a567cee47f fix lint 2023-09-21 18:15:21 +01:00
Simon Larsen
158e2abc12 add app version 2023-09-21 18:01:40 +01:00
Simon Larsen
ca2095e867 refresh next function 2023-09-21 17:16:34 +01:00
Simon Larsen
708a9ba4b8 increase npm timeout in docker files 2023-09-21 17:09:09 +01:00
Simon Larsen
91a60eabbb fix fmt 2023-09-21 17:07:55 +01:00
Simon Larsen
7f0e07bd40 implement retry if the server is 500 status 2023-09-21 17:02:50 +01:00
Simon Larsen
01b17b9dff Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2023-09-21 13:06:37 +01:00
Simon Larsen
b7e5cf78b9 fix fmt 2023-09-21 13:06:06 +01:00
Simon Larsen
d92d8c260b add retry 2023-09-21 12:53:20 +01:00
Simon Larsen
0576199db8 add exception 2023-09-21 12:52:04 +01:00
Simon Larsen
6f5a804d77 refactor retry 2023-09-21 12:48:03 +01:00
Simon Larsen
2f6420152e add retry if there's packet loss 2023-09-21 12:39:36 +01:00
Simon Larsen
1d1d11bee2 Merge pull request #770 from OneUptime/helm-chart
Helm chart
2023-09-21 11:00:35 +01:00
Simon Larsen
594c36a512 fix common server test 2023-09-21 09:51:25 +01:00
Simon Larsen
ec5c852175 deprecate old charts and add new chart 2023-09-20 21:23:52 +01:00
Simon Larsen
01ff01029b fix fmt 2023-09-20 18:51:19 +01:00
Simon Larsen
bf5d16b64c fix fmt 2023-09-20 18:37:43 +01:00
Simon Larsen
26ee469467 change p to div to not apply p styles 2023-09-20 18:21:59 +01:00
Simon Larsen
b514c8c189 fix user details in master admin 2023-09-20 18:20:20 +01:00
Simon Larsen
2bbac9a545 fix api key perms 2023-09-20 18:00:30 +01:00
Simon Larsen
03386eeba0 add permissions to see the model. 2023-09-20 16:08:17 +01:00
Simon Larsen
a09842c8b0 add master key request perms 2023-09-20 15:38:07 +01:00
Simon Larsen
847b75b555 add api key ui to admin dash 2023-09-20 15:22:18 +01:00
Simon Larsen
c839317283 remove dup file 2023-09-20 11:08:32 +01:00
Simon Larsen
1f4fd86845 fix fmt 2023-09-20 10:58:20 +01:00
Simon Larsen
8ed8c6a05c add config.example 2023-09-20 09:57:13 +01:00
Simon Larsen
a716d54cc6 remove host settings from admin dash 2023-09-20 09:51:32 +01:00
Simon Larsen
668d00418d fix issue with has invitation accepted 2023-09-20 07:52:45 +01:00
Simon Larsen
5c36fa851c remove token secret 2023-09-14 14:14:27 +05:30
Simon Larsen
4352ada83e remove unused vars 2023-09-14 13:55:17 +05:30
Simon Larsen
c6d76c4bb0 remove unused vars 2023-09-14 13:53:31 +05:30
Simon Larsen
87a1a84d2e remove unused vars 2023-09-14 13:53:07 +05:30
Simon Larsen
b02e622fd3 fix hostname in config.env 2023-09-14 11:47:56 +05:30
Simon Larsen
ac7e6b915f remove hostname and route from config.env 2023-09-14 11:46:49 +05:30
Simon Larsen
f3e1dccfc1 fix fmt on env config 2023-09-14 10:15:02 +05:30
Simon Larsen
8683ac7677 add admin dash status check 2023-09-14 10:11:37 +05:30
Simon Larsen
eccb65f930 remove env vars from env config. 2023-09-14 09:58:45 +05:30
Simon Larsen
c1c27a387c fix reseller plan id 2023-09-13 23:23:57 +05:30
Simon Larsen
aece287747 fix docker files 2023-09-13 22:30:39 +05:30
Simon Larsen
d6ff2c12fb fix docker files 2023-09-13 21:40:25 +05:30
Simon Larsen
50c8fe003d fix fmt 2023-09-13 21:17:32 +05:30
Simon Larsen
3e6b16fcf6 add smtp in admin dash 2023-09-13 21:04:54 +05:30
Simon Larsen
ce43514ee3 add sendgrid api key 2023-09-13 20:28:53 +05:30
Simon Larsen
8318f09e26 fix mail server 2023-09-13 20:18:46 +05:30
Simon Larsen
7711902edd add email server type 2023-09-13 20:14:42 +05:30
Simon Larsen
94ffa754eb add use internal smtp server 2023-09-13 19:20:16 +05:30
Simon Larsen
48035ddec0 add plan type to model table 2023-09-13 16:30:52 +05:30
Simon Larsen
7694abe05e fix accounts webpack config 2023-09-13 16:11:31 +05:30
Simon Larsen
8eac47c4f9 fix typo nothing 2023-09-13 15:53:30 +05:30
Simon Larsen
fe90b50862 fix global config api 2023-09-13 15:53:04 +05:30
Simon Larsen
e3f2eaa3c6 fix fmt 2023-09-13 15:33:41 +05:30
Simon Larsen
fc09c689bc fix fmt 2023-09-13 15:13:12 +05:30
Simon Larsen
faf04a726c fix compile in common ui 2023-09-13 14:49:44 +05:30
Simon Larsen
31e04a26ff fix docker files 2023-09-13 14:28:57 +05:30
Simon Larsen
90ea8ebee9 remove pptr 2023-09-13 14:11:48 +05:30
Simon Larsen
4d2e66fce3 fix base 2023-09-13 13:56:36 +05:30
Simon Larsen
6057fafd97 fix docker files 2023-09-13 13:55:54 +05:30
Simon Larsen
57671c444c refactor docker files 2023-09-13 13:53:26 +05:30
Simon Larsen
11a3111098 fix compile err in identity 2023-09-13 13:40:10 +05:30
Simon Larsen
e74c711dfd fix compie err in workers 2023-09-13 13:38:50 +05:30
Simon Larsen
4f64693550 remove axios from webpack 2023-09-13 13:37:37 +05:30
Simon Larsen
2336961178 Merge branch 'master' of github.com-simon:OneUptime/oneuptime 2023-09-13 13:35:19 +05:30
Simon Larsen
c7938f62ae Merge pull request #746 from OneUptime/admin-dashboard
Admin dashboard
2023-09-13 13:10:45 +05:30
212 changed files with 5614 additions and 3061 deletions

View File

@@ -8,7 +8,21 @@ on:
- 'release'
jobs:
lint:
helm-lint:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Install Helm
run: |
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
- name: Lint Helm Chart
run: |
helm lint ./HelmChart/public/oneuptime
js-lint:
runs-on: ubuntu-latest
env:
CI_PIPELINE_ID: ${{github.run_number}}

2
.gitignore vendored
View File

@@ -88,3 +88,5 @@ Backups/*.tar
Haraka/dkim/keys/private_base64.txt
Haraka/dkim/keys/public_base64.txt
.eslintcache

View File

@@ -3,10 +3,13 @@
#
# Pull base image nodejs image.
FROM node:current-alpine
FROM node:current-alpine AS base
USER root
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retry-maxtimeout 6000000
RUN npm config set fetch-retry-mintimeout 1000000
ARG GIT_SHA
ARG APP_VERSION
@@ -24,7 +27,8 @@ SHELL ["/bin/bash", "-c"]
RUN mkdir /usr/src
# Install common
RUN mkdir /usr/src/Common
FROM base AS common
WORKDIR /usr/src/Common
COPY ./Common/package*.json /usr/src/Common/
RUN npm install
@@ -33,7 +37,8 @@ COPY ./Common /usr/src/Common
# Install Model
RUN mkdir /usr/src/Model
FROM base AS model
WORKDIR /usr/src/Model
COPY ./Model/package*.json /usr/src/Model/
RUN npm install
@@ -42,7 +47,8 @@ COPY ./Model /usr/src/Model
# Install CommonServer
RUN mkdir /usr/src/CommonServer
FROM base AS commonserver
WORKDIR /usr/src/CommonServer
COPY ./CommonServer/package*.json /usr/src/CommonServer/
RUN npm install
@@ -52,7 +58,8 @@ COPY ./CommonServer /usr/src/CommonServer
# Install CommonUI
RUN mkdir /usr/src/CommonUI
FROM base AS commonui
WORKDIR /usr/src/CommonUI
COPY ./CommonUI/package*.json /usr/src/CommonUI/
RUN npm install --force
@@ -61,11 +68,25 @@ COPY ./CommonUI /usr/src/CommonUI
#SET ENV Variables
# Install app
FROM base AS app
WORKDIR /usr/src/Common
COPY --from=common /usr/src/Common .
WORKDIR /usr/src/Model
COPY --from=model /usr/src/Model .
WORKDIR /usr/src/CommonServer
COPY --from=commonserver /usr/src/CommonServer .
WORKDIR /usr/src/CommonUI
COPY --from=commonui /usr/src/CommonUI .
ENV PRODUCTION=true
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies

View File

@@ -21,7 +21,7 @@ export default abstract class LoginUtil {
UserUtil.setAccessToken(token);
UserUtil.setEmail(user.email as Email);
UserUtil.setUserId(user.id as ObjectID);
UserUtil.setName(user.name as Name);
UserUtil.setName(user.name || new Name(''));
UserUtil.setIsMasterAdmin(user.isMasterAdmin as boolean);
Analytics.userAuth(user.email!);

View File

@@ -2,13 +2,13 @@ const path = require("path");
const webpack = require("webpack");
const dotenv = require('dotenv');
const express = require('express');
const axios = require('axios');
const readEnvFile = async (pathToFile) => {
const readEnvFile = (pathToFile) => {
const parsed = dotenv.config({ path: pathToFile }).parsed;
const env = {};
const env = {
};
for (const key in parsed) {
env[key] = JSON.stringify(parsed[key]);
@@ -17,67 +17,62 @@ const readEnvFile = async (pathToFile) => {
return env;
}
const webpackConfig = async () => {
return {
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'),
module.exports = {
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'),
}
},
externals: {
'react-native-sqlite-storage': 'react-native-sqlite-storage'
},
plugins: [
new webpack.DefinePlugin({
'process': {
'env': {
...readEnvFile('/usr/src/app/dev-env/.env')
}
}
},
externals: {
'react-native-sqlite-storage': 'react-native-sqlite-storage'
},
plugins: [
new webpack.DefinePlugin({
'process': {
'env': {
...(await 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,
}),
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader'
},
allowedHosts: "all",
setupMiddlewares: (middlewares, devServer) => {
devServer.app.use('/accounts/assets', express.static(path.resolve(__dirname, 'public', 'assets')));
return middlewares;
{
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',
}
};
module.exports = webpackConfig;
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

@@ -3,10 +3,13 @@
#
# Pull base image nodejs image.
FROM node:current-alpine
FROM node:current-alpine AS base
USER root
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retry-maxtimeout 6000000
RUN npm config set fetch-retry-mintimeout 1000000
ARG GIT_SHA
ARG APP_VERSION
@@ -24,7 +27,8 @@ SHELL ["/bin/bash", "-c"]
RUN mkdir /usr/src
# Install common
RUN mkdir /usr/src/Common
FROM base AS common
WORKDIR /usr/src/Common
COPY ./Common/package*.json /usr/src/Common/
RUN npm install
@@ -32,7 +36,8 @@ COPY ./Common /usr/src/Common
# Install Model
RUN mkdir /usr/src/Model
FROM base AS model
WORKDIR /usr/src/Model
COPY ./Model/package*.json /usr/src/Model/
RUN npm install
@@ -41,7 +46,8 @@ COPY ./Model /usr/src/Model
# Install CommonServer
RUN mkdir /usr/src/CommonServer
FROM base AS commonserver
WORKDIR /usr/src/CommonServer
COPY ./CommonServer/package*.json /usr/src/CommonServer/
RUN npm install
@@ -51,7 +57,8 @@ COPY ./CommonServer /usr/src/CommonServer
# Install CommonUI
RUN mkdir /usr/src/CommonUI
FROM base AS commonui
WORKDIR /usr/src/CommonUI
COPY ./CommonUI/package*.json /usr/src/CommonUI/
RUN npm install --force
@@ -59,11 +66,25 @@ COPY ./CommonUI /usr/src/CommonUI
#SET ENV Variables
# Install app
FROM base AS app
WORKDIR /usr/src/Common
COPY --from=common /usr/src/Common .
WORKDIR /usr/src/Model
COPY --from=model /usr/src/Model .
WORKDIR /usr/src/CommonServer
COPY --from=commonserver /usr/src/CommonServer .
WORKDIR /usr/src/CommonUI
COPY --from=commonui /usr/src/CommonUI .
ENV PRODUCTION=true
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies

View File

@@ -24,6 +24,7 @@ import SettingsEmail from './Pages/Settings/SMTP/Index';
import SettingsCallSMS from './Pages/Settings/CallSMS/Index';
import SettingsProbes from './Pages/Settings/Probes/Index';
import SettingsAuthentication from './Pages/Settings/Authentication/Index';
import SettingsAPIKey from './Pages/Settings/APIKey/Index';
const App: () => JSX.Element = () => {
Navigation.setNavigateHook(useNavigate());
@@ -105,6 +106,11 @@ const App: () => JSX.Element = () => {
}
element={<SettingsAuthentication />}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_API_KEY]?.toString() || ''}
element={<SettingsAPIKey />}
/>
</Routes>
</MasterPage>
);

View File

@@ -0,0 +1,102 @@
import Route from 'Common/Types/API/Route';
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 React, { FunctionComponent, ReactElement } from 'react';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import DashboardSideMenu from '../SideMenu';
import GlobalConfig from 'Model/Models/GlobalConfig';
import ObjectID from 'Common/Types/ObjectID';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
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>
);
};
export default Settings;

View File

@@ -47,20 +47,6 @@ const Settings: FunctionComponent = (): ReactElement => {
isEditable={true}
editButtonText="Edit Host"
formFields={[
{
field: {
host: true,
},
title: 'Host',
fieldType: FormFieldSchemaType.Text,
required: true,
description:
'IP address or Hostname of this server instance.',
placeholder: 'oneuptime.yourcompany.com',
validation: {
minLength: 2,
},
},
{
field: {
useHttps: true,
@@ -76,15 +62,6 @@ const Settings: FunctionComponent = (): ReactElement => {
modelType: GlobalConfig,
id: 'model-detail-global-config',
fields: [
{
field: {
host: true,
},
title: 'Host',
placeholder: 'None',
description:
'IP address or Hostname of this server instance.',
},
{
field: {
useHttps: true,

View File

@@ -2,15 +2,65 @@ import Route from 'Common/Types/API/Route';
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 React, { FunctionComponent, ReactElement } from 'react';
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import DashboardSideMenu from '../SideMenu';
import GlobalConfig from 'Model/Models/GlobalConfig';
import GlobalConfig, { EmailServerType } from 'Model/Models/GlobalConfig';
import ObjectID from 'Common/Types/ObjectID';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import PageLoader from 'CommonUI/src/Components/Loader/PageLoader';
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import { JSONObject } from 'Common/Types/JSON';
import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
import Pill from 'CommonUI/src/Components/Pill/Pill';
import { Green, Red } from 'Common/Types/BrandColors';
const Settings: FunctionComponent = (): ReactElement => {
const [emailServerType, setemailServerType] =
React.useState<EmailServerType>(EmailServerType.Internal);
const [isLoading, setIsLoading] = React.useState<boolean>(true);
const [error, setError] = React.useState<string>('');
const fetchItem: Function = async (): Promise<void> => {
setIsLoading(true);
const globalConfig: GlobalConfig | null =
await ModelAPI.getItem<GlobalConfig>(
GlobalConfig,
ObjectID.getZeroObjectID(),
{
_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 (error) {
return <ErrorMessage error={error} />;
}
return (
<Page
title={'Admin Settings'}
@@ -37,102 +87,31 @@ const Settings: FunctionComponent = (): ReactElement => {
sideMenu={<DashboardSideMenu />}
>
{/* Project Settings View */}
<CardModelDetail
name="Host Settings"
name="Internal SMTP Settings"
cardProps={{
title: 'Email and SMTP Settings',
title: 'Email Server Settings',
description:
'Email and SMTP Settings. We will use this SMTP server to send all the emails.',
'Pick which email server you would like to use to send emails.',
}}
isEditable={true}
editButtonText="Edit SMTP Config"
formSteps={[
{
title: 'SMTP Server',
id: 'server-info',
},
{
title: 'Authentication',
id: 'authentication',
},
{
title: 'Email',
id: 'email-info',
},
]}
editButtonText="Edit Server"
onSaveSuccess={() => {
window.location.reload();
}}
formFields={[
{
field: {
smtpHost: true,
emailServerType: true,
},
title: 'Hostname',
stepId: 'server-info',
fieldType: FormFieldSchemaType.Hostname,
title: 'Email Server Type',
fieldType: FormFieldSchemaType.Dropdown,
dropdownOptions:
DropdownUtil.getDropdownOptionsFromEnum(
EmailServerType
),
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: 'Make email communication secure?',
},
{
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={{
@@ -141,53 +120,269 @@ const Settings: FunctionComponent = (): ReactElement => {
fields: [
{
field: {
smtpHost: true,
emailServerType: 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,
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: 'Make email communication secure?',
},
{
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
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: JSONObject) => {
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>
);
};

View File

@@ -63,6 +63,17 @@ const DashboardSideMenu: () => JSX.Element = (): ReactElement => {
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>
);
};

View File

@@ -11,6 +11,7 @@ enum PageMap {
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

@@ -18,6 +18,7 @@ const RouteMap: Dictionary<Route> = {
[PageMap.SETTINGS_AUTHENTICATION]: new Route(
`/admin/settings/authentication`
),
[PageMap.SETTINGS_API_KEY]: new Route(`/admin/settings/api-key`),
};
export class RouteUtil {

View File

@@ -1,8 +1,11 @@
# Pull base image nodejs image.
FROM node:current-alpine
FROM node:current-alpine AS base
USER root
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retry-maxtimeout 6000000
RUN npm config set fetch-retry-mintimeout 1000000
ARG GIT_SHA
ARG APP_VERSION
@@ -20,7 +23,8 @@ SHELL ["/bin/bash", "-c"]
RUN mkdir /usr/src
# Install common
RUN mkdir /usr/src/Common
FROM base AS common
WORKDIR /usr/src/Common
COPY ./Common/package*.json /usr/src/Common/
RUN npm install
@@ -28,7 +32,8 @@ COPY ./Common /usr/src/Common
# Install Model
RUN mkdir /usr/src/Model
FROM base AS model
WORKDIR /usr/src/Model
COPY ./Model/package*.json /usr/src/Model/
RUN npm install
@@ -37,7 +42,8 @@ COPY ./Model /usr/src/Model
# Install CommonServer
RUN mkdir /usr/src/CommonServer
FROM base AS commonserver
WORKDIR /usr/src/CommonServer
COPY ./CommonServer/package*.json /usr/src/CommonServer/
RUN npm install
@@ -45,9 +51,21 @@ COPY ./CommonServer /usr/src/CommonServer
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
# Install app
FROM base AS app
RUN mkdir /usr/src/app
WORKDIR /usr/src/Common
COPY --from=common /usr/src/Common .
WORKDIR /usr/src/Model
COPY --from=model /usr/src/Model .
WORKDIR /usr/src/CommonServer
COPY --from=commonserver /usr/src/CommonServer .
ENV PRODUCTION=true
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
WORKDIR /usr/src/app

View File

@@ -229,6 +229,9 @@ export default class ServiceHandler {
DashboardApiRoute.toString() +
currentResource.model.crudApiPath?.toString();
pageData.isMasterAdminApiDocs =
currentResource.model.isMasterAdminApiDocs;
return res.render('pages/index', {
page: page,
resources: Resources,

View File

@@ -2,6 +2,7 @@ import BaseModel from 'Common/Models/BaseModel';
import Models from 'Model/Models/Index';
import ArrayUtil from 'Common/Types/ArrayUtil';
import Dictionary from 'Common/Types/Dictionary';
import { IsBillingEnabled } from 'CommonServer/EnvironmentConfig';
export interface ModelDocumentation {
name: string;
@@ -15,7 +16,13 @@ export default class ResourceUtil {
const resources: Array<ModelDocumentation> = Models.filter(
(model: typeof BaseModel) => {
const modelInstance: BaseModel = new model();
return modelInstance.enableDocumentation;
let showDocs: boolean = modelInstance.enableDocumentation;
if (modelInstance.isMasterAdminApiDocs && IsBillingEnabled) {
showDocs = false;
}
return showDocs;
}
)
.map((model: typeof BaseModel) => {

View File

@@ -37,7 +37,6 @@
"nodemon": "^2.0.20",
"npm-force-resolutions": "0.0.10",
"ora": "^6.1.0",
"puppeteer": "^13.5.1",
"start-server-and-test": "^1.14.0",
"ts-jest": "^28.0.2",
"ts-node-dev": "^1.1.8",
@@ -59,7 +58,7 @@
"moment-timezone": "^0.5.40",
"nanoid": "^3.3.2",
"nanoid-dictionary": "^4.3.0",
"posthog-js": "^1.37.0",
"posthog-js": "^1.77.0",
"process": "^0.11.10",
"reflect-metadata": "^0.1.13",
"slugify": "^1.6.5",
@@ -67,7 +66,7 @@
"uuid": "^8.3.2"
},
"devDependencies": {
"@faker-js/faker": "^6.3.1",
"@faker-js/faker": "^8.0.2",
"@types/jest": "^27.5.2",
"@types/node": "^17.0.22",
"jest": "^27.5.1",
@@ -79,6 +78,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@clickhouse/client": "^0.2.1",
"@elastic/elasticsearch": "^8.1.0",
"@opentelemetry/api": "^1.1.0",
"@opentelemetry/auto-instrumentations-node": "^0.31.0",
@@ -811,6 +811,21 @@
"node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
}
},
"node_modules/@jest/console/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@jest/console/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -827,6 +842,24 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@jest/console/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@jest/console/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/@jest/console/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -896,6 +929,21 @@
}
}
},
"node_modules/@jest/core/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@jest/core/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -912,6 +960,24 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@jest/core/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@jest/core/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/@jest/core/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -1096,6 +1162,21 @@
}
}
},
"node_modules/@jest/reporters/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@jest/reporters/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1112,6 +1193,24 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@jest/reporters/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@jest/reporters/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/@jest/reporters/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -1333,6 +1432,24 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@jest/types/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@jest/types/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/@jest/types/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -2758,7 +2875,8 @@
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/colors": {
"version": "1.0.3",
@@ -2841,6 +2959,11 @@
"ms": "2.0.0"
}
},
"node_modules/compression/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/compression/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -3549,6 +3672,11 @@
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
@@ -4395,6 +4523,11 @@
"node": ">=7.0.0"
}
},
"node_modules/jake/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/jake/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -5529,6 +5662,21 @@
"node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
}
},
"node_modules/jest-resolve/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/jest-resolve/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -5545,6 +5693,24 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/jest-resolve/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/jest-resolve/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/jest-resolve/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -6026,6 +6192,12 @@
"node": ">=7.0.0"
}
},
"node_modules/jest-util/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/jest-util/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -6478,6 +6650,20 @@
"integrity": "sha512-vlCUxxQAB8Nu6LQHqPpDRiMi06Du593/my/6JbMttQeEfJ7pf4OS8obSTh5xSOS80U/O7fq59Q8rQGAUxQatUQ==",
"dev": true
},
"node_modules/lighthouse/node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/lighthouse/node_modules/semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -6769,7 +6955,8 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/multimatch": {
"version": "5.0.0",
@@ -7453,31 +7640,6 @@
"once": "^1.3.1"
}
},
"node_modules/puppeteer": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.7.0.tgz",
"integrity": "sha512-U1uufzBjz3+PkpCxFrWzh4OrMIdIb2ztzCu0YEPfRHjHswcSwHZswnK+WdsOQJsRV8WeTg3jLhJR4D867+fjsA==",
"deprecated": "< 18.1.0 is no longer supported",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"cross-fetch": "3.1.5",
"debug": "4.3.4",
"devtools-protocol": "0.0.981744",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"pkg-dir": "4.2.0",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
"rimraf": "3.0.2",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3",
"ws": "8.5.0"
},
"engines": {
"node": ">=10.18.1"
}
},
"node_modules/puppeteer-core": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.7.0.tgz",
@@ -7537,42 +7699,6 @@
}
}
},
"node_modules/puppeteer/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/puppeteer/node_modules/ws": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
"integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
"dev": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@@ -8937,6 +9063,24 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/wrap-ansi/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -9625,6 +9769,15 @@
"slash": "^3.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -9635,6 +9788,21 @@
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -9689,6 +9857,15 @@
"strip-ansi": "^6.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -9699,6 +9876,21 @@
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -9840,6 +10032,15 @@
"v8-to-istanbul": "^9.0.1"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -9850,6 +10051,21 @@
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -10018,6 +10234,21 @@
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -11165,7 +11396,8 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"colors": {
"version": "1.0.3",
@@ -11190,7 +11422,7 @@
"Common": {
"version": "file:../Common",
"requires": {
"@faker-js/faker": "^6.3.1",
"@faker-js/faker": "^8.0.2",
"@types/crypto-js": "^4.1.1",
"@types/jest": "^27.5.2",
"@types/nanoid-dictionary": "^4.2.0",
@@ -11204,7 +11436,7 @@
"moment-timezone": "^0.5.40",
"nanoid": "^3.3.2",
"nanoid-dictionary": "^4.3.0",
"posthog-js": "^1.37.0",
"posthog-js": "^1.77.0",
"process": "^0.11.10",
"reflect-metadata": "^0.1.13",
"slugify": "^1.6.5",
@@ -11216,6 +11448,7 @@
"CommonServer": {
"version": "file:../CommonServer",
"requires": {
"@clickhouse/client": "^0.2.1",
"@elastic/elasticsearch": "^8.1.0",
"@faker-js/faker": "^6.3.1",
"@opentelemetry/api": "^1.1.0",
@@ -11297,6 +11530,11 @@
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -11861,6 +12099,11 @@
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}
}
},
@@ -12481,6 +12724,11 @@
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -13309,6 +13557,15 @@
"slash": "^3.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -13319,6 +13576,21 @@
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -13703,6 +13975,12 @@
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -14024,6 +14302,17 @@
"yargs-parser": "^21.0.0"
},
"dependencies": {
"cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -14296,7 +14585,8 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"multimatch": {
"version": "5.0.0",
@@ -14781,44 +15071,6 @@
"once": "^1.3.1"
}
},
"puppeteer": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.7.0.tgz",
"integrity": "sha512-U1uufzBjz3+PkpCxFrWzh4OrMIdIb2ztzCu0YEPfRHjHswcSwHZswnK+WdsOQJsRV8WeTg3jLhJR4D867+fjsA==",
"dev": true,
"requires": {
"cross-fetch": "3.1.5",
"debug": "4.3.4",
"devtools-protocol": "0.0.981744",
"extract-zip": "2.0.1",
"https-proxy-agent": "5.0.1",
"pkg-dir": "4.2.0",
"progress": "2.0.3",
"proxy-from-env": "1.1.0",
"rimraf": "3.0.2",
"tar-fs": "2.1.1",
"unbzip2-stream": "1.4.3",
"ws": "8.5.0"
},
"dependencies": {
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"ws": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
"integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
"dev": true,
"requires": {}
}
}
},
"puppeteer-core": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.7.0.tgz",
@@ -15857,6 +16109,21 @@
"requires": {
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
}
}
},

View File

@@ -46,7 +46,7 @@
"nodemon": "^2.0.20",
"npm-force-resolutions": "0.0.10",
"ora": "^6.1.0",
"puppeteer": "^13.5.1",
"start-server-and-test": "^1.14.0",
"ts-jest": "^28.0.2",
"ts-node-dev": "^1.1.8",

View File

@@ -9,8 +9,12 @@
<h2>Permissions</h2>
<% if(!pageData.isMasterAdminApiDocs){ %>
<p class="lead"> Your API Token needs permissions to create, update, read or delete this resource. If you do not have permissions to make a request a <code class="inline-code">4xx</code> status will be sent as response. </p>
<h3 id="consuming-webhooks" >
Read Permissions
@@ -138,6 +142,28 @@
</div>
<% } %>
<% if(pageData.isMasterAdminApiDocs){ %>
<div class="border-l-4 border-yellow-400 bg-yellow-50 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<div class="text-sm font-medium text-yellow-700 mb-0">
This API can only be accessed through a Master API Token. You can create one on the Admin Dashboard. Please add the token to the <code class="inline-code">ApiKey</code> header to make the request.
</div>
</div>
</div>
</div>
<% } %>
<h2 id="the-contact-model" class="scroll-mt-24">
The <%= pageData.title -%> model

View File

@@ -110,6 +110,7 @@ export default class BaseModel extends BaseEntity {
public enableWorkflowOn!: EnableWorkflowOn;
public enableDocumentation!: boolean;
public isMasterAdminApiDocs!: boolean;
public currentUserCanAccessColumnBy!: string | null;
public labelsColumn!: string | null;

View File

@@ -24,7 +24,7 @@ export const AccountsRoute: Route = new Route('/accounts');
export const WorkflowRoute: Route = new Route('/workflow');
export const ApiReferenceRoute: Route = new Route('/api-reference');
export const ApiReferenceRoute: Route = new Route('/reference');
export const AdminDashboardRoute: Route = new Route('/admin');

View File

@@ -20,6 +20,8 @@ export default class Hostname extends DatabaseProperty {
}
public set hostname(value: string) {
value = value.trim();
if (Hostname.isValid(value)) {
this._route = value;
} else {

View File

@@ -1,5 +1,4 @@
const LIMIT_MAX: number = 10000;
export const LIMIT_PER_PROJECT: number = 300;
export const DEFAULT_LIMIT: number = 10;
export default LIMIT_MAX;

View File

@@ -7,6 +7,7 @@ enum ExceptionCode {
WebRequestException = 6,
BadDataException = 400,
BadRequestException = 400,
UnabletoReachServerException = 415,
ServerException = 500,
NotAuthorizedException = 403,
NotAuthenticatedException = 401,

View File

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

View File

@@ -1,5 +1,11 @@
export default () => {
export interface EnableDocumentationProps {
isMasterAdminApiDocs?: boolean | undefined;
}
export default (props?: EnableDocumentationProps | undefined) => {
return (ctr: Function) => {
ctr.prototype.enableDocumentation = true;
ctr.prototype.isMasterAdminApiDocs =
props?.isMasterAdminApiDocs || false;
};
};

View File

@@ -19,7 +19,10 @@ import DatabaseCommonInteractionProps from 'Common/Types/Database/DatabaseCommon
import Query from '../Types/Database/Query';
import Select from '../Types/Database/Select';
import Sort from '../Types/Database/Sort';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import {
DEFAULT_LIMIT,
LIMIT_PER_PROJECT,
} from 'Common/Types/Database/LimitMax';
import PartialEntity from 'Common/Types/Database/PartialEntity';
import { UserPermission } from 'Common/Types/Permission';
import { IsBillingEnabled } from '../EnvironmentConfig';
@@ -283,7 +286,7 @@ export default class BaseAPI<
const limit: PositiveNumber = req.query['limit']
? new PositiveNumber(req.query['limit'] as string)
: new PositiveNumber(10);
: new PositiveNumber(DEFAULT_LIMIT);
if (limit.toNumber() > LIMIT_PER_PROJECT) {
throw new BadRequestException(

View File

@@ -1,54 +0,0 @@
import Probe from 'Model/Models/Probe';
import ProbeService, {
Service as ProbeServiceType,
} from '../Services/ProbeService';
import {
ExpressRequest,
ExpressResponse,
NextFunction,
} from '../Utils/Express';
import Response from '../Utils/Response';
import BaseAPI from './BaseAPI';
import GlobalConfigService from '../Services/GlobalConfigService';
import ObjectID from 'Common/Types/ObjectID';
import GlobalConfig from 'Model/Models/GlobalConfig';
import BadDataException from 'Common/Types/Exception/BadDataException';
export default class ProbeAPI extends BaseAPI<Probe, ProbeServiceType> {
public constructor() {
super(Probe, ProbeService);
this.router.get(
`${new this.entityType().getCrudApiPath()?.toString()}/vars`,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
const globalConfig: GlobalConfig | null = await GlobalConfigService.findOneById({
id: ObjectID.getZeroObjectID(),
select: {
host: true,
useHttps: true,
},
props: {
isRoot: true,
},
});
if(!globalConfig){
return Response.sendErrorResponse(req, res, new BadDataException("Global Config not found"));
}
return Response.sendJsonObjectResponse(req, res, {
HOST: globalConfig?.host?.toString() || 'localhost',
USE_HTTPS: globalConfig?.useHttps || false,
});
} catch (err) {
next(err);
}
}
);
}
}

View File

@@ -1,52 +1,46 @@
import Probe from 'Model/Models/Probe';
import ProbeService, {
Service as ProbeServiceType,
} from '../Services/ProbeService';
import GlobalConfig from 'Model/Models/GlobalConfig';
import GlobalConfigService, {
Service as GlobalConfigServiceType,
} from '../Services/GlobalConfigService';
import {
ExpressRequest,
ExpressResponse,
ExpressResponse,
NextFunction,
} from '../Utils/Express';
import Response from '../Utils/Response';
import BaseAPI from './BaseAPI';
import GlobalConfigService from '../Services/GlobalConfigService';
import ObjectID from 'Common/Types/ObjectID';
export default class ProbeAPI extends BaseAPI<Probe, ProbeServiceType> {
export default class GlobalConfigAPI extends BaseAPI<
GlobalConfig,
GlobalConfigServiceType
> {
public constructor() {
super(Probe, ProbeService);
super(GlobalConfig, GlobalConfigService);
this.router.get(
`${new this.entityType()
.getCrudApiPath()
?.toString()}/vars`,
`${new this.entityType().getCrudApiPath()?.toString()}/vars`,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
const globalConfig = await GlobalConfigService.findOneById({
id: ObjectID.getZeroObjectID(),
select: {
host: true,
useHttps: true
},
props: {
isRoot: true
}
const globalConfig: GlobalConfig | null =
await GlobalConfigService.findOneById({
id: ObjectID.getZeroObjectID(),
select: {
useHttps: true,
},
props: {
isRoot: true,
},
});
return Response.sendJsonObjectResponse(req, res, {
USE_HTTPS:
globalConfig?.useHttps?.toString() || 'false',
});
return Response.sendJsonObjectResponse(
req,
res,
{
'HOST': globalConfig?.host?.toString() || 'localhost',
'USE_HTTPS': globalConfig?.useHttps?.toString() || "false"
},
);
} catch (err) {
next(err);
}

View File

@@ -12,10 +12,10 @@ import {
} from '../Utils/Express';
import TeamMemberService from '../Services/TeamMemberService';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import NotAuthorizedException from 'Common/Types/Exception/NotAuthorizedException';
import PositiveNumber from 'Common/Types/PositiveNumber';
import Response from '../Utils/Response';
import TeamMember from 'Model/Models/TeamMember';
import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException';
export default class ProjectAPI extends BaseAPI<Project, ProjectServiceType> {
public constructor() {
@@ -35,7 +35,7 @@ export default class ProjectAPI extends BaseAPI<Project, ProjectServiceType> {
) => {
try {
if (!(req as OneUptimeRequest).userAuthorization?.userId) {
throw new NotAuthorizedException(
throw new NotAuthenticatedException(
'User should be logged in to access this API'
);
}
@@ -45,6 +45,7 @@ export default class ProjectAPI extends BaseAPI<Project, ProjectServiceType> {
query: {
userId: (req as OneUptimeRequest)
.userAuthorization!.userId!,
hasAcceptedInvitation: true,
},
select: {
project: {

View File

@@ -205,6 +205,7 @@ export default class ResellerPlanAPI extends BaseAPI<
data: {
activeMonitorsLimit: resellerPlan.monitorLimit!,
seatLimit: resellerPlan.teamMemberLimit!,
resellerPlanId: resellerPlan.id!,
},
props: {
isRoot: true,

View File

@@ -15,7 +15,7 @@ import Response from '../Utils/Response';
import NotAuthenticatedException from 'Common/Types/Exception/NotAuthenticatedException';
import BadDataException from 'Common/Types/Exception/BadDataException';
import StatusPageFooterLinkService from '../Services/StatusPageFooterLinkService';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import StatusPageFooterLink from 'Model/Models/StatusPageFooterLink';
import StatusPageHeaderLinkService from '../Services/StatusPageHeaderLinkService';
import StatusPageHeaderLink from 'Model/Models/StatusPageHeaderLink';
@@ -553,7 +553,7 @@ export default class StatusPageAPI extends BaseAPI<
createdAt: SortOrder.Ascending,
},
skip: 0,
limit: LIMIT_PER_PROJECT,
limit: LIMIT_MAX, // This can be optimized.
props: {
isRoot: true,
},

View File

@@ -31,9 +31,8 @@ export default class DatabaseConfig {
}
public static async getHost(): Promise<Hostname> {
return (
((await DatabaseConfig.getFromGlobalConfig('host')) as Hostname) ||
new Hostname('localhost')
return Promise.resolve(
new Hostname(process.env['HOST'] || 'localhost')
);
}

View File

@@ -62,49 +62,28 @@ export const ClusterKey: ObjectID = new ObjectID(
export const HasClusterKey: boolean = Boolean(process.env['ONEUPTIME_SECRET']);
export const RealtimeHostname: Hostname = Hostname.fromString(
process.env['REALTIME_HOSTNAME'] || 'realtime'
);
export const RealtimeHostname: Hostname = Hostname.fromString('realtime:3300');
export const NotificationHostname: Hostname = Hostname.fromString(
process.env['NOTIFICATION_HOSTNAME'] || 'notification'
);
export const NotificationHostname: Hostname =
Hostname.fromString('notification:3191');
export const WorkerHostname: Hostname = Hostname.fromString(
process.env['WORKER_HOSTNAME'] || 'worker'
);
export const WorkerHostname: Hostname = Hostname.fromString('worker:3452');
export const LinkShortenerHostname: Route = new Route(
process.env['LINK_SHORTENER_HOSTNAME'] || 'link-shortener'
);
export const LinkShortenerHostname: Route = new Route('link-shortener:3521');
export const WorkflowHostname: Hostname = Hostname.fromString(
process.env['WORKFLOW_HOSTNAME'] || 'workflow'
);
export const WorkflowHostname: Hostname = Hostname.fromString('workflow:3099');
export const DashboardApiHostname: Hostname = Hostname.fromString(
process.env['DASHBOARD_API_HOSTNAME'] || 'dashboard-api'
);
export const DashboardApiHostname: Hostname =
Hostname.fromString('dashboard-api:3002');
export const ProbeApiHostname: Hostname = Hostname.fromString(
process.env['PROBE_API_HOSTNAME'] || 'probe-api'
);
export const ProbeApiHostname: Hostname = Hostname.fromString('probe-api:3400');
export const DataIngestorHostname: Hostname = Hostname.fromString(
process.env['DATA_INGESTOR_HOSTNAME'] || 'daat-ingestor'
);
export const AccountsHostname: Hostname = Hostname.fromString('accounts:3003');
export const AccountsHostname: Hostname = Hostname.fromString(
process.env['ACCOUNTS_HOSTNAME'] || 'accounts'
);
export const HomeHostname: Hostname = Hostname.fromString('home:1444');
export const HomeHostname: Hostname = Hostname.fromString(
process.env['HOME_HOSTNAME'] || 'home'
);
export const DashboardHostname: Hostname = Hostname.fromString(
process.env['DASHBOARD_HOSTNAME'] || 'dashboard'
);
export const DashboardHostname: Hostname =
Hostname.fromString('dashboard:3000');
export const Env: string = process.env['NODE_ENV'] || 'production';

View File

@@ -14,8 +14,12 @@ import UserType from 'Common/Types/UserType';
import AccessTokenService from '../Services/AccessTokenService';
import { UserTenantAccessPermission } from 'Common/Types/Permission';
import Dictionary from 'Common/Types/Dictionary';
import Response from '../Utils/Response';
import QueryHelper from '../Types/Database/QueryHelper';
import GlobalConfigService from '../Services/GlobalConfigService';
import User from 'Model/Models/User';
import UserService from '../Services/UserService';
import GlobalConfig from 'Model/Models/GlobalConfig';
import logger from '../Utils/Logger';
export default class ProjectMiddleware {
public static getProjectId(req: ExpressRequest): ObjectID | null {
@@ -55,67 +59,132 @@ export default class ProjectMiddleware {
public static async isValidProjectIdAndApiKeyMiddleware(
req: ExpressRequest,
res: ExpressResponse,
_res: ExpressResponse,
next: NextFunction
): Promise<void> {
const tenantId: ObjectID | null = this.getProjectId(req);
try {
const tenantId: ObjectID | null = this.getProjectId(req);
const apiKey: ObjectID | null = this.getApiKey(req);
logger.info('tenantId', tenantId);
if (!tenantId) {
throw new BadDataException('ProjectId not found in the request');
}
const apiKey: ObjectID | null = this.getApiKey(req);
if (!apiKey) {
throw new BadDataException('ApiKey not found in the request');
}
const apiKeyModel: ApiKey | null = await ApiKeyService.findOneBy({
query: {
projectId: tenantId,
apiKey: apiKey,
expiresAt: QueryHelper.greaterThan(
OneUptimeDate.getCurrentDate()
),
},
select: {
_id: true,
},
props: { isRoot: true },
});
if (apiKeyModel) {
(req as OneUptimeRequest).userType = UserType.API;
// TODO: Add API key permissions.
// (req as OneUptimeRequest).permissions =
// apiKeyModel.permissions || [];
(req as OneUptimeRequest).tenantId = tenantId;
(req as OneUptimeRequest).userGlobalAccessPermission =
await AccessTokenService.getDefaultApiGlobalPermission(
tenantId
);
const userTenantAccessPermission: UserTenantAccessPermission | null =
await AccessTokenService.getApiTenantAccessPermission(
tenantId,
apiKeyModel.id!
);
if (userTenantAccessPermission) {
(req as OneUptimeRequest).userTenantAccessPermission = {};
(
(req as OneUptimeRequest)
.userTenantAccessPermission as Dictionary<UserTenantAccessPermission>
)[tenantId.toString()] = userTenantAccessPermission;
return next();
if (tenantId) {
(req as OneUptimeRequest).tenantId = tenantId;
}
}
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid Project ID or API Key')
);
if (!apiKey) {
throw new BadDataException('ApiKey not found in the request');
}
let apiKeyModel: ApiKey | null = null;
if (tenantId) {
apiKeyModel = await ApiKeyService.findOneBy({
query: {
projectId: tenantId,
apiKey: apiKey,
expiresAt: QueryHelper.greaterThan(
OneUptimeDate.getCurrentDate()
),
},
select: {
_id: true,
},
props: { isRoot: true },
});
if (apiKeyModel) {
(req as OneUptimeRequest).userType = UserType.API;
// TODO: Add API key permissions.
// (req as OneUptimeRequest).permissions =
// apiKeyModel.permissions || [];
(req as OneUptimeRequest).userGlobalAccessPermission =
await AccessTokenService.getDefaultApiGlobalPermission(
tenantId
);
const userTenantAccessPermission: UserTenantAccessPermission | null =
await AccessTokenService.getApiTenantAccessPermission(
tenantId,
apiKeyModel.id!
);
if (userTenantAccessPermission) {
(req as OneUptimeRequest).userTenantAccessPermission =
{};
(
(req as OneUptimeRequest)
.userTenantAccessPermission as Dictionary<UserTenantAccessPermission>
)[tenantId.toString()] = userTenantAccessPermission;
return next();
}
}
}
if (!apiKeyModel) {
// check master key.
const masterKeyGlobalConfig: GlobalConfig | null =
await GlobalConfigService.findOneBy({
query: {
_id: ObjectID.getZeroObjectID().toString(),
isMasterApiKeyEnabled: true,
masterApiKey: apiKey,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (masterKeyGlobalConfig) {
(req as OneUptimeRequest).userType = UserType.MasterAdmin;
// get master admin user
const user: User | null = await UserService.findOneBy({
query: {
isMasterAdmin: true,
},
select: {
_id: true,
email: true,
name: true,
},
props: {
isRoot: true,
},
});
if (!user) {
throw new BadDataException(
'Master Admin user not found. Please make sure you have created a master admin user.'
);
}
(req as OneUptimeRequest).userAuthorization = {
userId: user.id!,
isMasterAdmin: true,
email: user.email!,
name: user.name!,
};
return next();
}
}
if (!tenantId) {
throw new BadDataException(
'ProjectID not found in the request header.'
);
}
throw new BadDataException('Invalid Project ID or API Key');
} catch (err) {
next(err);
}
}
}

View File

@@ -75,6 +75,40 @@ export class AccessTokenService extends BaseService {
};
}
public async getMasterKeyApiGlobalPermission(
projectId: ObjectID
): Promise<UserGlobalAccessPermission> {
return {
projectIds: [projectId],
globalPermissions: [
Permission.Public,
Permission.User,
Permission.CurrentUser,
Permission.ProjectOwner,
],
_type: 'UserGlobalAccessPermission',
};
}
public async getMasterApiTenantAccessPermission(
projectId: ObjectID
): Promise<UserTenantAccessPermission> {
const userPermissions: Array<UserPermission> = [];
userPermissions.push({
permission: Permission.ProjectOwner,
labelIds: [],
_type: 'UserPermission',
});
const permission: UserTenantAccessPermission =
this.getDefaultUserTenantAccessPermission(projectId);
permission.permissions = permission.permissions.concat(userPermissions);
return permission;
}
public async getApiTenantAccessPermission(
projectId: ObjectID,
apiKeyId: ObjectID

View File

@@ -965,6 +965,7 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
const onDelete: OnDelete<TBaseModel> = deleteBy.props.ignoreHooks
? { deleteBy, carryForward: [] }
: await this.onBeforeDelete(deleteBy);
const beforeDeleteBy: DeleteBy<TBaseModel> = onDelete.deleteBy;
const carryForward: any = onDelete.carryForward;

View File

@@ -1,6 +1,10 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/Incident';
import DatabaseService, { OnCreate, OnUpdate } from './DatabaseService';
import DatabaseService, {
OnCreate,
OnDelete,
OnUpdate,
} from './DatabaseService';
import ObjectID from 'Common/Types/ObjectID';
import Monitor from 'Model/Models/Monitor';
import MonitorService from './MonitorService';
@@ -19,13 +23,20 @@ import Typeof from 'Common/Types/Typeof';
import URL from 'Common/Types/API/URL';
import User from 'Model/Models/User';
import TeamMemberService from './TeamMemberService';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import LIMIT_MAX, { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import UserService from './UserService';
import { JSONObject } from 'Common/Types/JSON';
import OnCallDutyPolicyService from './OnCallDutyPolicyService';
import UserNotificationEventType from 'Common/Types/UserNotification/UserNotificationEventType';
import SortOrder from 'Common/Types/Database/SortOrder';
import DatabaseConfig from '../DatabaseConfig';
import MonitorStatus from 'Model/Models/MonitorStatus';
import MonitorStatusService from './MonitorStatusService';
import PositiveNumber from 'Common/Types/PositiveNumber';
import QueryHelper from '../Types/Database/QueryHelper';
import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline';
import MonitorStatusTimelineService from './MonitorStatusTimelineService';
import DeleteBy from '../Types/Database/DeleteBy';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -411,6 +422,177 @@ export class Service extends DatabaseService<Model> {
return onUpdate;
}
public async doesMonitorHasMoreActiveManualIncidents(
monitorId: ObjectID,
proojectId: ObjectID
): Promise<boolean> {
const resolvedState: IncidentState | null =
await IncidentStateService.findOneBy({
query: {
projectId: proojectId,
isResolvedState: true,
},
props: {
isRoot: true,
},
select: {
_id: true,
order: true,
},
});
const incidentCount: PositiveNumber = await this.countBy({
query: {
monitors: QueryHelper.inRelationArray([monitorId]),
currentIncidentState: {
order: QueryHelper.lessThan(resolvedState?.order!),
},
isCreatedAutomatically: false,
},
props: {
isRoot: true,
},
});
return incidentCount.toNumber() > 0;
}
public async markMonitorsActiveForMonitoring(
projectId: ObjectID,
monitors: Array<Monitor>
): Promise<void> {
// resolve all the monitors.
if (monitors.length > 0) {
// get resolved monitor state.
const resolvedMonitorState: MonitorStatus | null =
await MonitorStatusService.findOneBy({
query: {
projectId: projectId!,
isOperationalState: true,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (resolvedMonitorState) {
for (const monitor of monitors) {
//check state of the monitor.
const doesMonitorHasMoreActiveManualIncidents: boolean =
await this.doesMonitorHasMoreActiveManualIncidents(
monitor.id!,
projectId!
);
if (doesMonitorHasMoreActiveManualIncidents) {
continue;
}
await MonitorService.updateOneById({
id: monitor.id!,
data: {
disableActiveMonitoringBecauseOfManualIncident:
false,
},
props: {
isRoot: true,
},
});
const latestState: MonitorStatusTimeline | null =
await MonitorStatusTimelineService.findOneBy({
query: {
monitorId: monitor.id!,
projectId: projectId!,
},
select: {
_id: true,
monitorStatusId: true,
},
props: {
isRoot: true,
},
sort: {
createdAt: SortOrder.Descending,
},
});
if (
latestState &&
latestState.monitorStatusId?.toString() ===
resolvedMonitorState.id!.toString()
) {
// already on this state. Skip.
continue;
}
const monitorStatusTimeline: MonitorStatusTimeline =
new MonitorStatusTimeline();
monitorStatusTimeline.monitorId = monitor.id!;
monitorStatusTimeline.projectId = projectId!;
monitorStatusTimeline.monitorStatusId =
resolvedMonitorState.id!;
await MonitorStatusTimelineService.create({
data: monitorStatusTimeline,
props: {
isRoot: true,
},
});
}
}
}
}
protected override async onBeforeDelete(
deleteBy: DeleteBy<Model>
): Promise<OnDelete<Model>> {
const incidents: Array<Model> = await this.findBy({
query: deleteBy.query,
limit: LIMIT_MAX,
skip: 0,
select: {
projectId: true,
monitors: {
_id: true,
},
},
props: {
isRoot: true,
},
});
return {
deleteBy,
carryForward: {
incidents: incidents,
},
};
}
protected override async onDeleteSuccess(
onDelete: OnDelete<Model>,
_itemIdsBeforeDelete: ObjectID[]
): Promise<OnDelete<Model>> {
if (onDelete.carryForward && onDelete.carryForward.incidents) {
for (const incident of onDelete.carryForward.incidents) {
if (incident.monitors && incident.monitors.length > 0) {
await this.markMonitorsActiveForMonitoring(
incident.projectId!,
incident.monitors
);
}
}
}
return onDelete;
}
public async changeIncidentState(
projectId: ObjectID,
incidentId: ObjectID,

View File

@@ -9,16 +9,11 @@ import PositiveNumber from 'Common/Types/PositiveNumber';
import SortOrder from 'Common/Types/Database/SortOrder';
import IncidentState from 'Model/Models/IncidentState';
import IncidentStateService from './IncidentStateService';
import Incident from 'Model/Models/Incident';
import MonitorStatusService from './MonitorStatusService';
import MonitorStatus from 'Model/Models/MonitorStatus';
import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline';
import MonitorStatusTimelineService from './MonitorStatusTimelineService';
import CreateBy from '../Types/Database/CreateBy';
import UserService from './UserService';
import User from 'Model/Models/User';
import MonitorService from './MonitorService';
import QueryHelper from '../Types/Database/QueryHelper';
import Incident from 'Model/Models/Incident';
export class Service extends DatabaseService<IncidentStateTimeline> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -132,10 +127,9 @@ export class Service extends DatabaseService<IncidentStateTimeline> {
});
if (isResolvedState) {
// resolve all the monitors.
const incident: Incident | null = await IncidentService.findOneBy({
query: {
_id: createdItem.incidentId?.toString(),
_id: createdItem.incidentId.toString(),
},
select: {
_id: true,
@@ -149,130 +143,17 @@ export class Service extends DatabaseService<IncidentStateTimeline> {
},
});
if (incident && incident.monitors && incident.monitors.length > 0) {
// get resolved monitor state.
const resolvedMonitorState: MonitorStatus | null =
await MonitorStatusService.findOneBy({
query: {
projectId: incident.projectId!,
isOperationalState: true,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (resolvedMonitorState) {
for (const monitor of incident.monitors) {
//check state of the monitor.
const doesMonitorHasMoreActiveManualIncidents: boolean =
await this.doesMonitorHasMoreActiveManualIncidents(
monitor.id!,
incident.projectId!
);
if (doesMonitorHasMoreActiveManualIncidents) {
continue;
}
await MonitorService.updateOneById({
id: monitor.id!,
data: {
disableActiveMonitoringBecauseOfManualIncident:
false,
},
props: {
isRoot: true,
},
});
const latestState: MonitorStatusTimeline | null =
await MonitorStatusTimelineService.findOneBy({
query: {
monitorId: monitor.id!,
projectId: incident.projectId!,
},
select: {
_id: true,
monitorStatusId: true,
},
props: {
isRoot: true,
},
sort: {
createdAt: SortOrder.Descending,
},
});
if (
latestState &&
latestState.monitorStatusId?.toString() ===
resolvedMonitorState.id!.toString()
) {
// already on this state. Skip.
continue;
}
const monitorStatusTimeline: MonitorStatusTimeline =
new MonitorStatusTimeline();
monitorStatusTimeline.monitorId = monitor.id!;
monitorStatusTimeline.projectId = incident.projectId!;
monitorStatusTimeline.monitorStatusId =
resolvedMonitorState.id!;
await MonitorStatusTimelineService.create({
data: monitorStatusTimeline,
props: {
isRoot: true,
},
});
}
}
if (incident) {
await IncidentService.markMonitorsActiveForMonitoring(
incident.projectId!,
incident.monitors || []
);
}
}
return createdItem;
}
public async doesMonitorHasMoreActiveManualIncidents(
monitorId: ObjectID,
proojectId: ObjectID
): Promise<boolean> {
const resolvedState: IncidentState | null =
await IncidentStateService.findOneBy({
query: {
projectId: proojectId,
isResolvedState: true,
},
props: {
isRoot: true,
},
select: {
_id: true,
order: true,
},
});
const incidentCount: PositiveNumber = await IncidentService.countBy({
query: {
monitors: QueryHelper.inRelationArray([monitorId]),
currentIncidentState: {
order: QueryHelper.lessThan(resolvedState?.order!),
},
isCreatedAutomatically: false,
},
props: {
isRoot: true,
},
});
return incidentCount.toNumber() > 0;
}
protected override async onBeforeDelete(
deleteBy: DeleteBy<IncidentStateTimeline>
): Promise<OnDelete<IncidentStateTimeline>> {

View File

@@ -14,12 +14,44 @@ import { FileRoute } from 'Common/ServiceRoute';
import DatabaseConfig from '../DatabaseConfig';
import Hostname from 'Common/Types/API/Hostname';
import Protocol from 'Common/Types/API/Protocol';
import CreateBy from '../Types/Database/CreateBy';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
protected override async onBeforeCreate(
createBy: CreateBy<Model>
): Promise<OnCreate<Model>> {
// check if this user is already invited.
if (createBy.data.statusPageId && createBy.data.email) {
const statusPageUser: Model | null = await this.findOneBy({
query: {
email: createBy.data.email,
statusPageId: createBy.data.statusPageId,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
if (statusPageUser) {
throw new BadDataException(
'This user is already invited to this status page.'
);
}
}
return {
createBy: createBy,
carryForward: null,
};
}
protected override async onCreateSuccess(
_onCreate: OnCreate<Model>,
createdItem: Model

View File

@@ -6,7 +6,7 @@ import BadDataException from 'Common/Types/Exception/BadDataException';
import StatusPageService from './StatusPageService';
import MailService from './MailService';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import URL from 'Common/Types/API/URL';
import { FileRoute } from 'Common/ServiceRoute';
import DatabaseConfig from '../DatabaseConfig';
@@ -175,7 +175,7 @@ export class Service extends DatabaseService<Model> {
subscriberWebhook: true,
},
skip: 0,
limit: LIMIT_PER_PROJECT,
limit: LIMIT_MAX,
props: props,
});
}

View File

@@ -16,7 +16,7 @@ import QueryHelper from '../Types/Database/QueryHelper';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import ProjectService from './ProjectService';
import { IsBillingEnabled } from '../EnvironmentConfig';
import { DashboardRoute } from 'Common/ServiceRoute';
import { AccountsRoute } from 'Common/ServiceRoute';
import DatabaseConfig from '../DatabaseConfig';
import BillingService from './BillingService';
import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
@@ -78,7 +78,10 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
isRoot: true,
});
let isNewUser: boolean = false;
if (!user) {
isNewUser = true;
user = await UserService.createByEmail(email, {
isRoot: true,
});
@@ -106,11 +109,24 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
toEmail: email,
templateType: EmailTemplateType.InviteMember,
vars: {
dashboardUrl: new URL(
httpProtocol,
host,
DashboardRoute
signInLink: URL.fromString(
new URL(
httpProtocol,
host,
AccountsRoute
).toString()
).toString(),
registerLink: URL.fromString(
new URL(
httpProtocol,
host,
AccountsRoute
).toString()
)
.addRoute('/register')
.addQueryParam('email', email.toString())
.toString(),
isNewUser: isNewUser.toString(),
projectName: project.name!,
homeUrl: new URL(httpProtocol, host).toString(),
},

View File

@@ -1,3 +1,4 @@
import '../TestingUtils/Init';
import ObjectID from 'Common/Types/ObjectID';
import ProjectMiddleware from '../../Middleware/ProjectAuthorization';
import {
@@ -6,13 +7,14 @@ import {
NextFunction,
} from '../../Utils/Express';
import ApiKeyService from '../../Services/ApiKeyService';
import Response from '../../Utils/Response';
import BadDataException from 'Common/Types/Exception/BadDataException';
import OneUptimeDate from 'Common/Types/Date';
import QueryHelper from '../../Types/Database/QueryHelper';
import ApiKey from 'Model/Models/ApiKey';
import AccessTokenService from '../../Services/AccessTokenService';
import { UserTenantAccessPermission } from 'Common/Types/Permission';
import Database from '../TestingUtils/Database';
import GlobalConfigService from '../../Services/GlobalConfigService';
jest.mock('../../Services/ApiKeyService');
jest.mock('../../Services/AccessTokenService');
@@ -97,110 +99,123 @@ describe('ProjectMiddleware', () => {
const req: ExpressRequest = { headers: {} } as ExpressRequest;
test('should return true when getApiKey returns a non-null value', () => {
const spyGetApiKey: jest.SpyInstance = jest
.spyOn(ProjectMiddleware, 'getApiKey')
.mockReturnValue(mockedObjectId);
req.headers['apikey'] = mockedObjectId.toString();
const result: boolean = ProjectMiddleware.hasApiKey(req);
expect(result).toStrictEqual(true);
expect(spyGetApiKey).toHaveBeenCalledWith(req);
});
test('should return false when getApiKey returns null', () => {
const spyGetApiKey: jest.SpyInstance = jest
.spyOn(ProjectMiddleware, 'getApiKey')
.mockReturnValue(null);
req.headers['apikey'] = undefined;
const result: boolean = ProjectMiddleware.hasApiKey(req);
expect(result).toStrictEqual(false);
expect(spyGetApiKey).toHaveBeenCalledWith(req);
});
});
describe('hasProjectID', () => {
const req: ExpressRequest = { headers: {} } as ExpressRequest;
test('should return true when getProjectId returns a non-null value', () => {
const spyGetProjectId: jest.SpyInstance = jest
.spyOn(ProjectMiddleware, 'getProjectId')
.mockReturnValue(mockedObjectId);
req.headers['tenantid'] = mockedObjectId.toString();
const result: boolean = ProjectMiddleware.hasProjectID(req);
expect(result).toStrictEqual(true);
expect(spyGetProjectId).toHaveBeenCalledWith(req);
});
test('should return false when getProjectId returns null', () => {
const spyGetProjectId: jest.SpyInstance = jest
.spyOn(ProjectMiddleware, 'getProjectId')
.mockReturnValue(null);
req.headers['tenantid'] = undefined;
const result: boolean = ProjectMiddleware.hasProjectID(req);
expect(result).toStrictEqual(false);
expect(spyGetProjectId).toHaveBeenCalledWith(req);
});
});
describe('isValidProjectIdAndApiKeyMiddleware', () => {
const req: ExpressRequest = {} as ExpressRequest;
const res: ExpressResponse = {} as ExpressResponse;
const next: NextFunction = jest.fn();
let next: NextFunction = jest.fn();
const mockedApiModel: ApiKey = {
id: mockedObjectId,
} as ApiKey;
beforeAll(() => {
jest.spyOn(ProjectMiddleware, 'getProjectId').mockReturnValue(
mockedObjectId
);
jest.spyOn(ProjectMiddleware, 'getApiKey').mockReturnValue(
mockedObjectId
);
let database!: Database;
beforeEach(async () => {
jest.clearAllMocks();
next = jest.fn();
database = new Database();
await database.createAndConnect();
if (req.headers === undefined) {
req.headers = {};
}
req.headers['tenantid'] = mockedObjectId.toString();
req.headers['apikey'] = mockedObjectId.toString();
});
afterEach(async () => {
await database.disconnectAndDropDatabase();
});
test('should throw BadDataException when getProjectId returns null', async () => {
const spyGetProjectId: jest.SpyInstance = jest
.spyOn(ProjectMiddleware, 'getProjectId')
.mockReturnValueOnce(null);
const spyFindOneBy: jest.SpyInstance = jest
.spyOn(GlobalConfigService, 'findOneBy')
.mockResolvedValue(null);
await expect(
ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware(
req,
res,
next
req.headers['tenantid'] = undefined;
req.headers['apikey'] = mockedObjectId.toString();
await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware(
req,
res,
next
);
expect(spyFindOneBy).toHaveBeenCalledWith({
query: {
_id: ObjectID.getZeroObjectID().toString(),
isMasterApiKeyEnabled: true,
masterApiKey: mockedObjectId,
},
props: {
isRoot: true,
},
select: {
_id: true,
},
});
expect(next).toHaveBeenCalledWith(
new BadDataException(
'ProjectID not found in the request header.'
)
).rejects.toThrowError('ProjectId not found in the request');
expect(spyGetProjectId).toHaveBeenCalledWith(req);
);
});
test('should throw BadDataException when getApiKey returns null', async () => {
const spyGetApiKey: jest.SpyInstance = jest
.spyOn(ProjectMiddleware, 'getApiKey')
.mockReturnValueOnce(null);
req.headers['apikey'] = undefined;
await expect(
ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware(
req,
res,
next
)
).rejects.toThrowError('ApiKey not found in the request');
await ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware(
req,
res,
next
);
expect(spyGetApiKey).toHaveBeenCalledWith(req);
expect(next).toHaveBeenCalledWith(
new BadDataException('ApiKey not found in the request')
);
});
test('should call Response.sendErrorResponse when apiKeyModel is null', async () => {
const spyFindOneBy: jest.SpyInstance = jest
.spyOn(ApiKeyService, 'findOneBy')
.mockResolvedValue(null);
const spySendErrorResponse: jest.SpyInstance = jest
.spyOn(Response, 'sendErrorResponse')
.mockImplementation(jest.fn);
jest.spyOn(QueryHelper, 'greaterThan').mockImplementation(
jest.fn()
@@ -226,18 +241,12 @@ describe('ProjectMiddleware', () => {
props: { isRoot: true },
});
expect(spySendErrorResponse).toHaveBeenCalledWith(
req,
res,
expect(next).toHaveBeenCalledWith(
new BadDataException('Invalid Project ID or API Key')
);
});
test('should call Response.sendErrorResponse when apiKeyModel is not null but getApiTenantAccessPermission returned null', async () => {
const spySendErrorResponse: jest.SpyInstance = jest
.spyOn(Response, 'sendErrorResponse')
.mockImplementation(jest.fn);
jest.spyOn(ApiKeyService, 'findOneBy').mockResolvedValue(
mockedApiModel
);
@@ -252,9 +261,8 @@ describe('ProjectMiddleware', () => {
);
expect(spyGetApiTenantAccessPermission).toHaveBeenCalled();
expect(spySendErrorResponse).toHaveBeenCalledWith(
req,
res,
// check first param of next
expect(next).toHaveBeenCalledWith(
new BadDataException('Invalid Project ID or API Key')
);
});

View File

@@ -54,6 +54,14 @@ export default class ModelPermission {
query: Query<TBaseModel>,
props: DatabaseCommonInteractionProps
): Promise<Query<TBaseModel>> {
if (props.isRoot) {
query = await this.addTenantScopeToQueryAsRoot(
modelType,
query,
props
);
}
if (!props.isRoot) {
this.checkModelLevelPermissions(
modelType,
@@ -265,6 +273,14 @@ export default class ModelPermission {
): Promise<CheckReadPermissionType<TBaseModel>> {
const model: BaseModel = new modelType();
if (props.isRoot) {
query = await this.addTenantScopeToQueryAsRoot(
modelType,
query,
props
);
}
if (!props.isRoot) {
//check if the user is logged in.
this.checkIfUserIsLoggedIn(
@@ -804,6 +820,25 @@ export default class ModelPermission {
}
}
private static async addTenantScopeToQueryAsRoot<
TBaseModel extends BaseModel
>(
modelType: { new (): TBaseModel },
query: Query<TBaseModel>,
props: DatabaseCommonInteractionProps
): Promise<Query<TBaseModel>> {
const model: BaseModel = new modelType();
const tenantColumn: string | null = model.getTenantColumn();
// If this model has a tenantColumn, and request has tenantId, and is multiTenantQuery null then add tenantId to query.
if (tenantColumn && props.tenantId && !props.isMultiTenantRequest) {
(query as any)[tenantColumn] = props.tenantId;
}
return query;
}
private static async addTenantScopeToQuery<TBaseModel extends BaseModel>(
modelType: { new (): TBaseModel },
query: Query<TBaseModel>,

View File

@@ -17,6 +17,7 @@ import JSONFunctions from 'Common/Types/JSONFunctions';
import FileModel from 'Common/Models/FileModel';
import Dictionary from 'Common/Types/Dictionary';
import StatusCode from 'Common/Types/API/StatusCode';
import { DEFAULT_LIMIT } from 'Common/Types/Database/LimitMax';
export default class Response {
private static logResponse(
@@ -276,6 +277,8 @@ export default class Response {
listData.limit = new PositiveNumber(
parseInt(oneUptimeRequest.query['limit'].toString())
);
} else {
listData.limit = new PositiveNumber(DEFAULT_LIMIT);
}
if (oneUptimeRequest.query['output-type'] === 'csv') {

View File

@@ -28,7 +28,7 @@ import Response from './Response';
import JSONFunctions from 'Common/Types/JSONFunctions';
import API from 'Common/Utils/API';
import URL from 'Common/Types/API/URL';
import { DashboardApiHostname } from '../EnvironmentConfig';
import { AppVersion, DashboardApiHostname } from '../EnvironmentConfig';
import { DashboardApiRoute } from 'Common/ServiceRoute';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
@@ -95,8 +95,8 @@ app.use(setDefaultHeaders);
* https://stackoverflow.com/questions/19917401/error-request-entity-too-large
*/
app.use(ExpressJson({ limit: '50mb' }));
app.use(ExpressUrlEncoded({ limit: '50mb' }));
app.use(ExpressJson({ limit: '50mb', extended: true }));
app.use(ExpressUrlEncoded({ limit: '50mb', extended: true }));
app.use(logRequest);
@@ -105,6 +105,8 @@ const init: Function = async (
port?: Port,
isFrontendApp?: boolean
): Promise<ExpressApplication> => {
logger.info(`App Version: ${AppVersion.toString()}`);
await Express.launchApplication(appName, port);
LocalCache.setString('app', 'name', appName);
CommonAPI(appName);

View File

@@ -73,7 +73,7 @@
"moment-timezone": "^0.5.40",
"nanoid": "^3.3.2",
"nanoid-dictionary": "^4.3.0",
"posthog-js": "^1.37.0",
"posthog-js": "^1.77.0",
"process": "^0.11.10",
"reflect-metadata": "^0.1.13",
"slugify": "^1.6.5",
@@ -81,7 +81,7 @@
"uuid": "^8.3.2"
},
"devDependencies": {
"@faker-js/faker": "^6.3.1",
"@faker-js/faker": "^8.0.2",
"@types/jest": "^27.5.2",
"@types/node": "^17.0.22",
"jest": "^27.5.1",

View File

@@ -6,7 +6,7 @@
"scripts": {
"compile": "tsc",
"test": "jest --detectOpenHandles",
"debug:test": "node --inspect node_modules/.bin/jest --runInBand ./Tests --detectOpenHandles"
"debug:test": "cd .. && export $(grep -v '^#' config.env | xargs) && cd CommonServer && node --inspect node_modules/.bin/jest --runInBand ./Tests --detectOpenHandles"
},
"author": "",
"license": "MIT",

View File

@@ -18,6 +18,8 @@ import Field, { FormFieldStyleType } from '../Types/Field';
import FieldLabelElement from '../Fields/FieldLabel';
import FormValues from '../Types/FormValues';
import { JSONValue } from 'Common/Types/JSON';
import IDGenerator from '../../ObjectID/IDGenerator';
import ObjectID from 'Common/Types/ObjectID';
export interface ComponentProps<T extends Object> {
field: Field<T>;
@@ -167,6 +169,35 @@ const FormField: <T extends Object>(
/>
)}
{props.field.fieldType === FormFieldSchemaType.ObjectID && (
<IDGenerator
tabIndex={index}
disabled={props.isDisabled || props.field.disabled}
error={
props.touched && props.error
? props.error
: undefined
}
dataTestId={fieldType}
onChange={(value: ObjectID) => {
props.field.onChange &&
props.field.onChange(value);
props.setFieldValue(props.fieldName, value);
}}
onEnterPress={() => {
props.submitForm && props.submitForm();
}}
initialValue={
props.currentValues &&
(props.currentValues as any)[props.fieldName]
? (props.currentValues as any)[
props.fieldName
]
: props.field.defaultValue || null
}
/>
)}
{props.field.fieldType ===
FormFieldSchemaType.RadioButton && (
<RadioButtons

View File

@@ -118,7 +118,7 @@ const ModelForm: <TBaseModel extends BaseModel>(
? (Object.keys(field.field)[0] as string)
: null;
if (key && hasPermissionOnField(key)) {
if (key && (hasPermissionOnField(key) || field.forceShow)) {
(select as Dictionary<boolean>)[key] = true;
}
}

View File

@@ -1197,30 +1197,47 @@ const ModelTable: <TBaseModel extends BaseModel>(
): ReactElement => {
const plan: PlanSelect | null = ProjectUtil.getCurrentPlan();
let showPlan: boolean = Boolean(
BILLING_ENABLED &&
plan &&
new props.modelType().readBillingPlan &&
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
new props.modelType().readBillingPlan!,
plan,
getAllEnvVars()
)
);
let planName: string = new props.modelType().readBillingPlan!;
if (props.isCreateable && !showPlan) {
// if createable then read create billing permissions.
showPlan = Boolean(
BILLING_ENABLED &&
plan &&
new props.modelType().createBillingPlan &&
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
new props.modelType().createBillingPlan!,
plan,
getAllEnvVars()
)
);
planName = new props.modelType().createBillingPlan!;
}
return (
<span>
{title}
{BILLING_ENABLED &&
plan &&
new props.modelType().readBillingPlan &&
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
new props.modelType().readBillingPlan!,
plan,
getAllEnvVars()
) && (
<span
style={{
marginLeft: '5px',
}}
>
<Pill
text={`${
new props.modelType().readBillingPlan
} Plan`}
color={Yellow}
/>
</span>
)}
{showPlan && (
<span
style={{
marginLeft: '5px',
}}
>
<Pill text={`${planName} Plan`} color={Yellow} />
</span>
)}
</span>
);
};

View File

@@ -0,0 +1,89 @@
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from 'react';
import Input from '../Input/Input';
import Button, { ButtonStyleType } from '../Button/Button';
import ObjectID from 'Common/Types/ObjectID';
import IconProp from 'Common/Types/Icon/IconProp';
export interface ComponentProps {
readonly?: boolean | undefined;
initialValue?: undefined | ObjectID;
onChange?: undefined | ((value: ObjectID) => void);
value?: ObjectID | undefined;
disabled?: boolean | undefined;
dataTestId?: string | undefined;
tabIndex?: number | undefined;
onEnterPress?: (() => void) | undefined;
error?: string | undefined;
}
const IDGenerator: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const [value, setValue] = useState<ObjectID | null>(null);
useEffect(() => {
if (props.initialValue) {
setValue(props.initialValue);
}
if (props.value) {
setValue(props.value);
}
}, []);
useEffect(() => {
if (props.initialValue) {
setValue(props.initialValue);
}
}, [props.initialValue]);
useEffect(() => {
setValue(props.value ? props.value : props.initialValue || null);
}, [props.value]);
return (
<>
<>
<div className="flex" data-testid={props.dataTestId}>
{value && (
<Input
readOnly={props.readonly}
tabIndex={props.tabIndex}
onEnterPress={props.onEnterPress}
value={value.toString()}
/>
)}
<div className="mt-2">
<Button
icon={IconProp.Refresh}
buttonStyle={ButtonStyleType.NORMAL}
disabled={props.disabled || props.readonly}
title={value ? 'Regenerate' : 'Generate'}
onClick={() => {
const generatedID: ObjectID =
ObjectID.generate();
setValue(generatedID);
props.onChange && props.onChange(generatedID);
}}
/>
</div>
</div>
</>
{props.error && (
<p
data-testid="error-message"
className="mt-1 text-sm text-red-400"
>
{props.error}
</p>
)}
</>
);
};
export default IDGenerator;

View File

@@ -22,11 +22,13 @@ import SubscriptionPlan from 'Common/Types/Billing/SubscriptionPlan';
import { JSONObject } from 'Common/Types/JSON';
export const getAllEnvVars: Function = (): JSONObject => {
return window?.process?.env || process?.env || {};
const envVars: JSONObject = window?.process?.env || process?.env || {};
return envVars;
};
export const env: Function = (key: string): string => {
return (getAllEnvVars()[key] as string) || '';
const allEnv: JSONObject = getAllEnvVars();
return (allEnv[key] as string) || '';
};
export const HTTP_PROTOCOL: Protocol =

View File

@@ -1,10 +1,11 @@
import { API_DOCS_HOSTNAME, HTTP_PROTOCOL, API_DOCS_ROUTE } from '../../Config';
import { API_DOCS_HOSTNAME, HTTP_PROTOCOL } from '../../Config';
import API from 'Common/Utils/API';
import { ApiReferenceRoute } from 'Common/ServiceRoute';
class HelmAPI extends API {
class ApiDocsRoute extends API {
public constructor() {
super(HTTP_PROTOCOL, API_DOCS_HOSTNAME, API_DOCS_ROUTE);
super(HTTP_PROTOCOL, API_DOCS_HOSTNAME, ApiReferenceRoute);
}
}
export default new HelmAPI();
export default new ApiDocsRoute();

View File

@@ -3,10 +3,13 @@
#
# Pull base image nodejs image.
FROM node:current-alpine
FROM node:current-alpine AS base
USER root
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retry-maxtimeout 6000000
RUN npm config set fetch-retry-mintimeout 1000000
ARG GIT_SHA
ARG APP_VERSION
@@ -24,7 +27,8 @@ SHELL ["/bin/bash", "-c"]
RUN mkdir /usr/src
# Install common
RUN mkdir /usr/src/Common
FROM base AS common
WORKDIR /usr/src/Common
COPY ./Common/package*.json /usr/src/Common/
RUN npm install
@@ -32,7 +36,8 @@ COPY ./Common /usr/src/Common
# Install Model
RUN mkdir /usr/src/Model
FROM base AS model
WORKDIR /usr/src/Model
COPY ./Model/package*.json /usr/src/Model/
RUN npm install
@@ -41,7 +46,8 @@ COPY ./Model /usr/src/Model
# Install CommonServer
RUN mkdir /usr/src/CommonServer
FROM base AS commonserver
WORKDIR /usr/src/CommonServer
COPY ./CommonServer/package*.json /usr/src/CommonServer/
RUN npm install
@@ -51,7 +57,8 @@ COPY ./CommonServer /usr/src/CommonServer
# Install CommonUI
RUN mkdir /usr/src/CommonUI
FROM base AS commonui
WORKDIR /usr/src/CommonUI
COPY ./CommonUI/package*.json /usr/src/CommonUI/
RUN npm install --force
@@ -59,11 +66,24 @@ COPY ./CommonUI /usr/src/CommonUI
#SET ENV Variables
# Install app
FROM base AS app
WORKDIR /usr/src/Common
COPY --from=common /usr/src/Common .
WORKDIR /usr/src/Model
COPY --from=model /usr/src/Model .
WORKDIR /usr/src/CommonServer
COPY --from=commonserver /usr/src/CommonServer .
WORKDIR /usr/src/CommonUI
COPY --from=commonui /usr/src/CommonUI .
ENV PRODUCTION=true
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies

View File

@@ -394,7 +394,7 @@ const Settings: FunctionComponent<ComponentProps> = (
{reseller && (
<Card
title={`You have purchased this project from ${reseller.name}`}
title={`You have purchased this plan from ${reseller.name}`}
description={`If you would like to change the plan, please contact ${reseller.name} at ${reseller.description}`}
buttons={
reseller.changePlanLink

View File

@@ -3,10 +3,13 @@
#
# Pull base image nodejs image.
FROM node:current-alpine
FROM node:current-alpine AS base
USER root
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retry-maxtimeout 6000000
RUN npm config set fetch-retry-mintimeout 1000000
ARG GIT_SHA
ARG APP_VERSION
@@ -28,7 +31,8 @@ SHELL ["/bin/bash", "-c"]
RUN mkdir /usr/src
# Install common
RUN mkdir /usr/src/Common
FROM base AS common
WORKDIR /usr/src/Common
COPY ./Common/package*.json /usr/src/Common/
RUN npm install
@@ -36,7 +40,8 @@ COPY ./Common /usr/src/Common
# Install Model
RUN mkdir /usr/src/Model
FROM base AS model
WORKDIR /usr/src/Model
COPY ./Model/package*.json /usr/src/Model/
RUN npm install
@@ -45,7 +50,8 @@ COPY ./Model /usr/src/Model
# Install CommonServer
RUN mkdir /usr/src/CommonServer
FROM base AS commonserver
WORKDIR /usr/src/CommonServer
COPY ./CommonServer/package*.json /usr/src/CommonServer/
RUN npm install
@@ -54,9 +60,19 @@ COPY ./CommonServer /usr/src/CommonServer
#SET ENV Variables
ENV PRODUCTION=true
# Install app
FROM base AS app
RUN mkdir /usr/src/app
WORKDIR /usr/src/Common
COPY --from=common /usr/src/Common .
WORKDIR /usr/src/Model
COPY --from=model /usr/src/Model .
WORKDIR /usr/src/CommonServer
COPY --from=commonserver /usr/src/CommonServer .
ENV PRODUCTION=true
WORKDIR /usr/src/app

View File

@@ -0,0 +1,19 @@
# Development
For local development you need to use docker-compose.dev.yml file.
You need to make sure you have:
- Docker and Docker compose installed.
- Node.js and NPM installed.
```
# Clone this repo and cd into it.
git clone https://github.com/OneUptime/oneuptime.git
cd oneuptime
# Copy config.example.env to config.env
cp config.example.env config.env
# Since this is dev, you don't have to edit any of those values in config.env. You can, but that's optional.
npm run dev
```

View File

@@ -0,0 +1,61 @@
# Deploy OneUptime completely free with Docker Compose
If you prefer to host OneUptime on your own server, you can use Docker Compose to deploy a single-server instance of OneUptime on Debian, Ubuntu, or RHEL. This option gives you more control and customization over your instance, but it also requires more technical skills and resources to deploy and maintain it.
#### Choose Your System Requirements
Depending on your usage and budget, you can choose from different system requirements for your server. For optimal performance, we suggest using OneUptime with:
- **Recommended System Requirements**
- 16GB RAM
- 8 Core
- 400 GB Disk
- Ubuntu 22.04
- Docker and Docker Compose installed
- **Homelab / Minimal Requirements**
- If you want to run OneUptime for personal or experimental use in a home environment (Some of our users even have it installed on RaspberyPi), you can use the homelab requirements:
- 8 GB RAM
- 4 Core
- 20 GB Disk
- Docker and Docker Compose installed
#### Prerequisites for Single-Server Deployment
Before you start the deployment process, please make sure you have:
- A server running Debian, Ubuntu, or RHEL derivative
- Docker and Docker Compose installed on your server
To install OneUptime:
```
# Clone this repo and cd into it.
git clone https://github.com/OneUptime/oneuptime.git
cd oneuptime
# Please make sure you're on release branch.
git checkout release
# Copy config.example.env to config.env
cp config.example.env config.env
# IMPORTANT: Edit config.env file. Please make sure you have random secrets.
npm start
```
If you don't like to use npm or do not have it installed, run this instead:
```
# Read env vars from config.env file and run docker-compose up.
export $(grep -v '^#' config.env | xargs) && docker compose up --remove-orphans -d
```
To update:
```
git checkout release # Please make sure you're on release branch.
git pull
npm run update
```
OneUptime should run at: http://localhost. You need to register a new account for your instance to start using it. If you would like to use https, please use a reverse proxy like Nginx.

23
Docs/Installation/Helm.md Normal file
View File

@@ -0,0 +1,23 @@
### Installation
```
helm install oneuptime ./HelmChart/public/oneuptime -f ./HelmChart/public/oneuptime/values.yaml
```
### Upgrade
```
helm upgrade oneuptime ./HelmChart/public/oneuptime -f ./HelmChart/public/oneuptime/values.yaml
```
### Remove
```
helm uninstall oneuptime
```
### Lint
```
helm lint ./HelmChart/public/oneuptime
```

View File

@@ -10,7 +10,7 @@ To begin with you need to create a custom probe in your Project Settings > Probe
To run a probe, please make sure you have docker installed. You can run custom probe by:
```
docker run --name oneuptime-probe --network host -e PROBE_KEY=<probe-key> -e PROBE_ID=<probe-id> -e PROBE_API_URL=https://oneuptime.com/probe-api -d oneuptime/probe:release
docker run --name oneuptime-probe --network host -e PROBE_KEY=<probe-key> -e PROBE_ID=<probe-id> -e PROBE_API_URL=https://oneuptime.com -d oneuptime/probe:release
```

View File

@@ -1,195 +0,0 @@
<table>
<tr>
<th rowspan="2">
Running mode
</th>
<th rowspan="2">
Rules order
</th>
<th colspan="2">
OneUptime level configurations
</th>
<th colspan="2">
Project level configurations
</th>
<th rowspan="2">
Results
</th>
</tr>
<tr>
<th>
Twilio credentials
</th>
<th>
Enabled SMS/Call
</th>
<th>
Twilio credentials + enable “send SMS with Twlio accounts”
</th>
<th>
Enabled SMS/Call alerts (Billing page)
</th>
</tr>
<tbody>
<tr>
<td rowspan="4">
SAAS
</td>
<td>
1
</td>
<td>
*
</td>
<td>
*
</td>
<td>
Set
</td>
<td>
*
</td>
<td>
Success (without checking daily limits)
</td>
</tr>
<tr>
<td>
2
</td>
<td>
Set
</td>
<td>
Enabled
</td>
<td>
Unset
</td>
<td>
Enabled - CHARGE FOR ALERTS
</td>
<td>
Success (after checking daily limits)
</td>
</tr>
<tr>
<td>
3
</td>
<td>
*
</td>
<td>
*
</td>
<td>
*
</td>
<td>
Disabled
</td>
<td>
Failure
</td>
</tr>
<tr>
<td>
4
</td>
<td>
*
</td>
<td>
Disabled
</td>
<td>
*
</td>
<td>
*
</td>
<td>
Failure
</td>
</tr>
<tr>
<td rowspan="3">
Enterprise
</td>
<td>
1
</td>
<td>
*
</td>
<td>
*
</td>
<td>
Set
</td>
<td rowspan="3">
Hidden
</td>
<td>
Success (without checking daily limits)
</td>
</tr>
<tr>
<td>
2
</td>
<td>
Set
</td>
<td>
Enabled
</td>
<td>
*
</td>
<td>
Success (After checking the daily limits)
</td>
</tr>
<tr>
<td>
3
</td>
<td>
*
</td>
<td>
*
</td>
<td>
*
</td>
<td>
Failure
</td>
</tr>
</tbody>
</table>
- In Enterprise Mode - We never charge for alerts.
- In SaaS mode - we only charge for alerts ONLY IF global config is used.
- Check limits for global config in both Enterprise and SaaS
- Ideally in Enterprise Mode - Enable Alerts in Project Billing Page should be hidden. So, you should NOT check if the alerts are enabled or disabled for the project. Just check if the alerts are enabled / disabled in admin dashboard (if using global config). If using local config (project config) - do not check if the alerts are enabled or disabled.
In general, under the SAAS mode, the SMS/Call alerts can fail for one of the following reasons:
1- The custom/global twilio settings are not configured.
2- SMS/Call alerts are disabled in the global configurations, and the custom twilio settings are not set.
3- SMS/Call alerts are disabled for a project (from billing page), and the custom twilio settings are not set.
4- The project's balance is not enough, and the custom twilio settings are not set.
5- The targeted phone number doesn't comply with the policy selected in the billing page (high risk countries not selected), and the custom twilio settings are not set.
6- the alert phone number is not set (in case of on-call team alerts).
7- If the API call fail for any reason (wrong credentials, service down etc...)
The same reasons, excepet for 3, 4 and 5 , can cause the failure of alerts under enterprise mode.
Return back to the [main README](../README.md)

View File

@@ -1,118 +0,0 @@
# Architecture
## General description
The main part of the project are:
- User interfaces,
- The probe server,
- The backend.
### User interfaces
OneUptime has four separated projects for the UI:
#### accounts
It's responsible for the registration operations, and the login process in the frontend side.
#### AdminDashboard
It's the UI used by the administrators.
#### dashboard
It's the UI used by the users.
#### StatusPage
It's a public page that renders the reports about the situation of the monitored resources.
### The probe server
The probe server is a service that fetches periodically from backend the list of the resources (IoT devices, websites, servers, etc...) that should be monitors. Then, it tries to ping them one by one to report their status to the backend.
### The backend
The backend is the heart of the project. In addition to collecting the data from the probe servers, it implements all the other features like managing users, projects, incidents, alerts, etc...
### The server monitor
In addition to the three main components, we have the server monitor. This is an agent that needs to be installed in all the servers that will be monitored. Its main task is to collect data, like remaining storage space and CPU load, and to provide them to the probe server when requested.
The following diagram describes the general architecture of the OneUptime project.
![](./oneuptime_architecture.png)
## Features
### Administrator accounts
Using the admin dashboard, the administrator will be able to :
- Manage users,
- Manage projects,
- Manage probe servers,
- Check the audit logs,
- Update the software license,
- Configure the SMTP server to enable OneUptime sending emails like alerts, invitation, verifications, etc...
- Configure Twilio account to enable OneUptime sending alerts SMS/calls,
- Configure single sign-on domains to allow users to authenticate using third-party identity providers.
### User accounts
The dashboard is dedicated to normal users to allow them to:
- Manage projects.
- Define components.
- Build status page.
- Check reports.
- Invite members.
#### Components
A component is one of the resources the user wants to monitor. It can be:
- A web server.
- A server
In this case, the user will need to install the server monitor agent in the targeted server. The agent can be found in the folder server-monitor of the project.
- An IoT device.
- A script.
For every component, the user can define the different criteria that allow the backend to decide whether this component is working properly, degraded, or down.
In addition to the name, type, and criteria, the user can choose the category to which the resource should belong. The categories can be managed through the same dashboard under Project Settings.
#### Status pages
The user can create many status pages. For each page, he can select the resources that should be represented. The status page will have a public link that anyone, even unauthenticated users, can use to view the situation of the resources.
#### Reports
OneUptime generates several reports related to the incidents, the average resolve time, and monitors.
#### Inviting members
The user can invite by the email other users to join the team.
![](./user_environment.png)
## OneUptime in production
OneUptime can be deployed in one of two modes: SAAS mode and enterprise mode. SAAS is the mode used by Hackerbay to run OneUptime. The clients can create accounts without being administrators or having access to the admin dashboard.
In enterprise mode, the client will have the oneuptime code deployed on his personal servers.
The running mode needs to be configured on the following subprojects, by updating the .env file in each of them, as the behavior of some of their features may change depending on the mode.
- accounts
- backend
- dashboard
### Some of the differences between SAAS and Enterprise mode
#### user registration
In SAAS, when a user submits the first form with his details: name, email, password, etc... to signup, a second form will show up to collect the credit card details. After submitting that form, the user will receive an email that contains the details to activate his account.
In enterprise mode, Only the first account in the database can be created using the signup form. Once created, the signup form will not be accessible. The initial account will be assigned the role of master administrator, and it'll have the privileges to create other new accounts.

View File

@@ -1,9 +0,0 @@
# Concept.
Please read OneUptime public website (https://oneuptime.com).
You can also run oneuptime website locally by running
- `npm install` and
- `npm start`
inside of the `home` folder of this project.

View File

@@ -1,7 +0,0 @@
# DNS
Here are all the active DNS we use in prodiction:
- oneuptime.com / www.oneuptime.com: For helm production service.
- staging.oneuptime.com: For staging service.
- charts.oneuptime.com: Linked to oneuptime.com | www.oneuptime.com but used specifically for helm charts.

View File

@@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2020-12-11T08:59:50.389Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36" etag="pGpaML1Jgff4x4CsnkXM" version="14.0.1" type="google"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">5VtZd5s4FP41PvOUHECI5dFOnDbTtMnUbTJ5miODbGuCkQdwY/fXjwRiEfJCamOSNA8xumhB997vboIeuJivPkRoMftMfRz0DM1f9cBlzzAM03HZD6esM4ru6mZGmUbEF7SSMCI/sSBqgrokPo6ljgmlQUIWMtGjYYi9RKKhKKLPcrcJDeRVF2iKFcLIQ4FKfSB+MsuojmGX9I+YTGf5yroldjxHeWexk3iGfPpcIYFhD1xElCbZ1Xx1gQPOvZwvD9frh+Dmyfrw51/xf+j74NO3L/dn2WRXLxlSbCHCYfLLU2uP2nfb7bvrxSW8CejdDYwHYoj2AwVLwa9vH4eMcHs//Hp/PXzgl1fs38dbfnn1eH3H7z7cfv00EkxJ1jmnGX8W/DKkCfsZxElEnwqeM24NJjRMhIIwJWI9smvGTDB4npEEjxbI45RnpouMNkvmAe/Lh5IguKABjdKlwGQyMTyvWKRyx7fGFrTEYhW6xf40vpDYMI4SvKppyB726oXMGVowneMkWrNxYpYz3RTMFEg5Mx1BeC71zsg7zSo6l/dDQtWnxeSlONmFkOgLpGsqMsI+A4do0iiZ0SkNUTAsqYOILkMf81k5t8o+N5QuhDD+xUmyFoJEy4TKosrW5Asdhb0RDlBCfsiTbeKUGHpHCVumEAtwDEkqpu2cQ3mSBEVTnIhxVQDtm0qz5Iliuow8rEyUiq7Y0QHStN6cONnDpjzZsSsgfEIqhF27t1vRD24UqkIFEDTSjmMJFULFAv/OUoYHCvkgcwlUZzjDPBKgIUmYI2HPn7ZJ6LGYJtWiMQ5oOI3ToIbfidM12D0yIUwedUnu8XJt+SbDhbKSa6pn0m1L9Ux63cQdjded2DLGw2j9Nx/PfIBoPorp0sblSmqtq607HBG2dxwJ4oGQ2QsFXdss00MdomtKumC5tejjV90hdMy6Z23ZIaqmM8Nr7M2wvwxwnKYVQcDSCkLDHJ5MQDHOATshoZ/imqY0loTEJE5IOGUNOikQX1iAl+GZzCtZh8OVxieReBoeJ0f8RtaryEGMNoNUw5GFX4mGqqbAdU5pCgz4Hj1env/ux7nZpc/T1QzwKnV1Q+TN2M9IYIlnf5zliqjUNK/C5DwlnK+mvLxwPgnoszdDUXIeMDn9E5A5SbYkf21BAADZbFlwgy/UNmRpVltZmv4+9f9Qtd4SrxuuJD4HNHNfx/I5hoqXx6FaC+F8vUEsOpSZjgIy5abfYzzmgcSA6zfxUNAXN+bE9zP54pj8RON0Pi7hBd9QukU46MHLXQARlTQxuFfUr6qi3aGI2yscLGrSocz+MwGmX5Vq3oVOJjFuR2DOW4o0T4PKBl7JAO1En7Yj6Y+uwRPj11Xw++X2fcA30/Rd8LU125bZ/9rRm+trVVwY+zyOH8aM8SiLpa+OFZb42CNxOmWnQYljNgxKCuLxoxL7zUUlLVirWrBR53bTXBlaeyZqOVPWLQVGAj5YkbIsw44qVtCWy7K2vQEQximjdLOTKL2MI6QoogwqTlix2htcGI1r+Vs81YFwNUDtBE5vWNvqRxFaV7oJb711Ib12aKDnhG34rw84A5ZW08jsGY4b66gF7RFO6139gCE2fp3QB4bsC3Wtc+yDTg5SKzmEaYGu69X7j3haqlfb8Nx0bDf/c4CkHaChSz4WpoCa//cMK+BVrHiBQnY95df9tNxcnhOllWcvwszh+nl/tn51yCtFo1ZL2Da81KADcEIw5gnj20jou4EiPJUjPsysqgc4I/bIyQ4k6B0iQbfO5WNUpopdeybr7WVpTfTXaqi/1qF532H6q2ZVdxEdp0eQOGIayHlCstcDUG/7KeLrMPR6LUQ1NoVdJ7X0pq3ahyx+HS3HsReRccri1xzMQkfmKshj8u7yWPc9WgzTaWgxMsx2ZTHyx1ROW/s/EAnSkq+hfc7MhKrRb+6o1VA8JnA3GJWTnrZCNSnedoD3SkPKtrEEm76hdJyM76XFF5YQSjqlg921lz392ym9wE6sbKFReu/waiFekaSYi11XpmKtcibeWFcap052GqtrS1XHeqm6taqjacqHd6axU++hqx3S/TQlSkstp7yX49jMBOw6jrUglCWaf7BxoEaemfDcrr2K2d4RbY6/PRWxAfKesuDdm2HvKX1RepLuNV7QMH37co4Sdi8uM6U/sqoZi1gigtJT3maVs3YOf1G8yD5Qm5AVt9KnS8XApkysiK2k8+CSfPxaw2//zvZeX2Q19UWw08KFpUI2L1ww6xlw1I0LuPbvrhW5d1ajcOsJhb7p9c0jJdSsWX7dmZnL8iNZMPwf</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -1,18 +0,0 @@
# How OneUptime Works
## The overview of how OneUptime works
One of the main purpose of OneUptime is to monitor resources. The flow of how this is done is depicted in the flowchart below.
## Description
1. The probe server has a cron job running that pings all available monitors at a time interval.
2. After the probe server pings a monitor, it calls the backend API along with the response from the monitor.
3. A monitor contains criteria for an UP, Down, and Degraded statuses. The backend checks if the response from the probe matches any of these criteria.
4. If there is a match, an incident is created.
5. External subscribers added to the monitor are alerted via SMS, Webhook or Email alerts.
6. All On-call schedules that are associated with the monitor are then identified.
7. Each schedule contains an escalation policy which defines which team will be alerted for the incident.
8. The members in the team will be alerted via SMS, Call and/or Email.
![](./how-oneuptime-works-flowchart.png)

View File

@@ -1,17 +0,0 @@
# Introduction
OneUptime is one complete Observability platform.
OneUptime lets you do:
**Monitoring:** Monitors your website, web apps, APIs, servers and more and give you detailed metrics of things that might be wrong with your infrastructure.
**Status Page:** OneUptime gives you a beautiful and customizable status page for your online business which helps improve transparency with your customers and cuts support costs.
**Tests:** Write automated tests for your website, API's and more and know instantly when they start failing.
**On-Call and Incident Management:** On-Call Management lets you alert the right team at the right time saving you critical time during downtime.
**Performance Monitoring:** Monitor the performance of your apps, servers, APIs, and more and alert your team when any of your performance metrics degrades.
**Website:** https://oneuptime.com

View File

@@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2020-06-05T17:09:47.701Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" etag="hbal5e5wRNsxc2uXYzzf" version="13.1.14" type="device"><diagram id="P0tG3eMJVzySH8hgiAyU" name="Page-1">7Vtbc9o6EP41PIbxHfMYQmh7mpxmDp3Tnr5khC1AjbF8ZEGgv74rW8IyNgQIt3TSzDTWWlrJ2m+/XV3SsG8m8w8MJeN7GuKoYRnhvGF3G5ZltkwffgnJIpc4jpsLRoyEslIh6JNfWAoNKZ2SEKelipzSiJOkLAxoHOOAl2SIMfpcrjakUbnXBI1wRdAPUFSVfiMhH+dS3zUK+UdMRmPVs2nINxOkKktBOkYhfdZE9m3DvmGU8vxpMr/BkZg8NS95u96at8uBMRzzbRpctXr3P+IkYfGPYfdf9Bn/s1hc2XJsfKE+GIfw/bJIGR/TEY1RdFtIO4xO4xALrQaUijp3lCYgNEH4E3O+kMZEU05BNOaTSL7Fc8K/a8//CVVNV5a6c6k5Kyy0wgNmZII5ZkoWc7b4rhc0TaJYqMpKC720qizliPFrgRgQxDTGStYjUbRsGKoaQYTSlAS5UFYR35PPp5jEtWaSopROWSBrfZ2gvz7/P7t7jO/+/vhj+gFFZHrlSLgjNsJ8gw3NJZjACzGFj2ILaMdwhDiZlceBpDuMlvUKxMCDBM0OAHLOAqALNLwCtdFst60SsFu+szO0zwAj65Uwkk0fKIERWoaMALYv6U/yv20bZRX5+GWrAoww3WihVUtEhXRDP0a5H6vVLrPhC/XtlrniC/kICs9Yzsn+zmLL6ZuhaContM8Rn6bZPCfgCRu9SYLzYPx7ZTQNwythtW2296dhs+yNuRv8kTxcW880Gqch4k2j1KD1wOgAiw/EbAbTu4oslCZ5wjQkc4GmTuZimN3OsPC0fBqXSYuY9hCl4yXuNDylnNEnfEMjygqbRWiAoweaEk5oLAyF48zIHRgLJ5Bg3a1UGFDO6USrcB2RkXjBBaw7SJaWeoZgbNVnQ/iz6fvdfMiJ+MTJfCRS0uYkDRBuZq0SRlLczOfjcYRjgF6wFhdiGHiuiaqWlW8tZ4V7VPm5yBgtaZ+xliy6xpGw0K5g4RxRuuTJhZ/qzmyWnVlCp/DkMr0AW/llirF8fx+O2Z8L/C25wKnln7UIAiZ2fNfLGx044lquU1aRD/7gEddsuTtFXDWu2ohbtFbDocNhinlj1TV2i8qbbKoHZcmYxoTGhNN37jwed9quXUaRezrurE+Dq+R5R1KBRDrMnDPnCJGyNSwvguF0BgAQbySegALXg+YYqdzlZUtq42Y7ijxXtqRGqVl5gIInmJV3X1e+HiKOBijFj3oC+Spnb7+cJ9n+CX29uhz7hgfvGXMFCShJItAvupVgsNaYvgYg66nfsZvui4Bw7FOSfwUQn758reDgbZvdNcTPWrPDp01jcH48IwF+HEGESg5i65XtFsdVttdsrex6GltXV8wqoE8jFdCVJCJKcjPGwZOIb+Nsba22b7LkIJdpKULeGIZXtNeEejdnXp7tnzoouEV4WHzJrplErYHa1USinsW9jdCDtVW7ZfgrWWbjVUutgy5KNn28Bs5pDDYcwywKJgbDW8YUuDg9MjutI49Dsxad8ojE0Ls6xjPqqGyVsmD+gaWa2URk/y8R+Cq2ulruUiu6qlmVON4pyaqaqmxDVvcoFuebEirNjYRUapU1Shj9CcbYpR1SDbWdx93bQ7KB4xRv3RBQMySjKRNt7xGJUi2HM4ZiKWagCACxl8KvzyQitKqxf98HLTcoM95u6pcf2u9/EY0LvwZf2azk7GFj3dmbY7T22Yq7+ChkqlsHL4ahNQvaNx2GzCrvoHBCYgLBAtVtclx2bny4KLMSFy2jZXX8I0Ufs25ZdNLoc55rInue8p91T6zei6xtOcStR8Zp7nLY7uuSjOViSNyK2j0Y6lur2UZqlkPI3VQcbq1Pbb8aJA5IKHho26af4hnholMKAZntmjZ9AvoAwigpSMBukC5M0mXaoAa1e04iF55oBhkOGkQ7J1ovpw4Zm5adcCWwrtLqhIRh7u04Jb+yUeUOJk+PQLXbabjdTcwor+7Jxo3lhTnd0zbQ0qaIa9lt53UhVulSeha1Co4YgKtbUlkEeo+7Wdxt9TrXjnWouFs5x3dqNqlOG3m9jZH3YDeCLvBC3SHj78Vd4dk4TM3Xr0WyDSLhvQOKWPV4SjkNmWQ3mXWr1vviiy6cOX8HBU+jDFeatw2zf1Al6+xaHYdkzrx6NiLH0x1zLq5qX4upsXpBGFtNEojlNSCWNQPo0eqJkyb4JeQQKHsJPERXM9AnImbPFtOaBgzj+NHh1AZO9JtJPKqygW17nmEciA3M1STcrFCB266hgvaxqKB1WioQtwMtr8wH3l7XbS6QENy3QQjVhLz7TgRnJoK6Q8rTEoF/4pygygO+94fwgPc2eMCr8EBfX2u/c8GZuGDNMfZh2ACKxV+H5evJ4m/s7Nvf</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -1,21 +0,0 @@
# Description of the subprojects in this repo.
- `accounts` - A React project used for Authentication (Log in, Sign up, Forgot Password, etc.)
- `dashboard` - A React project for OneUptime user where user can interact with the OneUptime platform.
- `admin-dashobard` - React Project where admin can block users, delete projects and more.
- `ApiReference` - HTML/CSS project. A public reference of OneUptime documentation.
- `backend` - NodeJS Service. It's OneUptime API's.
- `home` - HTML/CSS. Home Page / Marketing page of OneUptime.
- `HttpTestServer` - A test server used to test website monitors for OneUptime.
- `ci` - DevOps/CI/CD scripts.
- `marketing` - This is where you'll find logos, brief description of OneUptime, etc.
- `certifications` - SOC/ISO/PCI certifications and more.
- `postman-collection` - Postman collection for OneUptime API.
- `probe` - Probe is an agent that gets installed on a third party server on a thir party datacenter and it monitors users websites, services, from that data center. You can deploy multiple probes to monitor users resources - A probe in a datacenter in EU, in US, etc.
- `server-monitor` - A probe that gets installed on a server and that monitors that particular server.
- `tests` - Smoke test that is executed after OneUptime is deployed to staging or production. If smoke test fails, the staging / production deployment will automatically be rolled back.
- `StatusPage` - React project - Status page project of OneUptime.
- `zapier` - OneUptime integrates with zapier. This is where integration code is. This gets deployed to zapier directly.
- `InitScript` - a container that runs schema migration script.
- `HelmChart` - Helm Chart Scripts for installation of OneUptime into Kubernetes.
- `kubernetes` - Values of Kubenrets env

View File

@@ -1,49 +0,0 @@
# Running OneUptime
## Running this project for local environment
- Make sure Docker and Docker Compose is installed.
- Make sure Node and NPM are installed.
- Run: `npm run dev`
- Docker will build and run containers. This will take some time.
- You should see OneUptime Home Page on `http://localhost`
- The containers are hot reloadable, so any changes you do in local development, it should auto-restart.
- However, containers don't auto-restart for changes made to `package.json` / `webpack.config.js` files. If you've made a change to those files, you should build the container again by running:
```
npm run force-build-dev <application_name>
Example:
npm run force-build-dev accounts
```
- After force build completes, you can run it again by running `npm run dev`
### Logs
- To check the logs of the application, you should:
```
npm run logs-dev <application_name>
Example:
npm run logs-dev accounts
```
### Debugging
- Debugging with breakpoints are supported.
- You should use VSCode for the best debugging experience.
- Select the Debug item on the left menu of VS Code and then you should see list of various apps that you can debug. You can pick any and press the green button in VS Code which attaches the debugger.
- If you make any change to the file, container automatically restarts and the debugger connection is broken. so, you need to press that green button again to debug.
### Clear cache and Prune
- Sometimes you need a clean system for various reasons with a clean docker images and clean cache.
- In those cases, you can prune docker by running `npm run prune`. Please note this will also delete images that are not related to OneUptime project.
## Running on: on-prem, staging, or production.
### Running with Docker Compose:
- Run `docker compose up`
### Running with Kubernetes and Helm
- Please check `README.md` in the `HelmChart` folder.

View File

@@ -1,53 +0,0 @@
# Running Tests
## Introduction
Tests are in the `/tests` folder.
There are two types of tests,
- SaaS
- Enterprise
### SaaS tests
This runs the application in SaaS mode. What is SaaS mode? SaaS mode enables plans and pricing with stripe. It runs the test as if its a hosted OneUptime service on oneuptime.com.
#### Running tests in SaaS mode
```
npm run docker-saas-test
```
This spins up a new local OneUptime cluster on Docker Compose and runs a test on it.
### Enterprise tests
This runs the application in Enterprise mode. What is Enterprise mode? Enterprise mode DISABLES plans and pricing. It runs the test as if its a hosted on an on-premise datacenter with an enterprise.
#### Running tests in Enterprise mode
```
npm run docker-enterprise-test
```
This spins up a new local OneUptime cluster on Docker Compose and runs a test on it.
### Debugging tests
To debug tests you first need to run the cluster and then run the tests seperately.
```
npm run docker-saas # Running a cluster in SaaS mode, or...
npm run docker-enterprise # Run a cluster in enterprise mode.
```
Once the cluster is running, you can run tests like:
```
export SLOMO=20
export HEADLESS=false
jest ./saas-tests/StatusPage/StatusPage.test.js # or any file.
```
There's also a .vscode/launch.json in test folder which will help you to debug tests with vscode.

View File

@@ -1 +0,0 @@
<mxfile host="app.diagrams.net" modified="2020-06-05T17:11:07.152Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" etag="8O-XtXlOKaga96iVvsNe" version="13.1.14" type="device"><diagram id="3j40zMsNCfodiNM318Qs" name="Page-1">7Vtbb+I6EP41SOc8BOVCLjwCbbfVnmp7SrX7WJnEEJ8mdtZ2CuyvP3YSc4nTbqslJUW0Egpjx7Hn+2Y8Mw49Z5KuvlCQxbckgknPNqNVz7no2XbgBeJTCtalYDBwS8GCoqgUWVvBFP2CldCspDmKINvryAlJOMr2hSHBGIZ8TwYoJcv9bnOS7D81AwuoCaYhSHTpDxTxuFqWa27l1xAtYvVky6xaUqA6VwIWg4gsd0TOZc+ZUEJ4eZWuJjCRulN6Ke+7eqF1MzEKMX/LDWD6PYgvH6yn26/uanIT3Y9+/WsoNT+DJK9WfEfJf1KT5aT5WmmCkhxHUA5m9pzxMkYcTjMQytalgF7IYp4m4pslLhMwg8kYhE+L4rYJSQgthnHmxZ/oMieY78jN4k/InyHlSAAwStACi7YZ4ZykoqGaqWiGqxd1YG00KxgJSQo5XYsu1Q22Qme9z7LlFlpPQRvvwOoElRBUdFpsht5qXFxUSn8HAENN/7cEI04oE9K/QpJmBIvFsb9PEo/B8Ld4OF4DHhujOjgelqcBomle6jB7cfWVJwIz1d18r1Zcq6aVoaYVK2jQiuW3pZVAU8oEcLggxawtTUFiROGfxZexcHqZFIYJyaPf0/TdHGRiKIQXDySTI5hvY/qbiPsKO3TgjoeMTtcLILgHhPZtLxGPH8+ouFrIKwapWLSGVkYQ5pBePks/UwGx2aykSiPA4o2j2UGLcUqe4B5e1sV4okC4IwxxRCReIZRP2AHyn1qHDaB1pLlEdkxyniAsHqU2eTkTUHXZDK7Ilq4WMhbpk/kchbBfLpv1s3jN5NCPMWFSHXOUJHuTd034miM8PG2Uve+bu6s7QdvXOeXaLVHK1yi1hDNJmTN9Okkfy+4WfyydQB++hXp+17ZQa/DaHqpr6HT3UL9re6jlatDwGNGoXA2Xkxvd3Zwd38c7vheoUg3je323W55Pz+amHPCcVZUCpnHoFJK4NyTV9rDJoFtL4tTADVGxDgFgWWkbc7SSSIwPZNdYJO4tmLRmvZo5WkFw0WDVKQsB7Bd3ZRQx2M8gnROaAhzCx7QsOvwZyQ7BpVrq6zgalxrrM15bTLI0Jomt4EyibpNo2DES2S9vC2Su5+lhGRci6axkdUVr31QFzzzsMg+drvFQT0HuYUZoA49OMi5xjh+Y6JmGMm+RhWGZ+iaAlU/1fubyaGgsgncuVnwNQQSpwREXGNnmA1xxI5QaNQyEY0gRV9IIsSwBaymXwb8SSwjk+ZpR1BO2sh/Fyg0jhRHKU9Ui77yuWuxASfk6g3NBA8MoK4ylVDLCKD63s1a+aiQglEdsUqV1P3YPGUmei2FQuqlXCsWWuig7nV2ccnGVW3ukOcYiX38EmTwDPfZeWys+WQ05WKOTG7ZlYk0HOCdtYjc4RFEZDZwt6PNZkFPfpo5uQU3125O2oG88lgcb9f2JquDI7Pf7evPZ3D6juQ06Z2764foDBJLjKUxnkJ5mcF5/88NuCM4bX/2wBq0F53r19juCy/aOPFsy4D8q+udMlvzlp275tunb4+DoFuzWQk7b16kz8HTmuG0Rx9HLzbeF6Z6JU6LvX41HA/v4xAn8jhGnobocpQgjATeQFax2+VNDyTXlf6d5dVz+eG7X+KMXlr8t8dnvKPAvRgPT9o7OG1+FeJ3hjdOQ4R0pyDyAfuuF3sb3AD620OvopXaWy5fosvLtej2oP2ETDQkOYcZZX/76osOBZf3w2fYDjUdNqaH6ScvhWaTXMi9TIG67mt5Oe7YHUokNnrFspxyxe06I52iRi1BCYNw24z5FMKEzsVu1v+MzTq/9VcUxc49NOtkiyAFKzn6tg3lP/RxaBCTqxbnD80x83f6mrWjb+WGgc/k/</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -3,10 +3,14 @@
#
# Pull base image nodejs image.
FROM node:current-alpine
FROM node:current-alpine AS base
USER root
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retry-maxtimeout 6000000
RUN npm config set fetch-retry-mintimeout 1000000
ARG GIT_SHA
ARG APP_VERSION
@@ -28,7 +32,8 @@ SHELL ["/bin/bash", "-c"]
RUN mkdir /usr/src
# Install common
RUN mkdir /usr/src/Common
FROM base AS common
WORKDIR /usr/src/Common
COPY ./Common/package*.json /usr/src/Common/
RUN npm install
@@ -36,7 +41,8 @@ COPY ./Common /usr/src/Common
# Install Model
RUN mkdir /usr/src/Model
FROM base AS model
WORKDIR /usr/src/Model
COPY ./Model/package*.json /usr/src/Model/
RUN npm install
@@ -45,19 +51,28 @@ COPY ./Model /usr/src/Model
# Install CommonServer
RUN mkdir /usr/src/CommonServer
FROM base AS commonserver
WORKDIR /usr/src/CommonServer
COPY ./CommonServer/package*.json /usr/src/CommonServer/
RUN npm install
COPY ./CommonServer /usr/src/CommonServer
# Install app
FROM base AS app
WORKDIR /usr/src/Common
COPY --from=common /usr/src/Common .
WORKDIR /usr/src/Model
COPY --from=model /usr/src/Model .
WORKDIR /usr/src/CommonServer
COPY --from=commonserver /usr/src/CommonServer .
#SET ENV Variables
ENV PRODUCTION=true
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Install app dependencies

View File

@@ -2,6 +2,9 @@ FROM node:18.13.0-alpine
USER root
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retry-maxtimeout 6000000
RUN npm config set fetch-retry-mintimeout 1000000
ARG GIT_SHA
ARG APP_VERSION

View File

@@ -1,8 +1,11 @@
# Pull base image nodejs image.
FROM node:current-alpine
FROM node:current-alpine AS base
USER root
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retry-maxtimeout 6000000
RUN npm config set fetch-retry-mintimeout 1000000
ARG GIT_SHA
ARG APP_VERSION
@@ -20,7 +23,8 @@ SHELL ["/bin/bash", "-c"]
RUN mkdir /usr/src
# Install common
RUN mkdir /usr/src/Common
FROM base AS common
WORKDIR /usr/src/Common
COPY ./Common/package*.json /usr/src/Common/
RUN npm install
@@ -28,7 +32,8 @@ COPY ./Common /usr/src/Common
# Install Model
RUN mkdir /usr/src/Model
FROM base AS model
WORKDIR /usr/src/Model
COPY ./Model/package*.json /usr/src/Model/
RUN npm install
@@ -37,7 +42,8 @@ COPY ./Model /usr/src/Model
# Install CommonServer
RUN mkdir /usr/src/CommonServer
FROM base AS commonserver
WORKDIR /usr/src/CommonServer
COPY ./CommonServer/package*.json /usr/src/CommonServer/
RUN npm install
@@ -45,7 +51,21 @@ COPY ./CommonServer /usr/src/CommonServer
RUN mkdir /usr/src/app
# Install app
FROM base AS app
WORKDIR /usr/src/Common
COPY --from=common /usr/src/Common .
WORKDIR /usr/src/Model
COPY --from=model /usr/src/Model .
WORKDIR /usr/src/CommonServer
COPY --from=commonserver /usr/src/CommonServer .
ENV PRODUCTION=true
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
WORKDIR /usr/src/app

35
HelmChart/MicroK8s.md Normal file
View File

@@ -0,0 +1,35 @@
# Introduction
### Installation
Install Microk8s: Read the [installation guide](https://microk8s.io/docs) for more details.
Install Helm
Run
```
sudo microk8s kubectl config view --raw > ~/.kube/config
```
### Unistallation
```bash
microk8s uninstall
```
### Common Issues
- launch failed: instance "microk8s-vm" already exists (on MacOS)
```bash
multipass delete microk8s-vm
multipass purge
# reinstall
microk8s install
microk8s status --wait-ready
```

View File

@@ -1,3 +1,26 @@
# Helm Chart for OneUptime
This project is deprecated. New helm charts will be launched soon.
## Introduction
This chart bootstraps a [OneUptime](https://oneuptime.com) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
## Adding Dependencies
- Add dependencies in Chart.yaml and run helm dependency update.
## Addons for MicroK8s
- Hostpath Storage if you're using one node.
```
microk8s enable hostpath-storage
# then you should see storage class name
kubectl get storageclass
# You can then use this storage class to run this chart
```
By default, the hostpath provisioner will store all volume data under /var/snap/microk8s/common/default-storage
To customize the default directory, please read the docs here: https://microk8s.io/docs/addon-hostpath-storage

View File

@@ -1,39 +0,0 @@
apiVersion: v1
entries:
OneUptime:
- apiVersion: v2
created: "2021-09-10T14:13:26.551683+01:00"
dependencies:
- name: nginx-ingress-controller
repository: https://charts.bitnami.com/bitnami
version: 6.0.1
- name: redis
repository: https://charts.bitnami.com/bitnami
version: 14.8.8
description: One complete DevOps and DevOps platform.
digest: 84b0d954025bc9d98419d3f7d4ee9e4470db717297e4fc1aaa546af75b16a96a
home: https://oneuptime.com
icon: https://oneuptime.com/img/ou-bb.svg
keywords:
- DevOps
- IT DevOps
- DevOps
- Monitoring
- Status Page
- On-Call
- On-Call Management
- Incident Management
- Performance Monitoring
- API Test
- Website Monitoring
- Website Test
- SRE
maintainers:
- email: support@oneuptime.com
name: OneUptime Support
url: https://oneuptime.com/support
name: OneUptime
urls:
- OneUptime-4.0.0.tgz
version: 4.0.0
generated: "2021-09-10T14:13:26.53643+01:00"

View File

@@ -0,0 +1 @@
.git

View File

@@ -0,0 +1,9 @@
dependencies:
- name: nginx-ingress-controller
repository: https://charts.bitnami.com/bitnami
version: 6.0.1
- name: redis
repository: https://charts.bitnami.com/bitnami
version: 10.5.11
digest: sha256:a75a0ffb78f91da3da43a6808df7c9c1d8a5736f512f8e15f6ff67775d3b9ecb
generated: "2021-10-05T18:40:47.90754+01:00"

View File

@@ -0,0 +1,33 @@
apiVersion: v2
name: OneUptime
version: 4.0.0
description: One complete DevOps and DevOps platform.
keywords:
- DevOps
- IT DevOps
- DevOps
- Monitoring
- Status Page
- On-Call
- On-Call Management
- Incident Management
- Performance Monitoring
- API Test
- Website Monitoring
- Website Test
- SRE
home: https://oneuptime.com
dependencies:
# https://github.com/kubernetes/ingress-nginx/tree/master/charts/ingress-nginx
- name: nginx-ingress-controller
repository: https://charts.bitnami.com/bitnami
version: "6.0.1"
- name: redis
version: "10.5.11"
repository: "https://charts.bitnami.com/bitnami"
maintainers:
- name: OneUptime Support
email: support@oneuptime.com
url: https://oneuptime.com/support
icon: https://oneuptime.com/img/OneUptime.svg
engine: gotplhelm dependency update

View File

@@ -0,0 +1,85 @@
============================================
IMPORTANT: After Installation Steps
============================================
** Thank you for installing OneUptime **
** Please be patient while the chart is being deployed **
** This usually takes few minutes or more **
To access your OneUptime app from steps below:
{{- if eq (index .Values "nginx-ingress-controller" "service" "type") "LoadBalancer" }}
============================================
Make sure external IP's are assigned.
============================================
Please run these commands to get OneUptime URL
$ kubectl get svc {{ $.Release.Name }}-nginx-ingress-controller --namespace={{ $.Release.Namespace }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
If the load balancer did not assign an external IP yet (if the IP is still pending).
Retry this command after few mins.
{{- end}}
============================================
Sign Up
============================================
{{- if eq (index .Values "nginx-ingress-controller" "service" "type") "LoadBalancer" }}
Go to the External IP (generated from step 1) from your browser and sign up a new admin account.
{{- else}}
Go to the External IP of your server from your browser and sign up a new admin account.
{{- end}}
This is your master admin account (and not a user account).
To create a user account. Please follow steps below.
============================================
Setup Email
============================================
When you're done signing up the admin account. Head over to "Settings" -> "Email"
Add your SMTP server details here to enable email alerts.
============================================
Setup Twilio
============================================
When you're done signing up the admin account. Head over to "Settings" -> "Call and SMS"
Add your Twilio Settings here to enable call and SMS alert.
============================================
Create User
============================================
On the Admin Dahboard, go to the "Users" menu and add a new user.
Log out of the admin account, and log in with a user account to access User's OneUptime Dashboard.
{{- if not $.Values.isThirdPartyBilling }}
============================================
STEP 5: Buy License
============================================
OneUptime which you just installed runs on an evaluation license.
Please contact us at sales@oneuptime.com to buy a commercial license.
We support companies of all sizes.
Once you buy the commercial license,
you can enter that license key on your admin dashboard.
{{- end }}
============================================
Support and Demo
============================================
Demo:
If you're looking for a personlized OneUptime demo.
Please email us at demo@oneuptime.com to schedule one.
Support and Help:
If you're looking for help with anything,
Please email us at support@oneuptime.com and we'll get back to you in less than 1 business day.
Thank you for installing OneUptime!

View File

@@ -0,0 +1,44 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "oneuptime.mongodbConnectionString" -}}
{{ printf "mongodb://%s:%s@%s-%s.%s-%s.%s.%s:%s/%s" $.Values.mongo.oneuptimeDbUsername $.Values.mongo.oneuptimeDbPassword $.Release.Name "mongo-standalone-0" $.Release.Name "mongo-standalone" $.Release.Namespace "svc.cluster.local" "27017" $.Values.mongo.databaseName }}
{{- end -}}
{{- define "oneuptime.internalSmtpServer" -}}
{{ printf "%s-haraka.%s.%s" $.Release.Name $.Release.Namespace "svc.cluster.local" }}
{{- end -}}
{{- define "oneuptime.redisHost" -}}
{{ printf "%s-redis-master.%s.%s" $.Release.Name $.Release.Namespace "svc.cluster.local" }}
{{- end -}}
{{- define "oneuptime.backendHost" -}}
{{ printf "%s-backend.%s.%s" $.Release.Name $.Release.Namespace "svc.cluster.local" }}
{{- end -}}
{{- define "oneuptime.oneuptimeHost" -}}
{{ printf "%s-backend.%s" $.Values.oneuptime.host }}
{{- end -}}
{{- define "oneuptime.serverUrl" -}}
{{ printf "http://%s-backend.%s.%s" $.Release.Name $.Release.Namespace "svc.cluster.local" }}
{{- end -}}
{{- define "oneuptime.probeApiUrl" -}}
{{ printf "http://%s-ProbeAPI.%s.%s" $.Release.Name $.Release.Namespace "svc.cluster.local" }}
{{- end -}}
{{- define "oneuptime.scriptRunnerUrl" -}}
{{ printf "http://%s-script.%s.%s" $.Release.Name $.Release.Namespace "svc.cluster.local" }}
{{- end -}}
{{- define "oneuptime.dataIngestorUrl" -}}
{{ printf "http://%s-ingestor.%s.%s" $.Release.Name $.Release.Namespace "svc.cluster.local" }}
{{- end -}}
{{- define "oneuptime.realtimeUrl" -}}
{{ printf "http://%s-realtime.%s.%s" $.Release.Name $.Release.Namespace "svc.cluster.local" }}
{{- end -}}

View File

@@ -0,0 +1,105 @@
############-----ACCOUNTS----#############################
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ printf "%s-%s" $.Release.Name "accounts" }}
namespace: {{ $.Release.Namespace }}
labels:
app: {{ printf "%s-%s" $.Release.Name "accounts" }}
app.kubernetes.io/part-of: oneuptime
app.kubernetes.io/managed-by: Helm
spec:
selector:
matchLabels:
app: {{ printf "%s-%s" $.Release.Name "accounts" }}
replicas: {{ $.Values.replicaCount }}
template:
metadata:
labels:
app: {{ printf "%s-%s" $.Release.Name "accounts" }}
spec:
containers:
- image: {{ printf "%s/%s/%s:%s" .Values.image.registry .Values.image.repository "accounts" .Values.image.tag }}
name: {{ printf "%s-%s" $.Release.Name "accounts" }}
imagePullPolicy: {{ $.Values.image.pullPolicy }}
resources:
requests:
cpu: 250m
limits:
cpu: 500m
env:
{{- if .Values.saas.isSaasService }}
- name: STRIPE_PUBLIC_KEY
value: {{ $.Values.saas.stripe.publicKey }}
- name: BILLING_ENABLED
value: 'true'
- name: AMPLITUDE_PUBLIC_KEY
value: {{ $.Values.saas.amplitude.key }}
{{- end }}
- name: NODE_ENV
value: {{ $.Values.nodeEnv }}
- name: DISABLE_SIGNUP
value: {{ $.Values.disableSignup | quote }}
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: POD_SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
ports:
- containerPort: {{ $.Values.host.accountsPort }}
hostPort: {{ $.Values.host.accountsPort }}
name: {{ printf "%s-%s" $.Release.Name "accounts" }}
restartPolicy: {{ $.Values.image.restartPolicy }}
---
# OneUptime Accounts Service
apiVersion: v1
kind: Service
metadata:
labels:
app: {{ printf "%s-%s" $.Release.Name "accounts" }}
app.kubernetes.io/part-of: oneuptime
app.kubernetes.io/managed-by: Helm
name: {{ printf "%s-%s" $.Release.Name "accounts" }}
namespace: {{ $.Release.Namespace }}
spec:
ports:
- port: {{ $.Values.host.accountsServicePort }}
targetPort: {{ $.Values.host.accountsPort }}
selector:
app: {{ printf "%s-%s" $.Release.Name "accounts" }}
type: ClusterIP
---
###########################################
{{- if .Values.autoScaler.enabled }}
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: {{ printf "%s-%s" $.Release.Name "accounts" }}
spec:
maxReplicas: {{ $.Values.autoScaler.maxReplicas }}
minReplicas: {{ $.Values.autoScaler.minReplicas }}
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ printf "%s-%s" $.Release.Name "accounts" }}
targetCPUUtilizationPercentage: {{ $.Values.autoScaler.averageCpuUtilization }}
---
{{- end }}

View File

@@ -0,0 +1,103 @@
############-AdminDashboard-#########
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ printf "%s-%s" $.Release.Name "admin" }}
namespace: {{ $.Release.Namespace }}
labels:
app: {{ printf "%s-%s" $.Release.Name "admin" }}
app.kubernetes.io/part-of: oneuptime
app.kubernetes.io/managed-by: Helm
spec:
selector:
matchLabels:
app: {{ printf "%s-%s" $.Release.Name "admin" }}
replicas: {{ $.Values.replicaCount }}
template:
metadata:
labels:
app: {{ printf "%s-%s" $.Release.Name "admin" }}
spec:
containers:
- image: {{ printf "%s/%s/%s:%s" .Values.image.registry .Values.image.repository "AdminDashboard" .Values.image.tag }}
name: {{ printf "%s-%s" $.Release.Name "admin" }}
imagePullPolicy: {{ $.Values.image.pullPolicy }}
resources:
requests:
cpu: 250m
limits:
cpu: 500m
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: POD_SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
{{- if .Values.saas.isSaasService }}
- name: BILLING_ENABLED
value: 'true'
{{- else }}
- name: LICENSE_URL
value: {{ $.Values.oneuptime.licensingUrl }}
{{- end }}
- name: NODE_ENV
value: {{ $.Values.nodeEnv }}
- name: IS_THIRD_PARTY_BILLING
value: {{ $.Values.isThirdPartyBilling | quote }}
- name: INTERNAL_SMTP_SERVER
value: {{ template "oneuptime.internalSmtpServer" . }}
ports:
- containerPort: {{ $.Values.host.adminDashboardPort }}
hostPort: {{ $.Values.host.adminDashboardPort }}
name: {{ printf "%s-%s" $.Release.Name "admin" }}
restartPolicy: {{ $.Values.image.restartPolicy }}
---
apiVersion: v1
kind: Service
metadata:
labels:
app: {{ printf "%s-%s" $.Release.Name "admin" }}
app.kubernetes.io/part-of: oneuptime
app.kubernetes.io/managed-by: Helm
name: {{ printf "%s-%s" $.Release.Name "admin" }}
namespace: {{ $.Release.Namespace }}
spec:
ports:
- port: {{ $.Values.host.adminDashboardServicePort }}
targetPort: {{ $.Values.host.adminDashboardPort }}
selector:
app: {{ printf "%s-%s" $.Release.Name "admin" }}
type: ClusterIP
---
##################################
{{- if .Values.autoScaler.enabled }}
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: {{ printf "%s-%s" $.Release.Name "admin" }}
spec:
maxReplicas: {{ $.Values.autoScaler.maxReplicas }}
minReplicas: {{ $.Values.autoScaler.minReplicas }}
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ printf "%s-%s" $.Release.Name "admin" }}
targetCPUUtilizationPercentage: {{ $.Values.autoScaler.averageCpuUtilization }}
---
{{- end }}

View File

@@ -0,0 +1,111 @@
############-----DASHBOARD----#############################
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ printf "%s-%s" $.Release.Name "dashboard" }}
namespace: {{ $.Release.Namespace }}
labels:
app: {{ printf "%s-%s" $.Release.Name "dashboard" }}
app.kubernetes.io/part-of: oneuptime
app.kubernetes.io/managed-by: Helm
spec:
selector:
matchLabels:
app: {{ printf "%s-%s" $.Release.Name "dashboard" }}
replicas: {{ $.Values.replicaCount }}
template:
metadata:
labels:
app: {{ printf "%s-%s" $.Release.Name "dashboard" }}
spec:
containers:
- image: {{ printf "%s/%s/%s:%s" .Values.image.registry .Values.image.repository "dashboard" .Values.image.tag }}
name: {{ printf "%s-%s" $.Release.Name "dashboard" }}
imagePullPolicy: {{ $.Values.image.pullPolicy }}
resources:
requests:
cpu: 250m
limits:
cpu: 500m
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: POD_SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: DOMAIN
value: {{ $.Values.domain }}
- name: NODE_ENV
value: {{ $.Values.nodeEnv }}
- name: PUSHNOTIFICATION_PUBLIC_KEY
value: {{ $.Values.pushNotification.publicKey }}
{{- if .Values.statusPageDomain }}
- name: STATUSPAGE_DOMAIN
value: {{ $.Values.statusPageDomain }}
{{- end }}
{{- if .Values.saas.isSaasService }}
- name: STRIPE_PUBLIC_KEY
value: {{ $.Values.saas.stripe.publicKey }}
- name: BILLING_ENABLED
value: 'true'
- name: AMPLITUDE_PUBLIC_KEY
value: {{ $.Values.saas.amplitude.key }}
{{- end }}
ports:
- containerPort: {{ $.Values.host.dashboardPort }}
hostPort: {{ $.Values.host.dashboardPort }}
name: {{ printf "%s-%s" $.Release.Name "dashboard" }}
restartPolicy: {{ $.Values.image.restartPolicy }}
---
# OneUptime Dashoard Service
apiVersion: v1
kind: Service
metadata:
labels:
app: {{ printf "%s-%s" $.Release.Name "dashboard" }}
app.kubernetes.io/part-of: oneuptime
app.kubernetes.io/managed-by: Helm
name: {{ printf "%s-%s" $.Release.Name "dashboard" }}
namespace: {{ $.Release.Namespace }}
spec:
ports:
- port: {{ $.Values.host.dashboardServicePort }}
targetPort: {{ $.Values.host.dashboardPort }}
selector:
app: {{ printf "%s-%s" $.Release.Name "dashboard" }}
type: ClusterIP
---
##########################################################
{{- if .Values.autoScaler.enabled }}
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: {{ printf "%s-%s" $.Release.Name "dashboard" }}
spec:
maxReplicas: {{ $.Values.autoScaler.maxReplicas }}
minReplicas: {{ $.Values.autoScaler.minReplicas }}
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ printf "%s-%s" $.Release.Name "dashboard" }}
targetCPUUtilizationPercentage: {{ $.Values.autoScaler.averageCpuUtilization }}
---
{{- end }}

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