Merge branch 'telemetry'

This commit is contained in:
Simon Larsen 2023-11-21 13:15:53 +00:00
commit a5f2f4e338
No known key found for this signature in database
GPG Key ID: AB45983AA9C81CDE
160 changed files with 3580 additions and 4753 deletions

2
.gitignore vendored
View File

@ -97,3 +97,5 @@ Llama/Models/tokenizer*
Llama/Models/llama*
Llama/__pycache__/*
Examples/otel-dotnet/obj

View File

@ -6,12 +6,12 @@ import Name from 'Common/Types/Name';
import { DASHBOARD_URL } from 'CommonUI/src/Config';
import { JSONObject } from 'Common/Types/JSON';
import User from 'Model/Models/User';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Analytics from 'CommonUI/src/Utils/Analytics';
import BaseModel from 'Common/Models/BaseModel';
export default abstract class LoginUtil {
public static login(value: JSONObject): void {
const user: User = JSONFunctions.fromJSON(
const user: User = BaseModel.fromJSON(
value['user'] as JSONObject,
User
) as User;

View File

@ -8,19 +8,23 @@ import { TableAccessControl } from '../Types/BaseDatabase/AccessControl';
import EnableWorkflowOn from '../Types/BaseDatabase/EnableWorkflowOn';
import ObjectID from '../Types/ObjectID';
import CommonModel from './CommonModel';
import Route from '../Types/API/Route';
import { EnableRealtimeEventsOn } from '../Utils/Realtime';
export default class AnalyticsDataModel extends CommonModel {
export default class AnalyticsBaseModel extends CommonModel {
public constructor(data: {
tableName: string;
singularName: string;
pluralName: string;
tableEngine?: AnalyticsTableEngine | undefined;
tableColumns: Array<AnalyticsTableColumn>;
crudApiPath: Route;
allowAccessIfSubscriptionIsUnpaid?: boolean | undefined;
tableBillingAccessControl?: TableBillingAccessControl | undefined;
accessControl?: TableAccessControl | undefined;
primaryKeys: Array<string>; // this should be the subset of tableColumns
enableWorkflowOn?: EnableWorkflowOn | undefined;
enableRealtimeEventsOn?: EnableRealtimeEventsOn | undefined;
}) {
super({
tableColumns: data.tableColumns,
@ -100,6 +104,8 @@ export default class AnalyticsDataModel extends CommonModel {
data.allowAccessIfSubscriptionIsUnpaid || false;
this.accessControl = data.accessControl;
this.enableWorkflowOn = data.enableWorkflowOn;
this.crudApiPath = data.crudApiPath;
this.enableRealtimeEventsOn = data.enableRealtimeEventsOn;
// initialize Arrays.
for (const column of this.tableColumns) {
@ -141,6 +147,14 @@ export default class AnalyticsDataModel extends CommonModel {
this._tableEngine = v;
}
private _enableRealtimeEventsOn: EnableRealtimeEventsOn | undefined;
public get enableRealtimeEventsOn(): EnableRealtimeEventsOn | undefined {
return this._enableRealtimeEventsOn;
}
public set enableRealtimeEventsOn(v: EnableRealtimeEventsOn | undefined) {
this._enableRealtimeEventsOn = v;
}
private _primaryKeys: Array<string> = [];
public get primaryKeys(): Array<string> {
return this._primaryKeys;
@ -185,6 +199,14 @@ export default class AnalyticsDataModel extends CommonModel {
this._allowAccessIfSubscriptionIsUnpaid = v;
}
private _crudApiPath!: Route;
public get crudApiPath(): Route {
return this._crudApiPath;
}
public set crudApiPath(v: Route) {
this._crudApiPath = v;
}
public getTenantColumn(): AnalyticsTableColumn | null {
const column: AnalyticsTableColumn | undefined = this.tableColumns.find(
(column: AnalyticsTableColumn) => {
@ -199,6 +221,16 @@ export default class AnalyticsDataModel extends CommonModel {
return column;
}
public getTenantColumnValue(): ObjectID | null {
const column: AnalyticsTableColumn | null = this.getTenantColumn();
if (!column) {
return null;
}
return this.getColumnValue(column.key) as ObjectID | null;
}
public getRequiredColumns(): Array<AnalyticsTableColumn> {
return this.tableColumns.filter((column: AnalyticsTableColumn) => {
return column.required;

View File

@ -4,7 +4,8 @@ import AnalyticsTableColumn from '../Types/AnalyticsDatabase/TableColumn';
import TableColumnType from '../Types/AnalyticsDatabase/TableColumnType';
import OneUptimeDate from '../Types/Date';
import BadDataException from '../Types/Exception/BadDataException';
import { JSONObject, JSONValue } from '../Types/JSON';
import { JSONArray, JSONObject, JSONValue } from '../Types/JSON';
import JSONFunctions from '../Types/JSONFunctions';
import ObjectID from '../Types/ObjectID';
export type RecordValue =
@ -111,6 +112,39 @@ export default class CommonModel {
return this.tableColumns;
}
public static fromJSON<T extends CommonModel>(
json: JSONObject | JSONArray | CommonModel | Array<CommonModel>,
type: { new (): T }
): T | Array<T> {
if (Array.isArray(json)) {
const arr: Array<T> = [];
for (const item of json) {
if (item instanceof CommonModel) {
arr.push(item as T);
continue;
}
arr.push(new type().fromJSON(item) as T);
}
return arr;
}
if (json instanceof CommonModel) {
return json as T;
}
return new type().fromJSON(json) as T;
}
public static toJSON<T extends CommonModel>(
model: T,
_modelType: { new (): T }
): JSONObject {
return model.toJSON();
}
public fromJSON(json: JSONObject): CommonModel {
for (const key in json) {
this.setColumnValue(key, json[key]);
@ -133,12 +167,10 @@ export default class CommonModel {
}
if (recordValue instanceof Array) {
if (
recordValue.length > 0 &&
recordValue[0] instanceof CommonModel
) {
if (recordValue.length > 0 && column.nestedModelType) {
json[column.key] = CommonModel.toJSONArray(
recordValue as Array<CommonModel>
recordValue as Array<CommonModel>,
column.nestedModelType
);
}
@ -148,16 +180,21 @@ export default class CommonModel {
json[column.key] = recordValue;
});
return json;
return JSONFunctions.serialize(json);
}
public static fromJSONArray<TBaseModel extends CommonModel>(
modelType: { new (): CommonModel },
jsonArray: Array<JSONObject>
jsonArray: Array<JSONObject | CommonModel>,
modelType: { new (): CommonModel }
): Array<TBaseModel> {
const models: Array<CommonModel> = [];
jsonArray.forEach((json: JSONObject) => {
jsonArray.forEach((json: JSONObject | CommonModel) => {
if (json instanceof CommonModel) {
models.push(json);
return;
}
const model: CommonModel = new modelType();
model.fromJSON(json);
models.push(model);
@ -166,11 +203,14 @@ export default class CommonModel {
return models as Array<TBaseModel>;
}
public static toJSONArray(models: Array<CommonModel>): Array<JSONObject> {
public static toJSONArray(
models: Array<CommonModel>,
modelType: { new (): CommonModel }
): Array<JSONObject> {
const json: Array<JSONObject> = [];
models.forEach((model: CommonModel) => {
json.push(model.toJSON());
json.push(this.toJSON(model, modelType));
});
return json;

View File

@ -37,6 +37,7 @@ import IconProp from '../Types/Icon/IconProp';
import Text from '../Types/Text';
import { getColumnBillingAccessControlForAllColumns } from '../Types/Database/AccessControl/ColumnBillingAccessControl';
import ColumnBillingAccessControl from '../Types/BaseDatabase/ColumnBillingAccessControl';
import JSONFunctions from '../Types/JSONFunctions';
export type DbTypes =
| string
@ -530,4 +531,180 @@ export default class BaseModel extends BaseEntity {
)
);
}
public static toJSON(
model: BaseModel,
modelType: { new (): BaseModel }
): JSONObject {
const json: JSONObject = this.toJSONObject(model, modelType);
return JSONFunctions.serialize(json);
}
public static toJSONObject(
model: BaseModel,
modelType: { new (): BaseModel }
): JSONObject {
const json: JSONObject = {};
const vanillaModel: BaseModel = new modelType();
for (const key of vanillaModel.getTableColumns().columns) {
if ((model as any)[key] === undefined) {
continue;
}
const tableColumnMetadata: TableColumnMetadata =
vanillaModel.getTableColumnMetadata(key);
if (tableColumnMetadata) {
if (
(model as any)[key] &&
tableColumnMetadata.modelType &&
tableColumnMetadata.type === TableColumnType.Entity &&
(model as any)[key] instanceof BaseModel
) {
(json as any)[key] = this.toJSONObject(
(model as any)[key],
tableColumnMetadata.modelType
);
} else if (
(model as any)[key] &&
Array.isArray((model as any)[key]) &&
(model as any)[key].length > 0 &&
tableColumnMetadata.modelType &&
tableColumnMetadata.type === TableColumnType.EntityArray
) {
(json as any)[key] = this.toJSONObjectArray(
(model as any)[key] as Array<BaseModel>,
tableColumnMetadata.modelType
);
} else {
(json as any)[key] = (model as any)[key];
}
}
}
return json;
}
public static toJSONObjectArray(
list: Array<BaseModel>,
modelType: { new (): BaseModel }
): JSONArray {
const array: JSONArray = [];
for (const item of list) {
array.push(this.toJSONObject(item, modelType));
}
return array;
}
public static toJSONArray(
list: Array<BaseModel>,
modelType: { new (): BaseModel }
): JSONArray {
const array: JSONArray = [];
for (const item of list) {
array.push(this.toJSON(item, modelType));
}
return array;
}
private static _fromJSON<T extends BaseModel>(
json: JSONObject | T,
type: { new (): T }
): T {
if (json instanceof BaseModel) {
return json;
}
json = JSONFunctions.deserialize(json);
const baseModel: T = new type();
for (const key of Object.keys(json)) {
const tableColumnMetadata: TableColumnMetadata =
baseModel.getTableColumnMetadata(key);
if (tableColumnMetadata) {
if (
json[key] &&
tableColumnMetadata.modelType &&
tableColumnMetadata.type === TableColumnType.Entity
) {
if (
json[key] &&
Array.isArray(json[key]) &&
(json[key] as Array<any>).length > 0
) {
json[key] = (json[key] as Array<any>)[0];
}
(baseModel as any)[key] = this.fromJSON(
json[key] as JSONObject,
tableColumnMetadata.modelType
);
} else if (
json[key] &&
tableColumnMetadata.modelType &&
tableColumnMetadata.type === TableColumnType.EntityArray
) {
if (json[key] && !Array.isArray(json[key])) {
json[key] = [json[key]];
}
(baseModel as any)[key] = this.fromJSONArray(
json[key] as JSONArray,
tableColumnMetadata.modelType
);
} else {
(baseModel as any)[key] = json[key];
}
}
}
return baseModel as T;
}
public static fromJSON<T extends BaseModel>(
json: JSONObject | JSONArray,
type: { new (): T }
): T | Array<T> {
if (Array.isArray(json)) {
const arr: Array<T> = [];
for (const item of json) {
arr.push(this._fromJSON<T>(item, type));
}
return arr;
}
return this._fromJSON<T>(json, type);
}
public static fromJSONObject<T extends BaseModel>(
json: JSONObject | T,
type: { new (): T }
): T {
if (json instanceof BaseModel) {
return json;
}
return this.fromJSON<T>(json, type) as T;
}
public static fromJSONArray<T extends BaseModel>(
json: Array<JSONObject | T>,
type: { new (): T }
): Array<T> {
const arr: Array<T> = [];
for (const item of json) {
arr.push(this._fromJSON<T>(item, type));
}
return arr;
}
}

View File

@ -27,3 +27,5 @@ export const ApiReferenceRoute: Route = new Route('/reference');
export const AdminDashboardRoute: Route = new Route('/admin');
export const IngestorRoute: Route = new Route('/ingestor');
export const RealtimeRoute: Route = new Route('/realtime/socket');

View File

@ -28,12 +28,12 @@ describe('JSONFunctions Class', () => {
describe('toJSON and fromJSON Methods', () => {
test('toJSON returns a valid JSON object', () => {
const json: JSONObject = JSONFunctions.toJSON(baseModel, BaseModel);
const json: JSONObject = BaseModel.toJSON(baseModel, BaseModel);
expect(json).toEqual(expect.objectContaining({}));
});
test('toJSONObject returns a valid JSON object', () => {
const json: JSONObject = JSONFunctions.toJSONObject(
const json: JSONObject = BaseModel.toJSONObject(
baseModel,
BaseModel
);
@ -42,7 +42,7 @@ describe('JSONFunctions Class', () => {
test('fromJSON returns a BaseModel instance', () => {
const json: JSONObject = { name: 'oneuptime' };
const result: BaseModel | BaseModel[] = JSONFunctions.fromJSON(
const result: BaseModel | BaseModel[] = BaseModel.fromJSON(
json,
BaseModel
);

View File

@ -1,11 +1,17 @@
import BaseModel from '../../Models/BaseModel';
import AnalyticsBaseModel from '../../AnalyticsModels/BaseModel';
import { JSONArray, JSONObject, JSONObjectOrArray } from '../JSON';
import JSONFunctions from '../JSONFunctions';
import Typeof from '../Typeof';
import Headers from './Headers';
export default class HTTPResponse<
T extends JSONObjectOrArray | BaseModel | Array<BaseModel>
T extends
| JSONObjectOrArray
| BaseModel
| Array<BaseModel>
| AnalyticsBaseModel
| Array<AnalyticsBaseModel>
> {
private _statusCode: number = -1;
public get statusCode(): number {

View File

@ -112,9 +112,17 @@ export default class AnalyticsTableColumn {
this._nestedModel = v;
}
private _nestedModelType?: { new (): NestedModel } | undefined;
public get nestedModelType(): { new (): NestedModel } | undefined {
return this._nestedModelType;
}
public set nestedModelType(v: { new (): NestedModel } | undefined) {
this._nestedModelType = v;
}
public constructor(data: {
key: string;
nestedModel?: NestedModel | undefined;
nestedModelType?: { new (): NestedModel } | undefined;
title: string;
description: string;
required: boolean;
@ -128,7 +136,10 @@ export default class AnalyticsTableColumn {
| (() => Date | string | number | boolean)
| undefined;
}) {
if (data.type === TableColumnType.NestedModel && !data.nestedModel) {
if (
data.type === TableColumnType.NestedModel &&
!data.nestedModelType
) {
throw new Error('NestedModel is required when type is NestedModel');
}
@ -144,6 +155,9 @@ export default class AnalyticsTableColumn {
this.billingAccessControl = data.billingAccessControl;
this.allowAccessIfSubscriptionIsUnpaid =
data.allowAccessIfSubscriptionIsUnpaid || false;
this.nestedModel = data.nestedModel;
if (data.nestedModelType) {
this.nestedModel = new data.nestedModelType();
this.nestedModelType = data.nestedModelType;
}
}
}

View File

@ -0,0 +1,6 @@
enum DatabaseType {
Database = 'Database',
AnalyticsDatabase = 'AnalyticsDatabase',
}
export default DatabaseType;

View File

@ -4,6 +4,7 @@ enum CodeType {
HTML = 'html',
JSON = 'json',
Markdown = 'markdown',
SQL = 'sql',
// TODO add more mime types.
}

View File

@ -10,13 +10,16 @@ enum IconProp {
Settings = 'Settings',
Criteria = 'Criteria',
Notification = 'Notification',
CursorArrowRays = 'CursorArrowRays',
Cube = 'Cube',
Squares = 'Squares',
RectangleStack = 'RectangleStack',
ChartBar = 'ChartBar',
SquareStack = 'SquareStack',
Help = 'Help',
JSON = 'JSON',
Signal = 'Signal',
Stop = 'Stop',
Database = 'Database',
ChevronDown = 'ChevronDown',
Pencil = 'Pencil',

View File

@ -3,8 +3,6 @@ import DatabaseProperty from './Database/DatabaseProperty';
import OneUptimeDate from './Date';
import BaseModel from '../Models/BaseModel';
import { JSONArray, JSONObject, JSONValue, ObjectType } from './JSON';
import { TableColumnMetadata } from '../Types/Database/TableColumn';
import TableColumnType from './Database/TableColumnType';
import SerializableObject from './SerializableObject';
import SerializableObjectDictionary from './SerializableObjectDictionary';
import JSON5 from 'json5';
@ -20,182 +18,6 @@ export default class JSONFunctions {
return Object.keys(obj).length === 0;
}
public static toJSON(
model: BaseModel,
modelType: { new (): BaseModel }
): JSONObject {
const json: JSONObject = this.toJSONObject(model, modelType);
return JSONFunctions.serialize(json);
}
public static toJSONObject(
model: BaseModel,
modelType: { new (): BaseModel }
): JSONObject {
const json: JSONObject = {};
const vanillaModel: BaseModel = new modelType();
for (const key of vanillaModel.getTableColumns().columns) {
if ((model as any)[key] === undefined) {
continue;
}
const tableColumnMetadata: TableColumnMetadata =
vanillaModel.getTableColumnMetadata(key);
if (tableColumnMetadata) {
if (
(model as any)[key] &&
tableColumnMetadata.modelType &&
tableColumnMetadata.type === TableColumnType.Entity &&
(model as any)[key] instanceof BaseModel
) {
(json as any)[key] = this.toJSONObject(
(model as any)[key],
tableColumnMetadata.modelType
);
} else if (
(model as any)[key] &&
Array.isArray((model as any)[key]) &&
(model as any)[key].length > 0 &&
tableColumnMetadata.modelType &&
tableColumnMetadata.type === TableColumnType.EntityArray
) {
(json as any)[key] = this.toJSONObjectArray(
(model as any)[key] as Array<BaseModel>,
tableColumnMetadata.modelType
);
} else {
(json as any)[key] = (model as any)[key];
}
}
}
return json;
}
public static toJSONObjectArray(
list: Array<BaseModel>,
modelType: { new (): BaseModel }
): JSONArray {
const array: JSONArray = [];
for (const item of list) {
array.push(this.toJSONObject(item, modelType));
}
return array;
}
public static toJSONArray(
list: Array<BaseModel>,
modelType: { new (): BaseModel }
): JSONArray {
const array: JSONArray = [];
for (const item of list) {
array.push(this.toJSON(item, modelType));
}
return array;
}
private static _fromJSON<T extends BaseModel>(
json: JSONObject | T,
type: { new (): T }
): T {
if (json instanceof BaseModel) {
return json;
}
json = JSONFunctions.deserialize(json);
const baseModel: T = new type();
for (const key of Object.keys(json)) {
const tableColumnMetadata: TableColumnMetadata =
baseModel.getTableColumnMetadata(key);
if (tableColumnMetadata) {
if (
json[key] &&
tableColumnMetadata.modelType &&
tableColumnMetadata.type === TableColumnType.Entity
) {
if (
json[key] &&
Array.isArray(json[key]) &&
(json[key] as Array<any>).length > 0
) {
json[key] = (json[key] as Array<any>)[0];
}
(baseModel as any)[key] = this.fromJSON(
json[key] as JSONObject,
tableColumnMetadata.modelType
);
} else if (
json[key] &&
tableColumnMetadata.modelType &&
tableColumnMetadata.type === TableColumnType.EntityArray
) {
if (json[key] && !Array.isArray(json[key])) {
json[key] = [json[key]];
}
(baseModel as any)[key] = this.fromJSONArray(
json[key] as JSONArray,
tableColumnMetadata.modelType
);
} else {
(baseModel as any)[key] = json[key];
}
}
}
return baseModel as T;
}
public static fromJSON<T extends BaseModel>(
json: JSONObject | JSONArray,
type: { new (): T }
): T | Array<T> {
if (Array.isArray(json)) {
const arr: Array<T> = [];
for (const item of json) {
arr.push(this._fromJSON<T>(item, type));
}
return arr;
}
return this._fromJSON<T>(json, type);
}
public static fromJSONObject<T extends BaseModel>(
json: JSONObject | T,
type: { new (): T }
): T {
if (json instanceof BaseModel) {
return json;
}
return this.fromJSON<T>(json, type) as T;
}
public static fromJSONArray<T extends BaseModel>(
json: Array<JSONObject | T>,
type: { new (): T }
): Array<T> {
const arr: Array<T> = [];
for (const item of json) {
arr.push(this._fromJSON<T>(item, type));
}
return arr;
}
public static toCompressedString(val: JSONValue): string {
return JSON.stringify(val, null, 2);
}
@ -262,7 +84,7 @@ export default class JSONFunctions {
) {
return val;
} else if (val instanceof BaseModel) {
return this.toJSON(val, BaseModel);
return BaseModel.toJSON(val, BaseModel);
} else if (typeof val === Typeof.Number) {
return val;
} else if (ArrayBuffer.isView(val)) {

View File

@ -42,6 +42,13 @@ enum Permission {
CanEditProjectApiKey = 'CanEditProjectApiKey',
CanEditProjectApiKeyPermissions = 'CanEditProjectApiKeyPermissions',
// Logs
CanCreateTelemetryServiceLog = 'CanCreateTelemetryServiceLog',
CanDeleteTelemetryServiceLog = 'CanDeleteTelemetryServiceLog',
CanEditTelemetryServiceLog = 'CanEditTelemetryServiceLog',
CanReadTelemetryServiceLog = 'CanReadTelemetryServiceLog',
// Billing Permissions (Owner Permission)
CanManageProjectBilling = 'CanManageProjectBilling',
@ -58,10 +65,10 @@ enum Permission {
CanEditProjectProbe = 'CanEditProjectProbe',
CanReadProjectProbe = 'CanReadProjectProbe',
CanCreateService = 'CanCreateService',
CanDeleteService = 'CanDeleteService',
CanEditService = 'CanEditService',
CanReadService = 'CanReadService',
CanCreateTelemetryService = 'CanCreateTelemetryService',
CanDeleteTelemetryService = 'CanDeleteTelemetryService',
CanEditTelemetryService = 'CanEditTelemetryService',
CanReadTelemetryService = 'CanReadTelemetryService',
CanCreateMonitorGroupResource = 'CanCreateMonitorGroupResource',
CanDeleteMonitorGroupResource = 'CanDeleteMonitorGroupResource',
@ -1834,31 +1841,32 @@ export class PermissionHelper {
},
{
permission: Permission.CanCreateService,
title: 'Can Create Service',
description: 'This permission can create Service this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission: Permission.CanDeleteService,
title: 'Can Delete Service',
permission: Permission.CanCreateTelemetryService,
title: 'Can Create Telemetry Service',
description:
'This permission can delete Service of this project.',
'This permission can create Telemetry Service this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission: Permission.CanEditService,
title: 'Can Edit Service',
permission: Permission.CanDeleteTelemetryService,
title: 'Can Delete Telemetry Service',
description:
'This permission can edit Service of this project.',
'This permission can delete Telemetry Service of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission: Permission.CanReadService,
title: 'Can Read Service',
permission: Permission.CanEditTelemetryService,
title: 'Can Edit Telemetry Service',
description:
'This permission can edit Telemetry Service of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission: Permission.CanReadTelemetryService,
title: 'Can Read Telemetry Service',
description:
'This permission can read Service of this project.',
isAssignableToTenant: true,
@ -2112,6 +2120,43 @@ export class PermissionHelper {
isAccessControlPermission: false,
},
{
permission: Permission.CanCreateTelemetryServiceLog,
title: 'Can Create Telemetry Service Log',
description:
'This permission can create Telemetry Service Log this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanDeleteTelemetryServiceLog,
title: 'Can Delete Telemetry Service Log',
description:
'This permission can delete Telemetry Service Log of this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanEditTelemetryServiceLog,
title: 'Can Edit Telemetry Service Log',
description:
'This permission can edit Telemetry Service Log of this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanReadTelemetryServiceLog,
title: 'Can Read Telemetry Service Log',
description:
'This permission can read Telemetry Service Log of this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanCreateScheduledMaintenanceOwnerTeam,
title: 'Can Create Scheduled Maintenance Team Owner',

View File

@ -11,6 +11,7 @@ import Hostname from '../Types/API/Hostname';
import Route from '../Types/API/Route';
import BaseModel from '../Models/BaseModel';
import Dictionary from '../Types/Dictionary';
import AnalyticsBaseModel from '../AnalyticsModels/BaseModel';
export default class API {
private _protocol: Protocol = Protocol.HTTPS;
@ -178,7 +179,13 @@ export default class API {
}
public static async get<
T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>
T extends
| JSONObject
| JSONArray
| BaseModel
| Array<BaseModel>
| AnalyticsBaseModel
| Array<AnalyticsBaseModel>
>(
url: URL,
data?: JSONObject | JSONArray,
@ -188,7 +195,13 @@ export default class API {
}
public static async delete<
T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>
T extends
| JSONObject
| JSONArray
| BaseModel
| Array<BaseModel>
| AnalyticsBaseModel
| Array<AnalyticsBaseModel>
>(
url: URL,
data?: JSONObject | JSONArray,
@ -198,7 +211,13 @@ export default class API {
}
public static async head<
T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>
T extends
| JSONObject
| JSONArray
| BaseModel
| Array<BaseModel>
| AnalyticsBaseModel
| Array<AnalyticsBaseModel>
>(
url: URL,
data?: JSONObject | JSONArray,
@ -208,7 +227,13 @@ export default class API {
}
public static async put<
T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>
T extends
| JSONObject
| JSONArray
| BaseModel
| Array<BaseModel>
| AnalyticsBaseModel
| Array<AnalyticsBaseModel>
>(
url: URL,
data?: JSONObject | JSONArray,
@ -218,7 +243,13 @@ export default class API {
}
public static async post<
T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>
T extends
| JSONObject
| JSONArray
| BaseModel
| Array<BaseModel>
| AnalyticsBaseModel
| Array<AnalyticsBaseModel>
>(
url: URL,
data?: JSONObject | JSONArray,
@ -228,7 +259,13 @@ export default class API {
}
public static async fetch<
T extends JSONObject | JSONArray | BaseModel | Array<BaseModel>
T extends
| JSONObject
| JSONArray
| BaseModel
| Array<BaseModel>
| AnalyticsBaseModel
| Array<AnalyticsBaseModel>
>(
method: HTTPMethod,
url: URL,

39
Common/Utils/Realtime.ts Normal file
View File

@ -0,0 +1,39 @@
import DatabaseType from '../Types/BaseDatabase/DatabaseType';
import { JSONObject } from '../Types/JSON';
import ObjectID from '../Types/ObjectID';
export enum EventName {
ListenToModalEvent = 'ListenToModelEvent',
}
export enum ModelEventType {
Create = 'Create',
Update = 'Update',
Delete = 'Delete',
}
export interface ListenToModelEventJSON {
modelName: string;
modelType: DatabaseType;
query: JSONObject;
eventType: ModelEventType;
tenantId: string;
select: JSONObject;
}
export interface EnableRealtimeEventsOn {
create?: boolean | undefined;
update?: boolean | undefined;
delete?: boolean | undefined;
read?: boolean | undefined;
}
export default class RealtimeUtil {
public static getRoomId(
tenantId: string | ObjectID,
modelName: string,
eventType: ModelEventType
): string {
return tenantId.toString() + '-' + modelName + '-' + eventType;
}
}

View File

@ -25,10 +25,7 @@ import {
} from 'Common/Types/Database/LimitMax';
import PartialEntity from 'Common/Types/Database/PartialEntity';
import { UserPermission } from 'Common/Types/Permission';
import { IsBillingEnabled } from '../EnvironmentConfig';
import ProjectService from '../Services/ProjectService';
import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan';
import UserType from 'Common/Types/UserType';
import CommonAPI from './CommonAPI';
export default class BaseAPI<
TBaseModel extends BaseModel,
@ -192,7 +189,7 @@ export default class BaseAPI<
const permissions: Array<UserPermission> = [];
const props: DatabaseCommonInteractionProps =
await this.getDatabaseCommonInteractionProps(req);
await CommonAPI.getDatabaseCommonInteractionProps(req);
if (
props &&
@ -217,63 +214,6 @@ export default class BaseAPI<
return null;
}
public async getDatabaseCommonInteractionProps(
req: ExpressRequest
): Promise<DatabaseCommonInteractionProps> {
const props: DatabaseCommonInteractionProps = {
tenantId: undefined,
userGlobalAccessPermission: undefined,
userTenantAccessPermission: undefined,
userId: undefined,
userType: (req as OneUptimeRequest).userType,
isMultiTenantRequest: undefined,
};
if (
(req as OneUptimeRequest).userAuthorization &&
(req as OneUptimeRequest).userAuthorization?.userId
) {
props.userId = (req as OneUptimeRequest).userAuthorization!.userId;
}
if ((req as OneUptimeRequest).userGlobalAccessPermission) {
props.userGlobalAccessPermission = (
req as OneUptimeRequest
).userGlobalAccessPermission;
}
if ((req as OneUptimeRequest).userTenantAccessPermission) {
props.userTenantAccessPermission = (
req as OneUptimeRequest
).userTenantAccessPermission;
}
if ((req as OneUptimeRequest).tenantId) {
props.tenantId = (req as OneUptimeRequest).tenantId || undefined;
}
if (req.headers['is-multi-tenant-query']) {
props.isMultiTenantRequest = true;
}
if (IsBillingEnabled && props.tenantId) {
const plan: {
plan: PlanSelect | null;
isSubscriptionUnpaid: boolean;
} = await ProjectService.getCurrentPlan(props.tenantId!);
props.currentPlan = plan.plan || undefined;
props.isSubscriptionUnpaid = plan.isSubscriptionUnpaid;
}
// check for root permissions.
if (props.userType === UserType.MasterAdmin) {
props.isMasterAdmin = true;
}
return props;
}
public async getList(
req: ExpressRequest,
res: ExpressResponse
@ -313,7 +253,7 @@ export default class BaseAPI<
}
const databaseProps: DatabaseCommonInteractionProps =
await this.getDatabaseCommonInteractionProps(req);
await CommonAPI.getDatabaseCommonInteractionProps(req);
const list: Array<BaseModel> = await this.service.findBy({
query,
@ -353,7 +293,7 @@ export default class BaseAPI<
}
const databaseProps: DatabaseCommonInteractionProps =
await this.getDatabaseCommonInteractionProps(req);
await CommonAPI.getDatabaseCommonInteractionProps(req);
const count: PositiveNumber = await this.service.countBy({
query,
@ -382,7 +322,7 @@ export default class BaseAPI<
const item: BaseModel | null = await this.service.findOneById({
id: objectId,
select,
props: await this.getDatabaseCommonInteractionProps(req),
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEntityResponse(req, res, item, this.entityType);
@ -399,7 +339,7 @@ export default class BaseAPI<
query: {
_id: objectId.toString(),
},
props: await this.getDatabaseCommonInteractionProps(req),
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEmptyResponse(req, res);
@ -427,7 +367,7 @@ export default class BaseAPI<
_id: objectIdString,
},
data: item,
props: await this.getDatabaseCommonInteractionProps(req),
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEmptyResponse(req, res);
@ -440,7 +380,7 @@ export default class BaseAPI<
await this.onBeforeCreate(req, res);
const body: JSONObject = req.body;
const item: TBaseModel = JSONFunctions.fromJSON<TBaseModel>(
const item: TBaseModel = BaseModel.fromJSON<TBaseModel>(
body['data'] as JSONObject,
this.entityType
) as TBaseModel;
@ -452,7 +392,7 @@ export default class BaseAPI<
const createBy: CreateBy<TBaseModel> = {
data: item,
miscDataProps: miscDataProps,
props: await this.getDatabaseCommonInteractionProps(req),
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
};
const savedItem: BaseModel = await this.service.create(createBy);

View File

@ -0,0 +1,455 @@
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
OneUptimeRequest,
} from '../Utils/Express';
import UserMiddleware from '../Middleware/UserAuthorization';
import PositiveNumber from 'Common/Types/PositiveNumber';
import BadRequestException from 'Common/Types/Exception/BadRequestException';
import Response from '../Utils/Response';
import ObjectID from 'Common/Types/ObjectID';
import { JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import CreateBy from '../Types/AnalyticsDatabase/CreateBy';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
import Query from '../Types/AnalyticsDatabase/Query';
import Select from '../Types/AnalyticsDatabase/Select';
import Sort from '../Types/AnalyticsDatabase/Sort';
import {
DEFAULT_LIMIT,
LIMIT_PER_PROJECT,
} from 'Common/Types/Database/LimitMax';
import { UserPermission } from 'Common/Types/Permission';
import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel';
import AnalyticsDatabaseService from '../Services/AnalyticsDatabaseService';
import CommonAPI from './CommonAPI';
export default class BaseAnalyticsAPI<
TAnalyticsDataModel extends AnalyticsDataModel,
TBaseService extends AnalyticsDatabaseService<AnalyticsDataModel>
> {
public entityType: { new (): TAnalyticsDataModel };
public router: ExpressRouter;
public service: TBaseService;
public constructor(
type: { new (): TAnalyticsDataModel },
service: TBaseService
) {
this.entityType = type;
const router: ExpressRouter = Express.getRouter();
// Create
router.post(
`${new this.entityType().crudApiPath.toString()}`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
await this.createItem(req, res);
} catch (err) {
next(err);
}
}
);
// List
router.post(
`${new this.entityType().crudApiPath?.toString()}/get-list`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
await this.getList(req, res);
} catch (err) {
next(err);
}
}
);
// List
router.get(
`${new this.entityType().crudApiPath?.toString()}/get-list`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
await this.getList(req, res);
} catch (err) {
next(err);
}
}
);
// count
router.post(
`${new this.entityType().crudApiPath?.toString()}/count`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
await this.count(req, res);
} catch (err) {
next(err);
}
}
);
// Get Item
router.post(
`${new this.entityType().crudApiPath?.toString()}/:id/get-item`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
await this.getItem(req, res);
} catch (err) {
next(err);
}
}
);
// Get Item
router.get(
`${new this.entityType().crudApiPath?.toString()}/:id/get-item`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
await this.getItem(req, res);
} catch (err) {
next(err);
}
}
);
// Update
router.put(
`${new this.entityType().crudApiPath?.toString()}/:id`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
await this.updateItem(req, res);
} catch (err) {
next(err);
}
}
);
// Delete
router.delete(
`${new this.entityType().crudApiPath?.toString()}/:id`,
UserMiddleware.getUserMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
await this.deleteItem(req, res);
} catch (err) {
next(err);
}
}
);
this.router = router;
this.service = service;
}
public async getPermissionsForTenant(
req: ExpressRequest
): Promise<Array<UserPermission>> {
const permissions: Array<UserPermission> = [];
const props: DatabaseCommonInteractionProps =
await CommonAPI.getDatabaseCommonInteractionProps(req);
if (
props &&
props.userTenantAccessPermission &&
props.userTenantAccessPermission[props.tenantId?.toString() || '']
) {
return (
props.userTenantAccessPermission[
props.tenantId?.toString() || ''
]?.permissions || []
);
}
return permissions;
}
public getTenantId(req: ExpressRequest): ObjectID | null {
if ((req as OneUptimeRequest).tenantId) {
return (req as OneUptimeRequest).tenantId as ObjectID;
}
return null;
}
public async getList(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
await this.onBeforeList(req, res);
const skip: PositiveNumber = req.query['skip']
? new PositiveNumber(req.query['skip'] as string)
: new PositiveNumber(0);
const limit: PositiveNumber = req.query['limit']
? new PositiveNumber(req.query['limit'] as string)
: new PositiveNumber(DEFAULT_LIMIT);
if (limit.toNumber() > LIMIT_PER_PROJECT) {
throw new BadRequestException(
'Limit should be less than ' + LIMIT_PER_PROJECT
);
}
let query: Query<AnalyticsDataModel> = {};
let select: Select<AnalyticsDataModel> = {};
let sort: Sort<AnalyticsDataModel> = {};
if (req.body) {
query = JSONFunctions.deserialize(
req.body['query']
) as Query<AnalyticsDataModel>;
select = JSONFunctions.deserialize(
req.body['select']
) as Select<AnalyticsDataModel>;
sort = JSONFunctions.deserialize(
req.body['sort']
) as Sort<AnalyticsDataModel>;
}
const databaseProps: DatabaseCommonInteractionProps =
await CommonAPI.getDatabaseCommonInteractionProps(req);
const list: Array<AnalyticsDataModel> = await this.service.findBy({
query,
select,
skip: skip,
limit: limit,
sort: sort,
props: databaseProps,
});
const count: PositiveNumber = await this.service.countBy({
query,
props: databaseProps,
});
return Response.sendEntityArrayResponse(
req,
res,
list,
count,
this.entityType
);
}
public async count(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
let query: Query<AnalyticsDataModel> = {};
await this.onBeforeCount(req, res);
if (req.body) {
query = JSONFunctions.deserialize(
req.body['query']
) as Query<AnalyticsDataModel>;
}
const databaseProps: DatabaseCommonInteractionProps =
await CommonAPI.getDatabaseCommonInteractionProps(req);
const count: PositiveNumber = await this.service.countBy({
query,
props: databaseProps,
});
return Response.sendJsonObjectResponse(req, res, {
count: count.toNumber(),
});
}
public async getItem(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
const objectId: ObjectID = new ObjectID(req.params['id'] as string);
await this.onBeforeGet(req, res);
let select: Select<AnalyticsDataModel> = {};
if (req.body) {
select = JSONFunctions.deserialize(
req.body['select']
) as Select<AnalyticsDataModel>;
}
const item: AnalyticsDataModel | null = await this.service.findOneById({
id: objectId,
select,
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEntityResponse(req, res, item, this.entityType);
}
public async deleteItem(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
await this.onBeforeDelete(req, res);
const objectId: ObjectID = new ObjectID(req.params['id'] as string);
await this.service.deleteOneBy({
query: {
_id: objectId.toString(),
},
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEmptyResponse(req, res);
}
public async updateItem(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
await this.onBeforeUpdate(req, res);
const objectId: ObjectID = new ObjectID(req.params['id'] as string);
const objectIdString: string = objectId.toString();
const body: JSONObject = req.body;
const item: TAnalyticsDataModel =
AnalyticsDataModel.fromJSON<TAnalyticsDataModel>(
body['data'] as JSONObject,
this.entityType
) as TAnalyticsDataModel;
delete (item as any)['_id'];
delete (item as any)['createdAt'];
delete (item as any)['updatedAt'];
await this.service.updateOneBy({
query: {
_id: objectIdString,
},
data: item,
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
});
return Response.sendEmptyResponse(req, res);
}
public async createItem(
req: ExpressRequest,
res: ExpressResponse
): Promise<void> {
await this.onBeforeCreate(req, res);
const body: JSONObject = req.body;
const item: TAnalyticsDataModel =
AnalyticsDataModel.fromJSON<TAnalyticsDataModel>(
body['data'] as JSONObject,
this.entityType
) as TAnalyticsDataModel;
const createBy: CreateBy<TAnalyticsDataModel> = {
data: item,
props: await CommonAPI.getDatabaseCommonInteractionProps(req),
};
const savedItem: AnalyticsDataModel = await this.service.create(
createBy
);
return Response.sendEntityResponse(
req,
res,
savedItem,
this.entityType
);
}
public getRouter(): ExpressRouter {
return this.router;
}
public getEntityName(): string {
return this.entityType.name;
}
protected async onBeforeList(
_req: ExpressRequest,
_res: ExpressResponse
): Promise<any> {
return Promise.resolve(true);
}
protected async onBeforeCreate(
_req: ExpressRequest,
_res: ExpressResponse
): Promise<any> {
return Promise.resolve(true);
}
protected async onBeforeGet(
_req: ExpressRequest,
_res: ExpressResponse
): Promise<any> {
return Promise.resolve(true);
}
protected async onBeforeUpdate(
_req: ExpressRequest,
_res: ExpressResponse
): Promise<any> {
return Promise.resolve(true);
}
protected async onBeforeDelete(
_req: ExpressRequest,
_res: ExpressResponse
): Promise<any> {
return Promise.resolve(true);
}
protected async onBeforeCount(
_req: ExpressRequest,
_res: ExpressResponse
): Promise<any> {
return Promise.resolve(true);
}
}

View File

@ -1,6 +1,5 @@
import BadDataException from 'Common/Types/Exception/BadDataException';
import { JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Permission, { UserPermission } from 'Common/Types/Permission';
import BillingInvoice from 'Model/Models/BillingInvoice';
import Project from 'Model/Models/Project';
@ -19,6 +18,7 @@ import {
import Response from '../Utils/Response';
import BaseAPI from './BaseAPI';
import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus';
import BaseModel from 'Common/Models/BaseModel';
export default class UserAPI extends BaseAPI<
BillingInvoice,
@ -98,7 +98,7 @@ export default class UserAPI extends BaseAPI<
const body: JSONObject = req.body;
const item: BillingInvoice =
JSONFunctions.fromJSON<BillingInvoice>(
BaseModel.fromJSON<BillingInvoice>(
body['data'] as JSONObject,
this.entityType
) as BillingInvoice;

View File

@ -0,0 +1,65 @@
import { IsBillingEnabled } from '../EnvironmentConfig';
import ProjectService from '../Services/ProjectService';
import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan';
import UserType from 'Common/Types/UserType';
import { ExpressRequest, OneUptimeRequest } from '../Utils/Express';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
export default class CommonAPI {
public static async getDatabaseCommonInteractionProps(
req: ExpressRequest
): Promise<DatabaseCommonInteractionProps> {
const props: DatabaseCommonInteractionProps = {
tenantId: undefined,
userGlobalAccessPermission: undefined,
userTenantAccessPermission: undefined,
userId: undefined,
userType: (req as OneUptimeRequest).userType,
isMultiTenantRequest: undefined,
};
if (
(req as OneUptimeRequest).userAuthorization &&
(req as OneUptimeRequest).userAuthorization?.userId
) {
props.userId = (req as OneUptimeRequest).userAuthorization!.userId;
}
if ((req as OneUptimeRequest).userGlobalAccessPermission) {
props.userGlobalAccessPermission = (
req as OneUptimeRequest
).userGlobalAccessPermission;
}
if ((req as OneUptimeRequest).userTenantAccessPermission) {
props.userTenantAccessPermission = (
req as OneUptimeRequest
).userTenantAccessPermission;
}
if ((req as OneUptimeRequest).tenantId) {
props.tenantId = (req as OneUptimeRequest).tenantId || undefined;
}
if (req.headers['is-multi-tenant-query']) {
props.isMultiTenantRequest = true;
}
if (IsBillingEnabled && props.tenantId) {
const plan: {
plan: PlanSelect | null;
isSubscriptionUnpaid: boolean;
} = await ProjectService.getCurrentPlan(props.tenantId!);
props.currentPlan = plan.plan || undefined;
props.isSubscriptionUnpaid = plan.isSubscriptionUnpaid;
}
// check for root permissions.
if (props.userType === UserType.MasterAdmin) {
props.isMasterAdmin = true;
}
return props;
}
}

View File

@ -15,6 +15,7 @@ import BadDataException from 'Common/Types/Exception/BadDataException';
import MonitorStatus from 'Model/Models/MonitorStatus';
import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline';
import OneUptimeDate from 'Common/Types/Date';
import CommonAPI from './CommonAPI';
export default class MonitorGroupAPI extends BaseAPI<
MonitorGroup,
@ -47,7 +48,9 @@ export default class MonitorGroupAPI extends BaseAPI<
new ObjectID(
req.params['monitorGroupId'].toString()
),
await this.getDatabaseCommonInteractionProps(req)
await CommonAPI.getDatabaseCommonInteractionProps(
req
)
);
return Response.sendEntityResponse(
@ -91,7 +94,9 @@ export default class MonitorGroupAPI extends BaseAPI<
),
startDate,
endDate,
await this.getDatabaseCommonInteractionProps(req)
await CommonAPI.getDatabaseCommonInteractionProps(
req
)
);
return Response.sendEntityArrayResponse(

View File

@ -69,6 +69,8 @@ import IncidentState from 'Model/Models/IncidentState';
import IncidentStateService from '../Services/IncidentStateService';
import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState';
import ScheduledMaintenanceStateService from '../Services/ScheduledMaintenanceStateService';
import BaseModel from 'Common/Models/BaseModel';
import CommonAPI from './CommonAPI';
export default class StatusPageAPI extends BaseAPI<
StatusPage,
@ -324,12 +326,12 @@ export default class StatusPageAPI extends BaseAPI<
});
const response: JSONObject = {
statusPage: JSONFunctions.toJSON(item, StatusPage),
footerLinks: JSONFunctions.toJSONArray(
statusPage: BaseModel.toJSON(item, StatusPage),
footerLinks: BaseModel.toJSONArray(
footerLinks,
StatusPageFooterLink
),
headerLinks: JSONFunctions.toJSONArray(
headerLinks: BaseModel.toJSONArray(
headerLinks,
StatusPageHeaderLink
),
@ -411,7 +413,9 @@ export default class StatusPageAPI extends BaseAPI<
if (
!(await this.service.hasReadAccess(
objectId,
await this.getDatabaseCommonInteractionProps(req),
await CommonAPI.getDatabaseCommonInteractionProps(
req
),
req
))
) {
@ -986,52 +990,52 @@ export default class StatusPageAPI extends BaseAPI<
const response: JSONObject = {
scheduledMaintenanceEventsPublicNotes:
JSONFunctions.toJSONArray(
BaseModel.toJSONArray(
scheduledMaintenanceEventsPublicNotes,
ScheduledMaintenancePublicNote
),
scheduledMaintenanceEvents: JSONFunctions.toJSONArray(
scheduledMaintenanceEvents: BaseModel.toJSONArray(
scheduledMaintenanceEvents,
ScheduledMaintenance
),
activeAnnouncements: JSONFunctions.toJSONArray(
activeAnnouncements: BaseModel.toJSONArray(
activeAnnouncements,
StatusPageAnnouncement
),
incidentPublicNotes: JSONFunctions.toJSONArray(
incidentPublicNotes: BaseModel.toJSONArray(
incidentPublicNotes,
IncidentPublicNote
),
activeIncidents: JSONFunctions.toJSONArray(
activeIncidents: BaseModel.toJSONArray(
activeIncidents,
Incident
),
monitorStatusTimelines: JSONFunctions.toJSONArray(
monitorStatusTimelines: BaseModel.toJSONArray(
monitorStatusTimelines,
MonitorStatusTimeline
),
resourceGroups: JSONFunctions.toJSONArray(
resourceGroups: BaseModel.toJSONArray(
groups,
StatusPageGroup
),
monitorStatuses: JSONFunctions.toJSONArray(
monitorStatuses: BaseModel.toJSONArray(
monitorStatuses,
MonitorStatus
),
statusPageResources: JSONFunctions.toJSONArray(
statusPageResources: BaseModel.toJSONArray(
statusPageResources,
StatusPageResource
),
incidentStateTimelines: JSONFunctions.toJSONArray(
incidentStateTimelines: BaseModel.toJSONArray(
incidentStateTimelines,
IncidentStateTimeline
),
statusPage: JSONFunctions.toJSONObject(
statusPage: BaseModel.toJSONObject(
statusPage,
StatusPage
),
scheduledMaintenanceStateTimelines:
JSONFunctions.toJSONArray(
BaseModel.toJSONArray(
scheduledMaintenanceStateTimelines,
ScheduledMaintenanceStateTimeline
),
@ -1068,7 +1072,9 @@ export default class StatusPageAPI extends BaseAPI<
if (
!(await this.service.hasReadAccess(
objectId,
await this.getDatabaseCommonInteractionProps(req),
await CommonAPI.getDatabaseCommonInteractionProps(
req
),
req
))
) {
@ -1144,7 +1150,7 @@ export default class StatusPageAPI extends BaseAPI<
const response: JSONObject = await this.getIncidents(
objectId,
null,
await this.getDatabaseCommonInteractionProps(req),
await CommonAPI.getDatabaseCommonInteractionProps(req),
req
);
@ -1174,7 +1180,9 @@ export default class StatusPageAPI extends BaseAPI<
await this.getScheduledMaintenanceEvents(
objectId,
null,
await this.getDatabaseCommonInteractionProps(req),
await CommonAPI.getDatabaseCommonInteractionProps(
req
),
req
);
@ -1203,7 +1211,7 @@ export default class StatusPageAPI extends BaseAPI<
const response: JSONObject = await this.getAnnouncements(
objectId,
null,
await this.getDatabaseCommonInteractionProps(req),
await CommonAPI.getDatabaseCommonInteractionProps(req),
req
);
@ -1236,7 +1244,7 @@ export default class StatusPageAPI extends BaseAPI<
const response: JSONObject = await this.getIncidents(
objectId,
incidentId,
await this.getDatabaseCommonInteractionProps(req),
await CommonAPI.getDatabaseCommonInteractionProps(req),
req
);
@ -1270,7 +1278,9 @@ export default class StatusPageAPI extends BaseAPI<
await this.getScheduledMaintenanceEvents(
objectId,
scheduledMaintenanceId,
await this.getDatabaseCommonInteractionProps(req),
await CommonAPI.getDatabaseCommonInteractionProps(
req
),
req
);
@ -1303,7 +1313,7 @@ export default class StatusPageAPI extends BaseAPI<
const response: JSONObject = await this.getAnnouncements(
objectId,
announcementId,
await this.getDatabaseCommonInteractionProps(req),
await CommonAPI.getDatabaseCommonInteractionProps(req),
req
);
@ -1619,23 +1629,23 @@ export default class StatusPageAPI extends BaseAPI<
});
const response: JSONObject = {
scheduledMaintenanceEventsPublicNotes: JSONFunctions.toJSONArray(
scheduledMaintenanceEventsPublicNotes: BaseModel.toJSONArray(
scheduledMaintenanceEventsPublicNotes,
ScheduledMaintenancePublicNote
),
scheduledMaintenanceStates: JSONFunctions.toJSONArray(
scheduledMaintenanceStates: BaseModel.toJSONArray(
scheduledEventStates,
ScheduledMaintenanceState
),
scheduledMaintenanceEvents: JSONFunctions.toJSONArray(
scheduledMaintenanceEvents: BaseModel.toJSONArray(
scheduledMaintenanceEvents,
ScheduledMaintenance
),
statusPageResources: JSONFunctions.toJSONArray(
statusPageResources: BaseModel.toJSONArray(
statusPageResources,
StatusPageResource
),
scheduledMaintenanceStateTimelines: JSONFunctions.toJSONArray(
scheduledMaintenanceStateTimelines: BaseModel.toJSONArray(
scheduledMaintenanceStateTimelines,
ScheduledMaintenanceStateTimeline
),
@ -1742,11 +1752,11 @@ export default class StatusPageAPI extends BaseAPI<
});
const response: JSONObject = {
announcements: JSONFunctions.toJSONArray(
announcements: BaseModel.toJSONArray(
announcements,
StatusPageAnnouncement
),
statusPageResources: JSONFunctions.toJSONArray(
statusPageResources: BaseModel.toJSONArray(
statusPageResources,
StatusPageResource
),
@ -2049,20 +2059,20 @@ export default class StatusPageAPI extends BaseAPI<
});
const response: JSONObject = {
incidentPublicNotes: JSONFunctions.toJSONArray(
incidentPublicNotes: BaseModel.toJSONArray(
incidentPublicNotes,
IncidentPublicNote
),
incidentStates: JSONFunctions.toJSONArray(
incidentStates: BaseModel.toJSONArray(
incidentStates,
IncidentState
),
incidents: JSONFunctions.toJSONArray(incidents, Incident),
statusPageResources: JSONFunctions.toJSONArray(
incidents: BaseModel.toJSONArray(incidents, Incident),
statusPageResources: BaseModel.toJSONArray(
statusPageResources,
StatusPageResource
),
incidentStateTimelines: JSONFunctions.toJSONArray(
incidentStateTimelines: BaseModel.toJSONArray(
incidentStateTimelines,
IncidentStateTimeline
),

View File

@ -1,25 +1,41 @@
import SocketIO from 'socket.io';
import http from 'http';
import Express, { ExpressApplication } from '../Utils/Express';
const app: ExpressApplication = Express.getExpressApp();
const server: http.Server = http.createServer(app);
import Express from '../Utils/Express';
import { createAdapter } from '@socket.io/redis-adapter';
import Redis, { ClientType } from './Redis';
import DatabaseNotConnectedException from 'Common/Types/Exception/DatabaseNotConnectedException';
import { RealtimeRoute } from 'Common/ServiceRoute';
export type Socket = SocketIO.Socket;
export type SocketServer = SocketIO.Server;
const io: SocketIO.Server = new SocketIO.Server(server, {
path: '/realtime/socket.io',
transports: ['websocket', 'polling'], // Using websocket does not require sticky session
perMessageDeflate: {
threshold: 1024, // Defaults to 1024
zlibDeflateOptions: {
chunkSize: 16 * 1024, // Defaults to 16 * 1024
},
zlibInflateOptions: {
windowBits: 15, // Defaults to 15
memLevel: 8, // Defaults to 8
},
},
});
export default abstract class IO {
private static socketServer: SocketIO.Server | null = null;
export default io;
public static init(): void {
const server: http.Server = Express.getHttpServer();
this.socketServer = new SocketIO.Server(server, {
path: RealtimeRoute.toString(),
});
if (!Redis.getClient()) {
throw new DatabaseNotConnectedException(
'Redis is not connected. Please connect to Redis before connecting to SocketIO.'
);
}
const pubClient: ClientType = Redis.getClient()!.duplicate();
const subClient: ClientType = Redis.getClient()!.duplicate();
this.socketServer.adapter(createAdapter(pubClient, subClient));
}
public static getSocketServer(): SocketIO.Server | null {
if (!this.socketServer) {
this.init();
}
return this.socketServer;
}
}

View File

@ -16,7 +16,9 @@ import {
OnUpdate,
} from '../Types/AnalyticsDatabase/Hooks';
import Typeof from 'Common/Types/Typeof';
import ModelPermission from '../Types/AnalyticsDatabase/ModelPermission';
import ModelPermission, {
CheckReadPermissionType,
} from '../Types/AnalyticsDatabase/ModelPermission';
import ObjectID from 'Common/Types/ObjectID';
import Exception from 'Common/Types/Exception/Exception';
import API from 'Common/Utils/API';
@ -32,7 +34,6 @@ import UpdateBy from '../Types/AnalyticsDatabase/UpdateBy';
import FindBy from '../Types/AnalyticsDatabase/FindBy';
import PositiveNumber from 'Common/Types/PositiveNumber';
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
import Query from '../Types/AnalyticsDatabase/Query';
import Select from '../Types/AnalyticsDatabase/Select';
import { ExecResult } from '@clickhouse/client';
import { Stream } from 'node:stream';
@ -43,6 +44,11 @@ import FindOneByID from '../Types/AnalyticsDatabase/FindOneByID';
import OneUptimeDate from 'Common/Types/Date';
import CreateManyBy from '../Types/AnalyticsDatabase/CreateManyBy';
import StatementGenerator from '../Utils/AnalyticsDatabase/StatementGenerator';
import CountBy from '../Types/AnalyticsDatabase/CountBy';
import DeleteOneBy from '../Types/AnalyticsDatabase/DeleteOneBy';
import UpdateOneBy from '../Types/AnalyticsDatabase/UpdateOneBy';
import Realtime from '../Utils/Realtime';
import { ModelEventType } from 'Common/Utils/Realtime';
export default class AnalyticsDatabaseService<
TBaseModel extends AnalyticsBaseModel
@ -74,6 +80,40 @@ export default class AnalyticsDatabaseService<
});
}
public async countBy(
countBy: CountBy<TBaseModel>
): Promise<PositiveNumber> {
try {
const checkReadPermissionType: CheckReadPermissionType<TBaseModel> =
await ModelPermission.checkReadPermission(
this.modelType,
countBy.query,
null,
countBy.props
);
countBy.query = checkReadPermissionType.query;
const countStatement: string = this.toCountStatement(countBy);
const dbResult: ExecResult<Stream> = await this.execute(
countStatement
);
const strResult: string = await StreamUtil.convertStreamToText(
dbResult.stream
);
let countPositive: PositiveNumber = new PositiveNumber(strResult);
countPositive = await this.onCountSuccess(countPositive);
return countPositive;
} catch (error) {
await this.onCountError(error as Exception);
throw this.getException(error as Exception);
}
}
public async findBy(
findBy: FindBy<TBaseModel>
): Promise<Array<TBaseModel>> {
@ -84,6 +124,9 @@ export default class AnalyticsDatabaseService<
findBy: FindBy<TBaseModel>
): Promise<Array<TBaseModel>> {
try {
debugger;
if (!findBy.sort || Object.keys(findBy.sort).length === 0) {
findBy.sort = {
createdAt: SortOrder.Descending,
@ -111,10 +154,8 @@ export default class AnalyticsDatabaseService<
(onBeforeFind.select as any)['_id'] = true;
}
const result: {
query: Query<TBaseModel>;
select: Select<TBaseModel> | null;
} = await ModelPermission.checkReadPermission(
const result: CheckReadPermissionType<TBaseModel> =
await ModelPermission.checkReadPermission(
this.modelType,
onBeforeFind.query,
onBeforeFind.select || null,
@ -151,8 +192,8 @@ export default class AnalyticsDatabaseService<
let items: Array<TBaseModel> =
AnalyticsBaseModel.fromJSONArray<TBaseModel>(
this.modelType,
jsonItems
jsonItems,
this.modelType
);
items = this.sanitizeFindByItems(items, onBeforeFind);
@ -229,6 +270,36 @@ export default class AnalyticsDatabaseService<
return Promise.resolve({ findBy, carryForward: null });
}
public toCountStatement(countBy: CountBy<TBaseModel>): string {
if (!this.database) {
this.useDefaultDatabase();
}
let statement: string = `SELECT count() FROM ${
this.database.getDatasourceOptions().database
}.${this.model.tableName}
${
Object.keys(countBy.query).length > 0 ? 'WHERE' : ''
} ${this.statementGenerator.toWhereStatement(countBy.query)}
`;
if (countBy.limit) {
statement += `
LIMIT ${countBy.limit.toString()}
`;
}
if (countBy.skip) {
statement += `
OFFSET ${countBy.skip.toString()}
`;
}
logger.info(`${this.model.tableName} Count Statement`);
logger.info(statement);
return statement;
}
public toFindStatement(findBy: FindBy<TBaseModel>): {
statement: string;
columns: Array<string>;
@ -247,8 +318,8 @@ export default class AnalyticsDatabaseService<
Object.keys(findBy.query).length > 0 ? 'WHERE' : ''
} ${this.statementGenerator.toWhereStatement(findBy.query)}
ORDER BY ${this.statementGenerator.toSortStatemennt(findBy.sort!)}
LIMIT ${findBy.limit}
OFFSET ${findBy.skip}
LIMIT ${findBy.limit.toString()}
OFFSET ${findBy.skip.toString()}
`;
logger.info(`${this.model.tableName} Find Statement`);
@ -262,7 +333,7 @@ export default class AnalyticsDatabaseService<
this.useDefaultDatabase();
}
const statement: string = `ALTER TABLE ${
let statement: string = `ALTER TABLE ${
this.database.getDatasourceOptions().database
}.${this.model.tableName}
DELETE ${
@ -270,6 +341,18 @@ export default class AnalyticsDatabaseService<
} ${this.statementGenerator.toWhereStatement(deleteBy.query)}
`;
if (deleteBy.limit) {
statement += `
LIMIT ${deleteBy.limit.toString()}
`;
}
if (deleteBy.skip) {
statement += `
OFFSET ${deleteBy.skip.toString()}
`;
}
logger.info(`${this.model.tableName} Delete Statement`);
logger.info(statement);
@ -291,6 +374,14 @@ export default class AnalyticsDatabaseService<
return null;
}
public async deleteOneBy(deleteBy: DeleteOneBy<TBaseModel>): Promise<void> {
return await this._deleteBy({
...deleteBy,
limit: 1,
skip: 0,
});
}
public async deleteBy(deleteBy: DeleteBy<TBaseModel>): Promise<void> {
return await this._deleteBy(deleteBy);
}
@ -341,6 +432,15 @@ export default class AnalyticsDatabaseService<
});
}
public async updateOneBy(
updateOneBy: UpdateOneBy<TBaseModel>
): Promise<void> {
return await this._updateBy({
...updateOneBy,
limit: 1,
});
}
public async updateBy(updateBy: UpdateBy<TBaseModel>): Promise<void> {
await this._updateBy(updateBy);
}
@ -589,6 +689,42 @@ export default class AnalyticsDatabaseService<
}
}
// emit realtime events to the client.
if (
this.getModel().enableRealtimeEventsOn?.create &&
this.model.getTenantColumn()
) {
if (Realtime.isInitialized()) {
const promises: Array<Promise<void>> = [];
for (const item of items) {
const tenantId: ObjectID | null =
item.getTenantColumnValue();
if (!tenantId) {
continue;
}
promises.push(
Realtime.emitModelEvent({
model: item,
tenantId: tenantId,
eventType: ModelEventType.Create,
modelType: this.modelType,
})
);
}
await Promise.allSettled(promises);
} else {
logger.warn(
`Realtime is not initialized. Skipping emitModelEvent for ${
this.getModel().tableName
}`
);
}
}
return createBy.items;
} catch (error) {
await this.onCreateError(error as Exception);

View File

@ -128,7 +128,7 @@ import MonitorGroupService from './MonitorGroupService';
import MonitorGroupResourceService from './MonitorGroupResourceService';
import MonitorGroupOwnerUserService from './MonitorGroupOwnerUserService';
import MonitorGroupOwnerTeamService from './MonitorGroupOwnerTeamService';
import ServiceService from './ServiceService';
import TelemetryServiceService from './TelemetryServiceService';
const services: Array<BaseService> = [
PromoCodeService,
@ -245,7 +245,7 @@ const services: Array<BaseService> = [
MonitorGroupOwnerUserService,
MonitorGroupOwnerTeamService,
ServiceService,
TelemetryServiceService,
];
export const AnalyticsServices: Array<

View File

@ -37,4 +37,5 @@ export class Service extends DatabaseService<Model> {
return Promise.resolve({ createBy, carryForward: null });
}
}
export default new Service();

View File

@ -7,4 +7,5 @@ export class LogService extends AnalyticsDatabaseService<Log> {
super({ modelType: Log, database: clickhouseDatabase });
}
}
export default new LogService();

View File

@ -11,6 +11,7 @@ import UserNotificationEventType from 'Common/Types/UserNotification/UserNotific
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
this.hardDeleteItemsOlderThanInDays('createdAt', 30);
}
protected override async onBeforeCreate(

View File

@ -1,5 +1,5 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/Service';
import Model from 'Model/Models/TelemetryService';
import DatabaseService from './DatabaseService';
import CreateBy from '../Types/Database/CreateBy';
import { OnCreate } from '../Types/Database/Hooks';
@ -13,7 +13,7 @@ export class Service extends DatabaseService<Model> {
protected override async onBeforeCreate(
createBy: CreateBy<Model>
): Promise<OnCreate<Model>> {
createBy.data.serviceToken = ObjectID.generate();
createBy.data.telemetryServiceToken = ObjectID.generate();
return {
carryForward: null,

View File

@ -20,6 +20,7 @@ import OnCallDutyExecutionLogTimelineStatus from 'Common/Types/OnCallDutyPolicy/
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
this.hardDeleteItemsOlderThanInDays('createdAt', 30);
}
protected override async onBeforeCreate(

View File

@ -20,6 +20,7 @@ import ProjectService from '../../Services/ProjectService';
import UserType from 'Common/Types/UserType';
import { mockRouter } from './Helpers';
import { UserPermission } from 'Common/Types/Permission';
import CommonAPI from '../../API/CommonAPI';
jest.mock('../../Utils/Express', () => {
return {
@ -139,9 +140,7 @@ describe('BaseAPI', () => {
baseApiInstance = new BaseAPI(BaseModel, TestService);
emptyDatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
emptyRequest
);
await CommonAPI.getDatabaseCommonInteractionProps(emptyRequest);
});
afterEach(() => {
@ -246,7 +245,7 @@ describe('BaseAPI', () => {
describe('BaseAPI.getPermissionsForTenant', () => {
it('should return empty permissions if userTenantAccessPermission is not set', async () => {
jest.spyOn(
baseApiInstance,
CommonAPI,
'getDatabaseCommonInteractionProps'
).mockResolvedValueOnce({});
const permissions: UserPermission[] =
@ -258,7 +257,7 @@ describe('BaseAPI', () => {
// eslint-disable-next-line @typescript-eslint/typedef
const mockPermissions = [{ permission: 'granted' }];
jest.spyOn(
baseApiInstance,
CommonAPI,
'getDatabaseCommonInteractionProps'
).mockResolvedValueOnce({
userTenantAccessPermission: {
@ -274,7 +273,7 @@ describe('BaseAPI', () => {
it('should return empty permissions if tenantId is not available', async () => {
jest.spyOn(
baseApiInstance,
CommonAPI,
'getDatabaseCommonInteractionProps'
).mockResolvedValueOnce({
userTenantAccessPermission: {
@ -321,9 +320,7 @@ describe('BaseAPI', () => {
it('should initialize props with undefined values', async () => {
const props: DatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
request
);
await CommonAPI.getDatabaseCommonInteractionProps(request);
expect(props).toEqual(
expect.objectContaining({
tenantId: undefined,
@ -339,45 +336,35 @@ describe('BaseAPI', () => {
it('should set userId if userAuthorization is present', async () => {
request.userAuthorization = { userId: new ObjectID('123') } as any;
const props: DatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
request
);
await CommonAPI.getDatabaseCommonInteractionProps(request);
expect(props.userId).toEqual(new ObjectID('123'));
});
it('should set userGlobalAccessPermission if present in the request', async () => {
request.userGlobalAccessPermission = { canEdit: true } as any;
const props: DatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
request
);
await CommonAPI.getDatabaseCommonInteractionProps(request);
expect(props.userGlobalAccessPermission).toEqual({ canEdit: true });
});
it('should set userTenantAccessPermission if present in the request', async () => {
request.userTenantAccessPermission = { canView: true } as any;
const props: DatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
request
);
await CommonAPI.getDatabaseCommonInteractionProps(request);
expect(props.userTenantAccessPermission).toEqual({ canView: true });
});
it('should set tenantId if present in the request', async () => {
request.tenantId = new ObjectID('456');
const props: DatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
request
);
await CommonAPI.getDatabaseCommonInteractionProps(request);
expect(props.tenantId).toEqual(new ObjectID('456'));
});
it('should set isMultiTenantRequest based on headers', async () => {
request.headers['is-multi-tenant-query'] = 'true';
const props: DatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
request
);
await CommonAPI.getDatabaseCommonInteractionProps(request);
expect(props.isMultiTenantRequest).toBe(true);
});
@ -394,18 +381,14 @@ describe('BaseAPI', () => {
);
const props: DatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
request
);
await CommonAPI.getDatabaseCommonInteractionProps(request);
expect(props.currentPlan).toBe('Free');
expect(props.isSubscriptionUnpaid).toBe(false);
});
it('should set currentPlan and isSubscriptionUnpaid to undefined if tenantId is not present', async () => {
const props: DatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
request
);
await CommonAPI.getDatabaseCommonInteractionProps(request);
expect(props.currentPlan).toBeUndefined();
expect(props.isSubscriptionUnpaid).toBeUndefined();
});
@ -414,9 +397,7 @@ describe('BaseAPI', () => {
it('should set isMasterAdmin if userType is MasterAdmin', async () => {
request.userType = UserType.MasterAdmin;
const props: DatabaseCommonInteractionProps =
await baseApiInstance.getDatabaseCommonInteractionProps(
request
);
await CommonAPI.getDatabaseCommonInteractionProps(request);
expect(props.isMasterAdmin).toBe(true);
});
});

View File

@ -0,0 +1,11 @@
import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel';
import PositiveNumber from 'Common/Types/PositiveNumber';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
import Query from './Query';
export default interface CountBy<TBaseModel extends AnalyticsBaseModel> {
query: Query<TBaseModel>;
skip?: PositiveNumber | number;
limit?: PositiveNumber | number;
props: DatabaseCommonInteractionProps;
}

View File

@ -1,8 +1,9 @@
import Query from './Query';
import BaseModel from 'Common/AnalyticsModels/BaseModel';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
import PositiveNumber from 'Common/Types/PositiveNumber';
import DeleteOneBy from './DeleteOneBy';
export default interface DeleteOneBy<TBaseModel extends BaseModel> {
query: Query<TBaseModel>;
props: DatabaseCommonInteractionProps;
export default interface DeleteBy<TBaseModel extends BaseModel>
extends DeleteOneBy<TBaseModel> {
limit?: PositiveNumber | number | undefined;
skip?: PositiveNumber | number | undefined;
}

View File

@ -0,0 +1,8 @@
import Query from './Query';
import BaseModel from 'Common/AnalyticsModels/BaseModel';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
export default interface DeleteOneBy<TBaseModel extends BaseModel> {
query: Query<TBaseModel>;
props: DatabaseCommonInteractionProps;
}

View File

@ -1,9 +1,8 @@
import BaseModel from 'Common/AnalyticsModels/BaseModel';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
import Query from './Query';
import UpdateOneBy from './UpdateOneBy';
import PositiveNumber from 'Common/Types/PositiveNumber';
export default interface UpdateBy<TBaseModel extends BaseModel> {
query: Query<TBaseModel>;
data: TBaseModel;
props: DatabaseCommonInteractionProps;
export default interface UpdateBy<TBaseModel extends BaseModel>
extends UpdateOneBy<TBaseModel> {
limit?: PositiveNumber | number | undefined;
}

View File

@ -0,0 +1,9 @@
import BaseModel from 'Common/AnalyticsModels/BaseModel';
import DatabaseCommonInteractionProps from 'Common/Types/BaseDatabase/DatabaseCommonInteractionProps';
import Query from './Query';
export default interface UpdateOneBy<TBaseModel extends BaseModel> {
query: Query<TBaseModel>;
data: TBaseModel;
props: DatabaseCommonInteractionProps;
}

View File

@ -103,7 +103,7 @@ export default class CreateManyBaseModel<
array.push(
(await this.modelService.create({
data: JSONFunctions.fromJSON<TBaseModel>(
data: BaseModel.fromJSON<TBaseModel>(
(item as JSONObject) || {},
this.modelService.modelType
) as TBaseModel,
@ -118,7 +118,7 @@ export default class CreateManyBaseModel<
return {
returnValues: {
models: JSONFunctions.toJSONArray(
models: BaseModel.toJSONArray(
array,
this.modelService.modelType
),

View File

@ -97,7 +97,7 @@ export default class CreateOneBaseModel<
}
const model: TBaseModel = (await this.modelService.create({
data: JSONFunctions.fromJSON<TBaseModel>(
data: BaseModel.fromJSON<TBaseModel>(
(args['json'] as JSONObject) || {},
this.modelService.modelType
) as TBaseModel,
@ -109,10 +109,7 @@ export default class CreateOneBaseModel<
return {
returnValues: {
model: JSONFunctions.toJSON(
model,
this.modelService.modelType
),
model: BaseModel.toJSON(model, this.modelService.modelType),
},
executePort: successPort,
};

View File

@ -172,7 +172,7 @@ export default class FindManyBaseModel<
return {
returnValues: {
models: JSONFunctions.toJSONArray(
models: BaseModel.toJSONArray(
models,
this.modelService.modelType
),

View File

@ -144,10 +144,7 @@ export default class FindOneBaseModel<
return {
returnValues: {
model: model
? JSONFunctions.toJSON(
model,
this.modelService.modelType
)
? BaseModel.toJSON(model, this.modelService.modelType)
: null,
},
executePort: successPort,

View File

@ -98,10 +98,7 @@ export default class OnTriggerBaseModel<
return {
returnValues: {
model: data
? JSONFunctions.toJSON(
data as any,
this.service!.modelType
)
? BaseModel.toJSON(data as any, this.service!.modelType)
: null,
},
executePort: successPort,
@ -136,10 +133,7 @@ export default class OnTriggerBaseModel<
return {
returnValues: {
model: data
? JSONFunctions.toJSON(
model as any,
this.service!.modelType
)
? BaseModel.toJSON(model as any, this.service!.modelType)
: null,
},
executePort: successPort,

View File

@ -29,7 +29,7 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
}
public toUpdateStatement(updateBy: UpdateBy<TBaseModel>): string {
const statement: string = `ALTER TABLE ${
let statement: string = `ALTER TABLE ${
this.database.getDatasourceOptions().database
}.${this.model.tableName}
UPDATE ${this.toSetStatement(updateBy.data)}
@ -38,6 +38,10 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
} ${this.toWhereStatement(updateBy.query)}
`;
if (updateBy.limit) {
statement += ` LIMIT ${updateBy.limit}`;
}
logger.info(`${this.model.tableName} Update Statement`);
logger.info(statement);
@ -284,7 +288,7 @@ export default class StatementGenerator<TBaseModel extends AnalyticsBaseModel> {
throw new BadDataException(`Unknown column: ${key}`);
}
whereStatement += `${key} = ${this.sanitizeValue(
whereStatement += ` ${key} = ${this.sanitizeValue(
value,
tableColumn
)} AND`;

View File

@ -11,6 +11,7 @@ import {
import UserType from 'Common/Types/UserType';
import Dictionary from 'Common/Types/Dictionary';
import Port from 'Common/Types/Port';
import { Server, createServer } from 'http';
export type RequestHandler = express.RequestHandler;
export type NextFunction = express.NextFunction;
@ -47,6 +48,7 @@ export interface OneUptimeResponse extends express.Response {
class Express {
private static app: express.Application;
private static httpServer: Server;
public static getRouter(): express.Router {
return express.Router();
@ -56,6 +58,10 @@ class Express {
this.app = express();
}
public static getHttpServer(): Server {
return this.httpServer;
}
public static getExpressApp(): express.Application {
if (!this.app) {
this.setupExpress();
@ -72,12 +78,19 @@ class Express {
this.setupExpress();
}
if (!this.httpServer) {
this.httpServer = createServer(this.app);
}
return new Promise<express.Application>((resolve: Function) => {
this.app.listen(port?.toNumber() || this.app.get('port'), () => {
this.httpServer.listen(
port?.toNumber() || this.app.get('port'),
() => {
// eslint-disable-next-line
logger.info(`${appName} server started on port: ${port?.toNumber() || this.app.get('port')}`);
return resolve(this.app);
});
}
);
});
}
}

View File

@ -0,0 +1,153 @@
import { JSONObject } from 'Common/Types/JSON';
import IO, { SocketServer, Socket } from '../Infrastructure/SocketIO';
import RealtimeUtil, {
EventName,
ListenToModelEventJSON,
ModelEventType,
} from 'Common/Utils/Realtime';
import DatabaseType from 'Common/Types/BaseDatabase/DatabaseType';
import JSONFunctions from 'Common/Types/JSONFunctions';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ObjectID from 'Common/Types/ObjectID';
import BaseModel from 'Common/Models/BaseModel';
import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel';
import logger from './Logger';
export default abstract class Realtime {
private static socketServer: SocketServer | null = null;
public static isInitialized(): boolean {
return this.socketServer !== null;
}
public static init(): SocketServer | null {
if (!this.socketServer) {
this.socketServer = IO.getSocketServer();
logger.info('Realtime socket server initialized');
}
this.socketServer!.on('connection', (socket: Socket) => {
socket.on(
EventName.ListenToModalEvent,
async (data: JSONObject) => {
// TODO: validate if this soocket has access to this tenant
// TODO: validate if this socket has access to this model
// TODO: validate if this socket has access to this event type
// TODO: validate if this socket has access to this query
// TODO: validate if this socket has access to this select
// validate data
if (typeof data['eventType'] !== 'string') {
throw new BadDataException('eventType is not a string');
}
if (typeof data['modelType'] !== 'string') {
throw new BadDataException('modelType is not a string');
}
if (typeof data['modelName'] !== 'string') {
throw new BadDataException('modelName is not a string');
}
if (typeof data['query'] !== 'object') {
throw new BadDataException('query is not an object');
}
if (typeof data['tenantId'] !== 'string') {
throw new BadDataException('tenantId is not a string');
}
if (typeof data['select'] !== 'object') {
throw new BadDataException('select is not an object');
}
await Realtime.listenToModelEvent(socket, {
eventType: data['eventType'] as ModelEventType,
modelType: data['modelType'] as DatabaseType,
modelName: data['modelName'] as string,
query: JSONFunctions.deserialize(
data['query'] as JSONObject
),
tenantId: data['tenantId'] as string,
select: JSONFunctions.deserialize(
data['select'] as JSONObject
),
});
}
);
});
return this.socketServer;
}
public static async listenToModelEvent(
socket: Socket,
data: ListenToModelEventJSON
): Promise<void> {
if (!this.socketServer) {
this.init();
}
const roomId: string = RealtimeUtil.getRoomId(
data.tenantId,
data.modelName,
data.eventType
);
// join the room.
await socket.join(roomId);
}
public static async stopListeningToModelEvent(
socket: Socket,
data: ListenToModelEventJSON
): Promise<void> {
if (!this.socketServer) {
this.init();
}
// leave this room.
await socket.leave(
RealtimeUtil.getRoomId(
data.tenantId,
data.modelName,
data.eventType
)
);
}
public static async emitModelEvent(data: {
tenantId: string | ObjectID;
eventType: ModelEventType;
model: BaseModel | AnalyticsBaseModel;
modelType: { new (): BaseModel | AnalyticsBaseModel };
}): Promise<void> {
if (!this.socketServer) {
this.init();
}
let jsonObject: JSONObject = {};
if (data.model instanceof BaseModel) {
jsonObject = BaseModel.toJSON(
data.model,
data.modelType as { new (): BaseModel }
);
}
if (data.model instanceof AnalyticsBaseModel) {
jsonObject = AnalyticsBaseModel.toJSON(
data.model,
data.modelType as { new (): AnalyticsBaseModel }
);
}
const roomId: string = RealtimeUtil.getRoomId(
data.tenantId,
data.model.tableName!,
data.eventType
);
this.socketServer!.to(roomId).emit(roomId, jsonObject);
}
}

View File

@ -13,11 +13,11 @@ import PositiveNumber from 'Common/Types/PositiveNumber';
import URL from 'Common/Types/API/URL';
import BaseModel from 'Common/Models/BaseModel';
import EmptyResponse from 'Common/Types/API/EmptyResponse';
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';
import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel';
export default class Response {
private static logResponse(
@ -174,29 +174,40 @@ export default class Response {
public static sendEntityArrayResponse(
req: ExpressRequest,
res: ExpressResponse,
list: Array<BaseModel>,
list: Array<BaseModel | AnalyticsDataModel>,
count: PositiveNumber | number,
modelType: { new (): BaseModel }
modelType: { new (): BaseModel | AnalyticsDataModel }
): void {
if (!(count instanceof PositiveNumber)) {
count = new PositiveNumber(count);
}
return this.sendJsonArrayResponse(
req,
res,
JSONFunctions.serializeArray(
JSONFunctions.toJSONArray(list as Array<BaseModel>, modelType)
),
count
let jsonArray: JSONArray = [];
const model: BaseModel | AnalyticsDataModel = new modelType();
if (model instanceof BaseModel) {
jsonArray = BaseModel.toJSONArray(
list as Array<BaseModel>,
modelType as { new (): BaseModel }
);
}
if (model instanceof AnalyticsDataModel) {
jsonArray = AnalyticsDataModel.toJSONArray(
list as Array<AnalyticsDataModel>,
modelType as { new (): AnalyticsDataModel }
);
}
return this.sendJsonArrayResponse(req, res, jsonArray, count);
}
public static sendEntityResponse(
req: ExpressRequest,
res: ExpressResponse,
item: BaseModel | null,
modelType: { new (): BaseModel },
item: BaseModel | AnalyticsDataModel | null,
modelType: { new (): BaseModel | AnalyticsDataModel },
options?:
| {
miscData?: JSONObject;
@ -205,9 +216,17 @@ export default class Response {
): void {
let response: JSONObject = {};
if (item) {
response = JSONFunctions.serialize(
JSONFunctions.toJSONObject(item, modelType)
if (item && item instanceof BaseModel) {
response = BaseModel.toJSON(
item,
modelType as { new (): BaseModel }
);
}
if (item && item instanceof AnalyticsDataModel) {
response = AnalyticsDataModel.toJSON(
item,
modelType as { new (): AnalyticsDataModel }
);
}

View File

@ -14,6 +14,7 @@
"@opentelemetry/api": "^1.1.0",
"@opentelemetry/auto-instrumentations-node": "^0.31.0",
"@opentelemetry/sdk-node": "^0.30.0",
"@socket.io/redis-adapter": "^8.2.1",
"@types/ejs": "^3.1.1",
"@types/gridfs-stream": "^0.5.35",
"@types/json2csv": "^5.0.3",
@ -3733,6 +3734,22 @@
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@socket.io/redis-adapter": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-8.2.1.tgz",
"integrity": "sha512-6Dt7EZgGSBP0qvXeOKGx7NnSr2tPMbVDfDyL97zerZo+v69hMfL99skMCL3RKZlWVqLyRme2T0wcy3udHhtOsg==",
"dependencies": {
"debug": "~4.3.1",
"notepack.io": "~3.0.1",
"uid2": "1.0.0"
},
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"socket.io-adapter": "^2.4.0"
}
},
"node_modules/@sqltools/formatter": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
@ -8328,6 +8345,11 @@
"node": ">=0.10.0"
}
},
"node_modules/notepack.io": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz",
"integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg=="
},
"node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@ -10269,6 +10291,14 @@
"node": ">=0.8.0"
}
},
"node_modules/uid2": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/uid2/-/uid2-1.0.0.tgz",
"integrity": "sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/undici": {
"version": "5.27.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.27.2.tgz",

View File

@ -17,6 +17,7 @@
"@opentelemetry/api": "^1.1.0",
"@opentelemetry/auto-instrumentations-node": "^0.31.0",
"@opentelemetry/sdk-node": "^0.30.0",
"@socket.io/redis-adapter": "^8.2.1",
"@types/ejs": "^3.1.1",
"@types/gridfs-stream": "^0.5.35",
"@types/json2csv": "^5.0.3",
@ -43,7 +44,7 @@
"nodemailer": "^6.7.3",
"nodemailer-express-handlebars": "^5.0.0",
"pg": "^8.7.3",
"socket.io": "^4.4.1",
"socket.io": "^4.7.2",
"stripe": "^10.17.0",
"twilio": "^4.13.0",
"typeorm": "^0.3.10",

View File

@ -57,6 +57,7 @@
"redux": "^4.2.0",
"rehype-sanitize": "^5.0.1",
"remark-gfm": "^3.0.1",
"socket.io-client": "^4.7.2",
"tailwindcss": "^3.2.4",
"tippy.js": "^6.3.7",
"universal-cookie": "^4.0.4",
@ -1885,6 +1886,11 @@
"@sinonjs/commons": "^1.7.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@testing-library/dom": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz",
@ -3867,6 +3873,26 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/engine.io-client": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.11.0",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@ -11136,6 +11162,32 @@
"node": ">=8"
}
},
"node_modules/socket.io-client": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz",
"integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -12129,7 +12181,6 @@
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"dev": true,
"engines": {
"node": ">=10.0.0"
},
@ -12161,6 +12212,14 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -13631,6 +13690,11 @@
"@sinonjs/commons": "^1.7.0"
}
},
"@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"@testing-library/dom": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.19.0.tgz",
@ -15260,6 +15324,23 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"engine.io-client": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.11.0",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"engine.io-parser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
"integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ=="
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@ -20338,6 +20419,26 @@
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
"socket.io-client": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz",
"integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"socket.io-parser": "~4.2.4"
}
},
"socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -21049,7 +21150,6 @@
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"dev": true,
"requires": {}
},
"xml-name-validator": {
@ -21064,6 +21164,11 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@ -59,6 +59,7 @@
"redux": "^4.2.0",
"rehype-sanitize": "^5.0.1",
"remark-gfm": "^3.0.1",
"socket.io-client": "^4.7.2",
"tailwindcss": "^3.2.4",
"tippy.js": "^6.3.7",
"universal-cookie": "^4.0.4",

View File

@ -23,6 +23,7 @@ export interface ComponentProps {
tabIndex?: number | undefined;
error?: string | undefined;
value?: string | undefined;
showLineNumbers?: boolean | undefined;
}
const CodeEditor: FunctionComponent<ComponentProps> = (
@ -153,7 +154,7 @@ const CodeEditor: FunctionComponent<ComponentProps> = (
scrollBeyondLastColumn: 5,
scrollBeyondLastLine: true,
selectOnLineNumbers: true,
lineNumbers: 'off',
lineNumbers: props.showLineNumbers ? 'on' :'off',
selectionClipboard: true,
selectionHighlight: true,
showFoldingControls: 'mouseover',

View File

@ -0,0 +1,16 @@
import React, { FunctionComponent, ReactElement } from 'react';
import EmptyState from '../EmptyState/EmptyState';
import IconProp from 'Common/Types/Icon/IconProp';
const ComingSoon: FunctionComponent = (): ReactElement => {
return (
<EmptyState
id="coming-soon"
icon={IconProp.CursorArrowRays}
title="Coming soon!"
description="We will be launching this feature very soon. Stay Tuned!"
/>
);
};
export default ComingSoon;

View File

@ -108,7 +108,7 @@ const Dropdown: FunctionComponent<ComponentProps> = (
return (
<div
className={`${
props.className || 'relative mt-2 mb-1 rounded-md w-full'
props.className || 'relative mt-2 mb-1 rounded-md w-full overflow-visible'
}`}
onClick={() => {
props.onClick && props.onClick();

View File

@ -13,7 +13,6 @@ import HTTPResponse from 'Common/Types/API/HTTPResponse';
import Route from 'Common/Types/API/Route';
import Navigation from '../../Utils/Navigation';
import BasicFormModal from '../FormModal/BasicFormModal';
import JSONFunctions from 'Common/Types/JSONFunctions';
export interface ComponentProps<TBaseModel extends BaseModel> {
modelType: { new (): TBaseModel };
@ -119,7 +118,7 @@ const DuplicateModel: <TBaseModel extends BaseModel>(
onSubmit={(item: TBaseModel) => {
setShowModal(false);
duplicateItem(
JSONFunctions.fromJSONObject(
BaseModel.fromJSONObject(
item,
props.modelType
) as TBaseModel

View File

@ -8,6 +8,7 @@ export interface ComponentProps {
icon: IconProp | undefined;
footer?: ReactElement | undefined;
id: string;
iconClassName?: string;
}
const EmptyState: FunctionComponent<ComponentProps> = (
@ -20,7 +21,10 @@ const EmptyState: FunctionComponent<ComponentProps> = (
{props.icon && (
<Icon
icon={props.icon}
className="mx-auto h-12 w-12 text-gray-400"
className={
props.iconClassName ||
`mx-auto h-12 w-12 text-gray-400`
}
/>
)}

View File

@ -30,7 +30,6 @@ import TableColumnType from 'Common/Types/Database/TableColumnType';
import Typeof from 'Common/Types/Typeof';
import { TableColumnMetadata } from 'Common/Types/Database/TableColumn';
import { ButtonStyleType } from '../Button/Button';
import JSONFunctions from 'Common/Types/JSONFunctions';
import API from '../../Utils/API/API';
import { FormStep } from './Types/FormStep';
import Field from './Types/Field';
@ -292,7 +291,7 @@ const ModelForm: <TBaseModel extends BaseModel>(
);
if (!(item instanceof BaseModel) && item) {
item = JSONFunctions.fromJSON(
item = BaseModel.fromJSON(
item as JSONObject,
props.modelType
) as BaseModel;
@ -524,7 +523,7 @@ const ModelForm: <TBaseModel extends BaseModel>(
}
}
let tBaseModel: TBaseModel = JSONFunctions.fromJSON(
let tBaseModel: TBaseModel = BaseModel.fromJSON(
valuesToSend,
props.modelType
) as TBaseModel;
@ -552,7 +551,7 @@ const ModelForm: <TBaseModel extends BaseModel>(
if (props.onSuccess) {
props.onSuccess(
JSONFunctions.fromJSONObject(result.data, props.modelType),
BaseModel.fromJSONObject(result.data, props.modelType),
miscData
);
}

View File

@ -141,6 +141,10 @@ const Icon: FunctionComponent<ComponentProps> = ({
d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z"
/>
);
}else if (icon === IconProp.Stop) {
return getSvgWrapper(
<path strokeLinecap="round" strokeLinejoin="round" d="M5.25 7.5A2.25 2.25 0 017.5 5.25h9a2.25 2.25 0 012.25 2.25v9a2.25 2.25 0 01-2.25 2.25h-9a2.25 2.25 0 01-2.25-2.25v-9z" />
);
} else if (icon === IconProp.Copy) {
return getSvgWrapper(
<path
@ -165,6 +169,22 @@ const Icon: FunctionComponent<ComponentProps> = ({
d="M6 6.878V6a2.25 2.25 0 012.25-2.25h7.5A2.25 2.25 0 0118 6v.878m-12 0c.235-.083.487-.128.75-.128h10.5c.263 0 .515.045.75.128m-12 0A2.25 2.25 0 004.5 9v.878m13.5-3A2.25 2.25 0 0119.5 9v.878m0 0a2.246 2.246 0 00-.75-.128H5.25c-.263 0-.515.045-.75.128m15 0A2.25 2.25 0 0121 12v6a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18v-6c0-.98.626-1.813 1.5-2.122"
/>
);
} else if (icon === IconProp.CursorArrowRays) {
return getSvgWrapper(
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M15.042 21.672L13.684 16.6m0 0l-2.51 2.225.569-9.47 5.227 7.917-3.286-.672zM12 2.25V4.5m5.834.166l-1.591 1.591M20.25 10.5H18M7.757 14.743l-1.59 1.59M6 10.5H3.75m4.007-4.243l-1.59-1.59"
/>
);
} else if (icon === IconProp.ChartBar) {
return getSvgWrapper(
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
/>
);
} else if (icon === IconProp.SquareStack) {
return getSvgWrapper(
<path

View File

@ -0,0 +1,147 @@
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
import Log from 'Model/AnalyticsModels/Log';
import OneUptimeDate from 'Common/Types/Date';
export interface ComponentProps {
log: Log;
}
const LogItem: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const [isCollapsed, setIsCollapsed] = React.useState<boolean>(true);
useEffect(() => {
setIsCollapsed(true);
}, []);
let bodyColor: string = 'text-slate-200';
if (props.log.severityText === 'Warning') {
bodyColor = 'text-amber-400';
} else if (props.log.severityText === 'Error') {
bodyColor = 'text-rose-400';
}
if (isCollapsed) {
return (
<div
className="text-slate-200 flex cursor-pointer hover:border-slate-700 px-2 border-transparent border-2 rounded-md"
onClick={() => {
setIsCollapsed(false);
}}
>
{props.log.time && (
<div
className="text-slate-500 courier-prime flex-none"
style={{
width: '230px !important',
}}
>
{OneUptimeDate.getDateAsLocalFormattedString(
props.log.time
)}{' '}
&nbsp;{' '}
</div>
)}
{props.log.severityText === 'Information' && (
<div className="text-sky-400 courier-prime flex-none">
[INFO] &nbsp;
</div>
)}
{props.log.severityText === 'Warning' && (
<div className="text-amber-400 courier-prime flex-none">
[WARN] &nbsp;
</div>
)}
{props.log.severityText === 'Error' && (
<div className="text-rose-400 courier-prime flex-none">
[EROR] &nbsp;
</div>
)}
<div className={`${bodyColor} courier-prime`}>
{props.log.body?.toString()}
</div>
</div>
);
}
return (
<div
className="text-slate-200 cursor-pointer hover:border-slate-700 px-2 border-transparent border-2 rounded-md"
onClick={() => {
setIsCollapsed(true);
}}
>
{props.log.time && (
<div className="text-slate-500 courier-prime">
{OneUptimeDate.getDateAsFormattedString(props.log.time)}{' '}
&nbsp;{' '}
</div>
)}
{props.log.severityText === 'Information' && (
<div className="flex">
<div className="font-medium text-slate-200 courier-prime mr-2">
SEVERITY:
</div>
<div className="text-sky-400 courier-prime">
[INFO] &nbsp;
</div>
</div>
)}
{props.log.severityText === 'Warning' && (
<div className="flex">
<div className="font-medium text-slate-200 courier-prime mr-2">
SEVERITY:
</div>
<div className="text-amber-400 courier-prime">
[WARN] &nbsp;
</div>
</div>
)}
{props.log.severityText === 'Error' && (
<div className="flex">
<div className="font-medium text-slate-200 courier-prime mr-2">
SEVERITY:
</div>
<div className="text-rose-400 courier-prime">
[EROR] &nbsp;
</div>
</div>
)}
<div className="flex">
<div className="font-medium text-slate-200 courier-prime mr-2">
MESSAGE:&nbsp;
</div>
<div className={`${bodyColor} courier-prime`}>
{props.log.body?.toString()}
</div>
</div>
{props.log.traceId && (
<div className="flex">
<div className="font-medium text-slate-200 courier-prime mr-2">
TRACE:&nbsp;&nbsp;&nbsp;
</div>
<div className={`${bodyColor} courier-prime`}>
{props.log.traceId?.toString()}
</div>
</div>
)}
{props.log.spanId && (
<div className="flex">
<div className="font-medium text-slate-200 courier-prime mr-2">
SPAN:&nbsp;&nbsp;&nbsp;&nbsp;
</div>
<div className={`${bodyColor} courier-prime`}>
{props.log.spanId?.toString()}
</div>
</div>
)}
</div>
);
};
export default LogItem;

View File

@ -0,0 +1,218 @@
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
import Input from '../Input/Input';
import Button, { ButtonStyleType } from '../Button/Button';
import IconProp from 'Common/Types/Icon/IconProp';
import Dropdown from '../Dropdown/Dropdown';
import FieldLabelElement from '../Forms/Fields/FieldLabel';
import TelemetryService from 'Model/Models/TelemetryService';
import CodeEditor from '../CodeEditor/CodeEditor';
import CodeType from 'Common/Types/Code/CodeType';
export interface FiterOptions {
searchText?: string | undefined;
}
export interface ComponentProps {
onFilterChanged: (filterOptions: FiterOptions) => void;
onAutoScrollChanged: (turnOnAutoScroll: boolean) => void;
telemetryServices?: Array<TelemetryService>
}
const LogsFilters: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const [filterOptions, setFilterOptions] = React.useState<FiterOptions>({});
const [turnOnAutoScroll, setTurnOnAutoScroll] = React.useState<boolean>(true);
const [showMoreFilters, setShowMoreFilters] = React.useState<boolean>(false);
const [isSqlQuery, setIsSqlQuery] = React.useState<boolean>(false);
useEffect(() => {
props.onFilterChanged(filterOptions);
}, [filterOptions]);
return (
<div className="shadow sm:overflow-hidden sm:rounded-md">
<div className="bg-white py-6 px-4 sm:p-6">
<div >
<div className='flex space-x-2 justify-between'>
<div className='w-full mr-2'>
{!isSqlQuery && <div className='space-y-4'>
<div>
<FieldLabelElement
title='Search Logs'
required={true}
/>
<Input
placeholder="Search"
onChange={(value: string) => {
setFilterOptions({
searchText: value,
});
}}
/>
</div>
{showMoreFilters && <div>
<FieldLabelElement
title='Telemetry Services'
required={true}
/>
<Dropdown isMultiSelect={true} options={[
{
label: 'Information',
value: 'information'
},
{
label: 'Warning',
value: 'warning'
},
{
label: 'Error',
value: 'error'
}
]} />
</div>}
{showMoreFilters && <div>
<FieldLabelElement
title='Log Severity'
required={true}
/>
<Dropdown isMultiSelect={true} options={[
{
label: 'Information',
value: 'information'
},
{
label: 'Warning',
value: 'warning'
},
{
label: 'Error',
value: 'error'
}
]} />
</div>}
{showMoreFilters && <div className="flex space-x-2 w-full">
<div className='w-1/2'>
<FieldLabelElement
title='Start Time'
required={true}
/>
<Input
placeholder="Start Time"
onChange={(value: string) => {
setFilterOptions({
searchText: value,
});
}}
type={'datetime-local'}
/>
</div>
<div className='w-1/2'>
<FieldLabelElement
title='End Time'
required={true}
/>
<Input
placeholder="End Time"
onChange={(value: string) => {
setFilterOptions({
searchText: value,
});
}}
type={'datetime-local'}
/>
</div>
</div>}
</div>}
{isSqlQuery && <div>
<div className='space-y-1'>
<FieldLabelElement
title='SQL Query'
required={true}
description='Enter a SQL query to filter logs.'
/>
<CodeEditor
type={CodeType.SQL}
placeholder="SQL Query"
onChange={(value: string) => {
setFilterOptions({
searchText: value,
});
}}
showLineNumbers={true}
/>
</div>
</div>}
</div>
{!isSqlQuery && <div>
<div className='mt-7 -ml-5 justify-end flex w-44'>
{!turnOnAutoScroll && <Button title='Start Autoscroll' icon={IconProp.Play} onClick={() => {
setTurnOnAutoScroll(true);
props.onAutoScrollChanged(true);
}} />}
{turnOnAutoScroll && <Button title='Stop Autoscroll' icon={IconProp.Stop} onClick={() => {
setTurnOnAutoScroll(false);
props.onAutoScrollChanged(false);
}} />}
</div>
</div>}
{isSqlQuery && <div className=''>
<div className='mt-12 -ml-8 justify-end flex w-44'>
<Button title='Search with SQL' onClick={() => {
}} />
</div>
</div>}
</div>
</div>
<div>
<div className='flex justify-between -ml-2 -mr-2'>
<div className='flex'>
{!isSqlQuery && <div>
{!showMoreFilters && <Button buttonStyle={ButtonStyleType.SECONDARY_LINK} title='Show More Options' onClick={() => {
setShowMoreFilters(true);
}} />}
{showMoreFilters && <Button buttonStyle={ButtonStyleType.SECONDARY_LINK} title='Hide More Options' onClick={() => {
setShowMoreFilters(false);
}} />}
</div>}
<div>
{!isSqlQuery && <Button buttonStyle={ButtonStyleType.SECONDARY_LINK} title='Search with SQL instead' onClick={() => {
setIsSqlQuery(true);
}} />}
{isSqlQuery && <Button buttonStyle={ButtonStyleType.SECONDARY_LINK} title='Search with Text instead' onClick={() => {
setIsSqlQuery(false);
}} />}
</div>
</div>
<div className='flex'>
<div>
<Button buttonStyle={ButtonStyleType.SECONDARY_LINK} title='Save as Preset' onClick={() => {
setIsSqlQuery(true);
}} />
</div>
<div>
<Button buttonStyle={ButtonStyleType.SECONDARY_LINK} title='Load from Preset' onClick={() => {
setIsSqlQuery(true);
}} />
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default LogsFilters;

View File

@ -0,0 +1,89 @@
import Log from 'Model/AnalyticsModels/Log';
import React, { FunctionComponent, ReactElement, Ref } from 'react';
import LogItem from './LogItem';
import LogsFilters, { FiterOptions } from './LogsFilters';
export interface ComponentProps {
logs: Array<Log>;
onFilterChanged: (filterOptions: FiterOptions) => void;
}
const LogsViewer: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const [screenHeight, setScreenHeight] = React.useState<number>(
window.innerHeight
);
const [autoScroll, setAutoScroll] = React.useState<boolean>(true);
const logsViewerRef: Ref<HTMLDivElement> =
React.useRef<HTMLDivElement>(null);
// Update the screen height when the window is resized
React.useEffect(() => {
const handleResize: any = (): void => {
setScreenHeight(window.innerHeight);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// Keep scroll to the bottom of the log
const scrollToBottom = (): void => {
const logsViewer: HTMLDivElement | null = logsViewerRef.current;
if (logsViewer) {
logsViewer.scrollTop = logsViewer.scrollHeight;
}
}
React.useEffect(() => {
if (!autoScroll) {
return;
}
scrollToBottom();
}, [props.logs]);
return (
<div>
<div className='mb-5'>
<LogsFilters onAutoScrollChanged={(autoscroll: boolean)=>{
setAutoScroll(autoscroll);
if(autoScroll){
scrollToBottom();
}
}} onFilterChanged={props.onFilterChanged} />
</div>
<div
ref={logsViewerRef}
className="shadow-xl rounded-xl bg-slate-800 p-5 overflow-hidden hover:overflow-y-auto dark-scrollbar"
style={{
height: screenHeight - 330,
}}
>
{props.logs.map((log: Log, i: number) => {
return <LogItem key={i} log={log} />;
})}
{props.logs.length === 0 && (
<div className={`text-slate-200 courier-prime`}>
No logs found for this service.
</div>
)}
</div>
</div>
);
};
export default LogsViewer;

View File

@ -14,7 +14,6 @@ import Field from './Field';
import DetailField from '../Detail/Field';
import Detail from '../Detail/Detail';
import API from '../../Utils/API/API';
import JSONFunctions from 'Common/Types/JSONFunctions';
import ErrorMessage from '../ErrorMessage/ErrorMessage';
import { useAsyncEffect } from 'use-async-effect';
import User from '../../Utils/User';
@ -258,7 +257,7 @@ const ModelDetail: <TBaseModel extends BaseModel>(
return (
<Detail
id={props.id}
item={JSONFunctions.toJSONObject(item, props.modelType)}
item={BaseModel.toJSONObject(item, props.modelType)}
fields={fields}
showDetailsInNumberOfColumns={props.showDetailsInNumberOfColumns}
/>

View File

@ -10,7 +10,6 @@ import { JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import Alert, { AlertType } from '../Alerts/Alert';
import FormValues from '../Forms/Types/FormValues';
import JSONFunctions from 'Common/Types/JSONFunctions';
import ModelAPI from '../../Utils/ModelAPI/ModelAPI';
export interface ComponentProps<TBaseModel extends BaseModel> {
@ -94,7 +93,7 @@ const ModelFormModal: <TBaseModel extends BaseModel>(
onSuccess={(data: TBaseModel) => {
props.onSuccess &&
props.onSuccess(
JSONFunctions.fromJSONObject(
BaseModel.fromJSONObject(
data as TBaseModel,
props.modelType
)

View File

@ -15,7 +15,6 @@ import API from '../../Utils/API/API';
import URL from 'Common/Types/API/URL';
import { JSONArray } from 'Common/Types/JSON';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import JSONFunctions from 'Common/Types/JSONFunctions';
export interface ComponentProps<TBaseModel extends BaseModel> {
query?: Query<TBaseModel>;
@ -78,7 +77,7 @@ const ModelList: <TBaseModel extends BaseModel>(
)) as HTTPResponse<JSONArray>;
listResult = {
data: JSONFunctions.fromJSONArray(
data: BaseModel.fromJSONArray(
result.data as JSONArray,
props.modelType
),

View File

@ -52,7 +52,6 @@ import SubscriptionPlan, {
} from 'Common/Types/Billing/SubscriptionPlan';
import Pill from '../Pill/Pill';
import { Yellow } from 'Common/Types/BrandColors';
import JSONFunctions from 'Common/Types/JSONFunctions';
import { ModalWidth } from '../Modal/Modal';
import ProjectUtil from '../../Utils/Project';
import API from '../../Utils/API/API';
@ -304,7 +303,7 @@ const ModelTable: <TBaseModel extends BaseModel>(
if (column.tooltipText) {
tooltipText = (item: JSONObject): string => {
return column.tooltipText!(
JSONFunctions.fromJSONObject(item, props.modelType)
BaseModel.fromJSONObject(item, props.modelType)
);
};
}
@ -479,7 +478,7 @@ const ModelTable: <TBaseModel extends BaseModel>(
if (column.tooltipText) {
classicColumn.tooltipText = (item: JSONObject): string => {
return column.tooltipText!(
JSONFunctions.fromJSONObject(item, props.modelType)
BaseModel.fromJSONObject(item, props.modelType)
);
};
}
@ -853,13 +852,10 @@ const ModelTable: <TBaseModel extends BaseModel>(
) => {
try {
const baseModel: TBaseModel =
JSONFunctions.fromJSONObject(
item,
props.modelType
);
BaseModel.fromJSONObject(item, props.modelType);
if (props.onBeforeView) {
item = JSONFunctions.toJSONObject(
item = BaseModel.toJSONObject(
await props.onBeforeView(baseModel),
props.modelType
);
@ -913,9 +909,9 @@ const ModelTable: <TBaseModel extends BaseModel>(
) => {
try {
if (props.onBeforeEdit) {
item = JSONFunctions.toJSONObject(
item = BaseModel.toJSONObject(
await props.onBeforeEdit(
JSONFunctions.fromJSONObject(
BaseModel.fromJSONObject(
item,
props.modelType
)
@ -948,9 +944,9 @@ const ModelTable: <TBaseModel extends BaseModel>(
) => {
try {
if (props.onBeforeDelete) {
item = JSONFunctions.toJSONObject(
item = BaseModel.toJSONObject(
await props.onBeforeDelete(
JSONFunctions.fromJSONObject(
BaseModel.fromJSONObject(
item,
props.modelType
)
@ -1032,7 +1028,7 @@ const ModelTable: <TBaseModel extends BaseModel>(
dragDropIdField={'_id'}
dragDropIndexField={props.dragDropIndexField}
totalItemsCount={totalItemsCount}
data={JSONFunctions.toJSONObjectArray(data, props.modelType)}
data={BaseModel.toJSONObjectArray(data, props.modelType)}
filterError={tableFilterError}
id={props.id}
columns={tableColumns}
@ -1108,7 +1104,7 @@ const ModelTable: <TBaseModel extends BaseModel>(
<OrderedStatesList
error={error}
isLoading={isLoading}
data={JSONFunctions.toJSONObjectArray(data, props.modelType)}
data={BaseModel.toJSONObjectArray(data, props.modelType)}
id={props.id}
titleField={props.orderedStatesListProps?.titleField || ''}
descriptionField={
@ -1176,7 +1172,7 @@ const ModelTable: <TBaseModel extends BaseModel>(
dragDropIndexField={props.dragDropIndexField}
isLoading={isLoading}
totalItemsCount={totalItemsCount}
data={JSONFunctions.toJSONObjectArray(data, props.modelType)}
data={BaseModel.toJSONObjectArray(data, props.modelType)}
id={props.id}
fields={fields}
itemsOnPage={itemsOnPage}
@ -1443,7 +1439,7 @@ const ModelTable: <TBaseModel extends BaseModel>(
currentDeleteableItem['_id']
) {
deleteItem(
JSONFunctions.fromJSON(
BaseModel.fromJSON(
currentDeleteableItem,
props.modelType
)

View File

@ -1,5 +1,4 @@
import { JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import React, { FunctionComponent, ReactElement } from 'react';
import Image from '../Image/Image';
import URL from 'Common/Types/API/URL';
@ -7,6 +6,7 @@ import { FILE_URL } from '../../Config';
import Probe from 'Model/Models/Probe';
import Icon from '../Icon/Icon';
import IconProp from 'Common/Types/Icon/IconProp';
import BaseModel from 'Common/Models/BaseModel';
export interface ComponentProps {
probe?: Probe | JSONObject | undefined | null;
@ -18,7 +18,7 @@ const ProbeElement: FunctionComponent<ComponentProps> = (
let probe: JSONObject | null | undefined = null;
if (props.probe instanceof Probe) {
probe = JSONFunctions.toJSONObject(props.probe, Probe);
probe = BaseModel.toJSONObject(props.probe, Probe);
} else {
probe = props.probe;
}

View File

@ -14,6 +14,7 @@ import {
StatusPageRoute,
WorkflowRoute,
homeRoute,
RealtimeRoute,
} from 'Common/ServiceRoute';
import Version from 'Common/Types/Version';
import URL from 'Common/Types/API/URL';
@ -48,6 +49,9 @@ export const NOTIFICATION_HOSTNAME: Hostname = Hostname.fromString(HOST);
export const DASHBOARD_HOSTNAME: Hostname = Hostname.fromString(HOST);
// realtime
export const REALTIME_HOSTNAME: Hostname = Hostname.fromString(HOST);
export const INTEGRATION_HOSTNAME: Hostname = Hostname.fromString(HOST);
export const STATUS_PAGE_HOSTNAME: Hostname = Hostname.fromString(HOST);
@ -72,6 +76,12 @@ export const DASHBOARD_API_URL: URL = new URL(
DashboardApiRoute
);
export const REALTIME_URL: URL = new URL(
HTTP_PROTOCOL,
REALTIME_HOSTNAME,
RealtimeRoute
);
export const IDENTITY_URL: URL = new URL(
HTTP_PROTOCOL,
IDENTITY_HOSTNAME,

View File

@ -0,0 +1,461 @@
import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel';
import ObjectID from 'Common/Types/ObjectID';
import Query from './Query';
import Select from './Select';
import API from '../API/API';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { DASHBOARD_API_URL } from '../../Config';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import HTTPMethod from 'Common/Types/API/HTTPMethod';
import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
import { FormType } from '../../Components/Forms/ModelForm';
import Dictionary from 'Common/Types/Dictionary';
import ProjectUtil from '../Project';
import Sort from './Sort';
import Project from 'Model/Models/Project';
import Navigation from '../Navigation';
export interface ListResult<TAnalyticsBaseModel extends AnalyticsBaseModel>
extends JSONObject {
data: Array<TAnalyticsBaseModel>;
count: number;
skip: number;
limit: number;
}
export interface RequestOptions {
requestHeaders?: Dictionary<string> | undefined;
overrideRequestUrl?: URL | undefined;
}
export default class ModelAPI {
public static async create<TAnalyticsBaseModel extends AnalyticsBaseModel>(
model: TAnalyticsBaseModel,
modelType: { new (): TAnalyticsBaseModel },
requestOptions?: RequestOptions | undefined
): Promise<
HTTPResponse<
| JSONObject
| JSONArray
| TAnalyticsBaseModel
| Array<TAnalyticsBaseModel>
>
> {
return await ModelAPI.createOrUpdate(
model,
modelType,
FormType.Create,
{},
requestOptions
);
}
public static async update<TAnalyticsBaseModel extends AnalyticsBaseModel>(
model: TAnalyticsBaseModel,
modelType: { new (): TAnalyticsBaseModel }
): Promise<
HTTPResponse<
| JSONObject
| JSONArray
| TAnalyticsBaseModel
| Array<TAnalyticsBaseModel>
>
> {
return await ModelAPI.createOrUpdate(model, modelType, FormType.Update);
}
public static async updateById<
TAnalyticsBaseModel extends AnalyticsBaseModel
>(
modelType: { new (): TAnalyticsBaseModel },
id: ObjectID,
data: JSONObject,
apiUrlOverride?: URL,
requestOptions?: RequestOptions
): Promise<
HTTPResponse<
| JSONObject
| JSONArray
| TAnalyticsBaseModel
| Array<TAnalyticsBaseModel>
>
> {
const model: AnalyticsBaseModel = new modelType();
let apiUrl: URL | null = apiUrlOverride || null;
if (!apiUrl) {
const apiPath: Route | null = model.crudApiPath;
if (!apiPath) {
throw new BadDataException(
'This model does not support create or update operations.'
);
}
apiUrl = URL.fromURL(DASHBOARD_API_URL).addRoute(apiPath);
}
apiUrl = apiUrl.addRoute(`/${id.toString()}`);
const result: HTTPResponse<
| JSONObject
| JSONArray
| TAnalyticsBaseModel
| Array<TAnalyticsBaseModel>
> = await API.fetch<
| JSONObject
| JSONArray
| TAnalyticsBaseModel
| Array<TAnalyticsBaseModel>
>(
HTTPMethod.PUT,
apiUrl,
{
data: data,
},
this.getCommonHeaders(requestOptions)
);
if (result.isSuccess()) {
return result;
}
this.checkStatusCode(result);
throw result;
}
public static async createOrUpdate<
TAnalyticsBaseModel extends AnalyticsBaseModel
>(
model: TAnalyticsBaseModel,
modelType: { new (): TAnalyticsBaseModel },
formType: FormType,
miscDataProps?: JSONObject,
requestOptions?: RequestOptions | undefined
): Promise<HTTPResponse<TAnalyticsBaseModel>> {
let apiUrl: URL | null = requestOptions?.overrideRequestUrl || null;
if (!apiUrl) {
const apiPath: Route | null = model.crudApiPath;
if (!apiPath) {
throw new BadDataException(
'This model does not support create or update operations.'
);
}
apiUrl = URL.fromURL(DASHBOARD_API_URL).addRoute(apiPath);
}
const httpMethod: HTTPMethod =
formType === FormType.Create ? HTTPMethod.POST : HTTPMethod.PUT;
if (httpMethod === HTTPMethod.PUT) {
apiUrl = apiUrl.addRoute(`/${model._id}`);
}
const apiResult: HTTPErrorResponse | HTTPResponse<TAnalyticsBaseModel> =
await API.fetch<TAnalyticsBaseModel>(
httpMethod,
apiUrl,
{
data: JSONFunctions.serialize(
AnalyticsBaseModel.toJSON(model, modelType)
),
miscDataProps: miscDataProps || {},
},
{
...this.getCommonHeaders(requestOptions),
...(requestOptions?.requestHeaders || {}),
}
);
if (apiResult.isSuccess() && apiResult instanceof HTTPResponse) {
const result: HTTPResponse<TAnalyticsBaseModel> =
apiResult as HTTPResponse<TAnalyticsBaseModel>;
result.data = AnalyticsBaseModel.fromJSON(
result.data,
modelType
) as TAnalyticsBaseModel;
return result;
}
this.checkStatusCode(apiResult);
throw apiResult;
}
public static async getList<TAnalyticsBaseModel extends AnalyticsBaseModel>(
modelType: { new (): TAnalyticsBaseModel },
query: Query<TAnalyticsBaseModel>,
limit: number,
skip: number,
select: Select<TAnalyticsBaseModel>,
sort: Sort<TAnalyticsBaseModel>,
requestOptions?: RequestOptions
): Promise<ListResult<TAnalyticsBaseModel>> {
const model: TAnalyticsBaseModel = new modelType();
const apiPath: Route | null = model.crudApiPath;
if (!apiPath) {
throw new BadDataException(
'This model does not support list operations.'
);
}
let apiUrl: URL = URL.fromURL(DASHBOARD_API_URL)
.addRoute(apiPath)
.addRoute('/get-list');
if (requestOptions?.overrideRequestUrl) {
apiUrl = requestOptions.overrideRequestUrl;
}
if (!apiUrl) {
throw new BadDataException(
'This model does not support list operations.'
);
}
const headers: Dictionary<string> =
this.getCommonHeaders(requestOptions);
const result: HTTPResponse<JSONArray> | HTTPErrorResponse =
await API.fetch<JSONArray>(
HTTPMethod.POST,
apiUrl,
{
query: JSONFunctions.serialize(query as JSONObject),
select: JSONFunctions.serialize(select as JSONObject),
sort: JSONFunctions.serialize(sort as JSONObject),
},
headers,
{
limit: limit.toString(),
skip: skip.toString(),
}
);
if (result.isSuccess()) {
const list: Array<TAnalyticsBaseModel> =
AnalyticsBaseModel.fromJSONArray(
result.data as JSONArray,
modelType
);
return {
data: list,
count: result.count,
skip: result.skip,
limit: result.limit,
};
}
this.checkStatusCode(result);
throw result;
}
public static async count<TAnalyticsBaseModel extends AnalyticsBaseModel>(
modelType: { new (): TAnalyticsBaseModel },
query: Query<TAnalyticsBaseModel>,
requestOptions?: RequestOptions | undefined
): Promise<number> {
const model: TAnalyticsBaseModel = new modelType();
const apiPath: Route | null = model.crudApiPath;
if (!apiPath) {
throw new BadDataException(
'This model does not support list operations.'
);
}
let apiUrl: URL = URL.fromURL(DASHBOARD_API_URL)
.addRoute(apiPath)
.addRoute('/count');
if (requestOptions?.overrideRequestUrl) {
apiUrl = requestOptions.overrideRequestUrl;
}
if (!apiUrl) {
throw new BadDataException(
'This model does not support count operations.'
);
}
const headers: Dictionary<string> =
this.getCommonHeaders(requestOptions);
const result: HTTPResponse<JSONObject> | HTTPErrorResponse =
await API.fetch<JSONObject>(
HTTPMethod.POST,
apiUrl,
{
query: JSONFunctions.serialize(query as JSONObject),
},
headers
);
if (result.isSuccess()) {
const count: number = result.data['count'] as number;
return count;
}
this.checkStatusCode(result);
throw result;
}
public static getCommonHeaders(
requestOptions?: RequestOptions
): Dictionary<string> {
let headers: Dictionary<string> = {};
if (!requestOptions || Object.keys(requestOptions).length === 0) {
const project: Project | null = ProjectUtil.getCurrentProject();
if (project && project.id) {
headers['tenantid'] = project.id.toString();
}
}
// add SSO headers.
headers = {
...headers,
};
return headers;
}
public static async getItem<TAnalyticsBaseModel extends AnalyticsBaseModel>(
modelType: { new (): TAnalyticsBaseModel },
id: ObjectID,
select: Select<TAnalyticsBaseModel>,
requestOptions?: RequestOptions | undefined
): Promise<TAnalyticsBaseModel | null> {
const apiPath: Route | null = new modelType().crudApiPath;
if (!apiPath) {
throw new BadDataException(
'This model does not support get operations.'
);
}
let apiUrl: URL = URL.fromURL(DASHBOARD_API_URL)
.addRoute(apiPath)
.addRoute('/' + id.toString())
.addRoute('/get-item');
if (requestOptions?.overrideRequestUrl) {
apiUrl = requestOptions.overrideRequestUrl;
}
if (!apiUrl) {
throw new BadDataException(
'This model does not support get operations.'
);
}
return this.post<TAnalyticsBaseModel>(
modelType,
apiUrl,
select,
requestOptions
);
}
public static async post<TAnalyticsBaseModel extends AnalyticsBaseModel>(
modelType: { new (): TAnalyticsBaseModel },
apiUrl: URL,
select?: Select<TAnalyticsBaseModel> | undefined,
requestOptions?: RequestOptions | undefined
): Promise<TAnalyticsBaseModel | null> {
const result: HTTPResponse<TAnalyticsBaseModel> | HTTPErrorResponse =
await API.fetch<TAnalyticsBaseModel>(
HTTPMethod.POST,
apiUrl,
{
select: JSONFunctions.serialize(select as JSONObject) || {},
},
this.getCommonHeaders(requestOptions)
);
if (result.isSuccess()) {
return AnalyticsBaseModel.fromJSON(
result.data,
modelType
) as TAnalyticsBaseModel;
}
this.checkStatusCode(result);
throw result;
}
public static async deleteItem<
TAnalyticsBaseModel extends AnalyticsBaseModel
>(
modelType: { new (): TAnalyticsBaseModel },
id: ObjectID,
requestOptions?: RequestOptions | undefined
): Promise<void> {
const apiPath: Route | null = new modelType().crudApiPath;
if (!apiPath) {
throw new BadDataException(
'This model does not support delete operations.'
);
}
const apiUrl: URL = URL.fromURL(DASHBOARD_API_URL)
.addRoute(apiPath)
.addRoute('/' + id.toString());
if (!apiUrl) {
throw new BadDataException(
'This model does not support delete operations.'
);
}
const result: HTTPResponse<TAnalyticsBaseModel> | HTTPErrorResponse =
await API.fetch<TAnalyticsBaseModel>(
HTTPMethod.DELETE,
apiUrl,
undefined,
this.getCommonHeaders(requestOptions)
);
if (result.isSuccess()) {
return;
}
this.checkStatusCode(result);
throw result;
}
private static checkStatusCode<
TAnalyticsBaseModel extends AnalyticsBaseModel
>(
result:
| HTTPResponse<
| TAnalyticsBaseModel
| JSONObject
| JSONArray
| Array<TAnalyticsBaseModel>
>
| HTTPErrorResponse
): void {
if (result.statusCode === 406) {
const project: Project | null = ProjectUtil.getCurrentProject();
if (project && project.id) {
Navigation.navigate(new Route(`/dashboard/${project._id}/sso`));
}
}
}
}

View File

@ -0,0 +1,17 @@
import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel';
import CompareBase from 'Common/Types/Database/CompareBase';
import InBetween from 'Common/Types/Database/InBetween';
import NotNull from 'Common/Types/Database/NotNull';
import Search from 'Common/Types/Database/Search';
import { JSONObject, JSONValue } from 'Common/Types/JSON';
type Query<TBaseModel extends AnalyticsDataModel | JSONObject> = {
[P in keyof TBaseModel]?:
| JSONValue
| Search
| InBetween
| NotNull
| CompareBase;
};
export default Query;

View File

@ -0,0 +1,8 @@
import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel';
import { JSONObject } from 'Common/Types/JSON';
type Select<TBaseModel extends AnalyticsDataModel | JSONObject> = {
[P in keyof TBaseModel]?: boolean | JSONObject;
};
export default Select;

View File

@ -0,0 +1,9 @@
import AnalyticsDataModel from 'Common/AnalyticsModels/BaseModel';
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
import { JSONObject } from 'Common/Types/JSON';
type Sort<TBaseModel extends AnalyticsDataModel | JSONObject> = {
[P in keyof TBaseModel]?: SortOrder;
};
export default Sort;

View File

@ -144,7 +144,7 @@ export default class ModelAPI {
apiUrl,
{
data: JSONFunctions.serialize(
JSONFunctions.toJSON(model, modelType)
BaseModel.toJSON(model, modelType)
),
miscDataProps: miscDataProps || {},
},
@ -165,7 +165,7 @@ export default class ModelAPI {
delete (result.data as any)['_miscData'];
}
result.data = JSONFunctions.fromJSONObject(result.data, modelType);
result.data = BaseModel.fromJSONObject(result.data, modelType);
return result;
}
@ -229,7 +229,7 @@ export default class ModelAPI {
);
if (result.isSuccess()) {
const list: Array<TBaseModel> = JSONFunctions.fromJSONArray(
const list: Array<TBaseModel> = BaseModel.fromJSONArray(
result.data as JSONArray,
modelType
);
@ -306,7 +306,7 @@ export default class ModelAPI {
): Dictionary<string> {
let headers: Dictionary<string> = {};
if (!requestOptions || !requestOptions.isMultiTenantRequest) {
if (!requestOptions || !requestOptions.isMultiTenantRequest || Object.keys(requestOptions).length === 0) {
const project: Project | null = ProjectUtil.getCurrentProject();
if (project && project.id) {
@ -375,7 +375,7 @@ export default class ModelAPI {
);
if (result.isSuccess()) {
return JSONFunctions.fromJSONObject(
return BaseModel.fromJSONObject(
result.data as JSONObject,
modelType
);

View File

@ -1,12 +1,12 @@
import LocalStorage from './LocalStorage';
import { JSONObject } from 'Common/Types/JSON';
import Project from 'Model/Models/Project';
import JSONFunctions from 'Common/Types/JSONFunctions';
import SubscriptionPlan, {
PlanSelect,
} from 'Common/Types/Billing/SubscriptionPlan';
import { BILLING_ENABLED, getAllEnvVars } from '../Config';
import ObjectID from 'Common/Types/ObjectID';
import BaseModel from 'Common/Models/BaseModel';
export default class ProjectUtil {
public static getCurrentProject(): Project | null {
@ -16,7 +16,7 @@ export default class ProjectUtil {
const projectJson: JSONObject = LocalStorage.getItem(
'current_project'
) as JSONObject;
return JSONFunctions.fromJSON(projectJson, Project) as Project;
return BaseModel.fromJSON(projectJson, Project) as Project;
}
public static getCurrentProjectId(): ObjectID | null {
@ -25,7 +25,7 @@ export default class ProjectUtil {
public static setCurrentProject(project: JSONObject | Project): void {
if (project instanceof Project) {
project = JSONFunctions.toJSON(project, Project);
project = BaseModel.toJSON(project, Project);
}
LocalStorage.setItem('current_project', project);
}

View File

@ -0,0 +1,141 @@
import AnalyticsBaseModel from 'Common/AnalyticsModels/BaseModel';
import BaseModel from 'Common/Models/BaseModel';
import AnalyticsQuery from './AnalyticsModelAPI/Query';
import Query from './ModelAPI/Query';
import RealtimeUtil, {
EventName,
ListenToModelEventJSON,
ModelEventType,
} from 'Common/Utils/Realtime';
import ObjectID from 'Common/Types/ObjectID';
import SocketIO, { Socket } from 'socket.io-client';
import { HOST, HTTP_PROTOCOL } from '../Config';
import URL from 'Common/Types/API/URL';
import JSONFunctions from 'Common/Types/JSONFunctions';
import DatabaseType from 'Common/Types/BaseDatabase/DatabaseType';
import AnalyticsSelect from './AnalyticsModelAPI/Select';
import Select from './ModelAPI/Select';
import { JSONObject } from 'Common/Types/JSON';
import { RealtimeRoute } from 'Common/ServiceRoute';
export interface ListenToAnalyticsModelEvent<Model extends AnalyticsBaseModel> {
modelType: { new (): Model };
query: AnalyticsQuery<Model>;
eventType: ModelEventType;
tenantId: ObjectID;
select: AnalyticsSelect<Model>;
}
export interface ListenToModelEvent<Model extends BaseModel> {
modelType: { new (): Model };
query: Query<Model>;
tenantId: ObjectID;
eventType: ModelEventType;
select: Select<Model>;
}
export default abstract class Reatime {
private static socket: Socket;
public static init(): void {
const socket: Socket = SocketIO(
new URL(HTTP_PROTOCOL, HOST).toString(),
{
path: RealtimeRoute.toString(),
}
);
this.socket = socket;
}
public static listenToModelEvent<Model extends BaseModel>(
listenToModelEvent: ListenToModelEvent<Model>,
onEvent: (model: Model) => void
): () => void {
// conver this to json and send it to the server.
if (!this.socket) {
this.init();
}
const listenToModelEventJSON: ListenToModelEventJSON = {
eventType: listenToModelEvent.eventType,
modelType: DatabaseType.Database,
modelName: listenToModelEvent.modelType.name,
query: JSONFunctions.serialize(listenToModelEvent.query),
tenantId: listenToModelEvent.tenantId.toString(),
select: JSONFunctions.serialize(listenToModelEvent.select),
};
this.emit(EventName.ListenToModalEvent, listenToModelEventJSON as any);
const roomId: string = RealtimeUtil.getRoomId(
listenToModelEvent.tenantId,
listenToModelEvent.modelType.name,
listenToModelEvent.eventType
);
this.socket.on(roomId, (model: JSONObject) => {
onEvent(
BaseModel.fromJSON(model, listenToModelEvent.modelType) as Model
);
});
// Stop listening to the event.
const stopListening: () => void = (): void => {
this.socket.off(roomId);
};
return stopListening;
}
public static listenToAnalyticsModelEvent<Model extends AnalyticsBaseModel>(
listenToModelEvent: ListenToAnalyticsModelEvent<Model>,
onEvent: (model: Model) => void
): () => void {
if (!this.socket) {
this.init();
}
const listenToModelEventJSON: ListenToModelEventJSON = {
eventType: listenToModelEvent.eventType,
modelType: DatabaseType.AnalyticsDatabase,
modelName: listenToModelEvent.modelType.name,
query: JSONFunctions.serialize(listenToModelEvent.query),
tenantId: listenToModelEvent.tenantId.toString(),
select: JSONFunctions.serialize(listenToModelEvent.select),
};
this.emit(EventName.ListenToModalEvent, listenToModelEventJSON as any);
const roomId: string = RealtimeUtil.getRoomId(
listenToModelEvent.tenantId,
listenToModelEvent.modelType.name,
listenToModelEvent.eventType
);
this.socket.on(roomId, (model: JSONObject) => {
onEvent(
AnalyticsBaseModel.fromJSON(
model,
listenToModelEvent.modelType
) as Model
);
});
// Stop listening to the event.
const stopListening: () => void = (): void => {
this.socket.off(roomId);
};
return stopListening;
}
public static emit(eventName: string, data: JSONObject): void {
if (!this.socket) {
this.init();
}
this.socket.emit(eventName, data);
}
}

View File

@ -34,6 +34,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Courier+Prime:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
* {
font-family: Inter;
@ -65,6 +66,15 @@
top: 0;
width: auto;
}
.courier-prime {
font-family: 'Courier Prime', monospace;
}
.dark-scrollbar {
scrollbar-color: #475569 #0f172a;
}
</style>
<script src="https://cdn.tailwindcss.com"></script>

View File

@ -187,6 +187,8 @@ import TelemetryServiceView from './Pages/Telemetry/Services/View/Index';
import TelemetryServiceViewDelete from './Pages/Telemetry/Services/View/Delete';
import TelemetryServiceViewLogs from './Pages/Telemetry/Services/View/Logs/Index';
import TelemetryServiceViewTraces from './Pages/Telemetry/Services/View/Traces/Index';
import TelemetryServiceViewMetrics from './Pages/Telemetry/Services/View/Metrics/Index';
import TelemetryServiceViewDahboard from './Pages/Telemetry/Services/View/Dashboard/Index';
const App: () => JSX.Element = () => {
Navigation.setNavigateHook(useNavigate());
@ -474,6 +476,42 @@ const App: () => JSX.Element = () => {
}
/>
<PageRoute
path={
RouteMap[
PageMap.TELEMETRY_SERVICES_VIEW_METRICS
]?.toString() || ''
}
element={
<TelemetryServiceViewMetrics
{...commonPageProps}
pageRoute={
RouteMap[
PageMap.TELEMETRY_SERVICES_VIEW_METRICS
] as Route
}
/>
}
/>
<PageRoute
path={
RouteMap[
PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS
]?.toString() || ''
}
element={
<TelemetryServiceViewDahboard
{...commonPageProps}
pageRoute={
RouteMap[
PageMap.TELEMETRY_SERVICES_VIEW_DASHBOARDS
] as Route
}
/>
}
/>
{/* Home */}
<PageRoute

View File

@ -15,7 +15,6 @@ import LabelsElement from '../../Components/Label/Labels';
import IncidentSeverity from 'Model/Models/IncidentSeverity';
import Query from 'CommonUI/src/Utils/ModelAPI/Query';
import Route from 'Common/Types/API/Route';
import JSONFunctions from 'Common/Types/JSONFunctions';
import GlobalEvents from 'CommonUI/src/Utils/GlobalEvents';
import EventName from '../../Utils/EventName';
import DashboardNavigation from '../../Utils/Navigation';
@ -34,6 +33,7 @@ import IncidentTemplateOwnerUser from 'Model/Models/IncidentTemplateOwnerUser';
import IncidentTemplateOwnerTeam from 'Model/Models/IncidentTemplateOwnerTeam';
import ObjectID from 'Common/Types/ObjectID';
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
import BaseModel from 'Common/Models/BaseModel';
export interface ComponentProps {
query?: Query<Incident> | undefined;
@ -108,7 +108,7 @@ const IncidentsTable: FunctionComponent<ComponentProps> = (
if (incidentTemplate) {
const initialValue: JSONObject = {
...JSONFunctions.toJSONObject(
...BaseModel.toJSONObject(
incidentTemplate,
IncidentTemplate
),
@ -519,7 +519,7 @@ const IncidentsTable: FunctionComponent<ComponentProps> = (
return (
<MonitorsElement
monitors={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['monitors'] as JSONArray) ||
[],
Monitor
@ -560,7 +560,7 @@ const IncidentsTable: FunctionComponent<ComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) || [],
Label
) as Array<Label>

View File

@ -0,0 +1,117 @@
import React, { FunctionComponent, ReactElement, useEffect } from 'react';
import LogsViewer from 'CommonUI/src/Components/LogsViewer/LogsViewer';
import Log from 'Model/AnalyticsModels/Log';
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import AnalyticsModelAPI, {
ListResult,
} from 'CommonUI/src/Utils/AnalyticsModelAPI/AnalyticsModelAPI';
import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader';
import API from 'CommonUI/src/Utils/API/API';
import ObjectID from 'Common/Types/ObjectID';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
import Realtime from 'CommonUI/src/Utils/Realtime';
import { ModelEventType } from 'Common/Utils/Realtime';
import ProjectUtil from 'CommonUI/src/Utils/Project';
export interface ComponentProps {
id: string;
telemetryServiceIds: Array<ObjectID>;
}
const DashboardLogsViewer: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const [logs, setLogs] = React.useState<Array<Log>>([]);
const [error, setError] = React.useState<string>('');
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const fetchItems: Function = async () => {
setError('');
setIsLoading(true);
try {
const listResult: ListResult<Log> =
await AnalyticsModelAPI.getList<Log>(
Log,
{
serviceId: props.telemetryServiceIds[0],
},
LIMIT_PER_PROJECT,
0,
{
body: true,
time: true,
projectId: true,
serviceId: true,
spanId: true,
traceId: true,
severityText: true,
},
{
time: SortOrder.Descending,
},
{}
);
// reverse the logs so that the newest logs are at the bottom
listResult.data.reverse();
setLogs(listResult.data);
} catch (err) {
setError(API.getFriendlyMessage(err));
}
setIsLoading(false);
};
useEffect(() => {
fetchItems().catch((err: unknown) => {
setError(API.getFriendlyMessage(err));
});
const disconnectFunction: () => void =
Realtime.listenToAnalyticsModelEvent(
{
modelType: Log,
query: {},
eventType: ModelEventType.Create,
tenantId: ProjectUtil.getCurrentProjectId()!,
select: {
body: true,
time: true,
projectId: true,
serviceId: true,
spanId: true,
traceId: true,
severityText: true,
},
},
(model: Log) => {
setLogs((logs: Array<Log>) => {
return [...logs, model];
});
}
);
return () => {
disconnectFunction();
};
}, []);
if (error) {
return <ErrorMessage error={error} />;
}
if (isLoading) {
return <ComponentLoader />;
}
return (
<div id={props.id}>
<LogsViewer onFilterChanged={()=>{}} logs={logs} />
</div>
);
};
export default DashboardLogsViewer;

View File

@ -13,7 +13,6 @@ import MonitorStatus from 'Model/Models/MonitorStatus';
import Query from 'CommonUI/src/Utils/ModelAPI/Query';
import Route from 'Common/Types/API/Route';
import MonitorType from 'Common/Types/Monitor/MonitorType';
import JSONFunctions from 'Common/Types/JSONFunctions';
import DashboardNavigation from '../../Utils/Navigation';
import MonitorTypeUtil from '../../Utils/MonitorType';
import FormValues from 'CommonUI/src/Components/Forms/Types/FormValues';
@ -26,6 +25,7 @@ import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal';
import MonitoringInterval from '../../Utils/MonitorIntervalDropdownOptions';
import MonitorStepsType from 'Common/Types/Monitor/MonitorSteps';
import { Grey } from 'Common/Types/BrandColors';
import BaseModel from 'Common/Models/BaseModel';
export interface ComponentProps {
query?: Query<Monitor> | undefined;
@ -266,7 +266,7 @@ const MonitorsTable: FunctionComponent<ComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) || [],
Label
) as Array<Label>

View File

@ -129,7 +129,7 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
),
}}
>
{/* <NavBarMenuItem
<NavBarMenuItem
title="Telemetry"
description="Logs, Traces, Metrics and more."
route={RouteUtil.populateRouteParams(
@ -139,7 +139,7 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
onClick={() => {
forceHideMoreMenu();
}}
/> */}
/>
<NavBarMenuItem
title="On-Call Duty"

View File

@ -1,7 +1,6 @@
import React, { FunctionComponent, ReactElement } from 'react';
import BaseModel from 'Common/Models/BaseModel';
import { JSONObject } from 'Common/Types/JSON';
import JSONFunctions from 'Common/Types/JSONFunctions';
export interface ComponentProps {
item: JSONObject;
@ -11,7 +10,7 @@ export interface ComponentProps {
const NotificationMethodView: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const item: BaseModel = JSONFunctions.fromJSONObject(
const item: BaseModel = BaseModel.fromJSONObject(
props.item,
props.modelType
);

View File

@ -12,7 +12,6 @@ import IncidentView from '../../../Components/Incident/Incident';
import Incident from 'Model/Models/Incident';
import OnCallDutyPolicyStatus from 'Common/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus';
import UserElement from '../../../Components/User/User';
import JSONFunctions from 'Common/Types/JSONFunctions';
import User from 'Model/Models/User';
import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
import ObjectID from 'Common/Types/ObjectID';
@ -21,6 +20,7 @@ import Columns from 'CommonUI/src/Components/ModelTable/Columns';
import OnCallPolicyView from '../OnCallPolicy';
import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy';
import Navigation from 'CommonUI/src/Utils/Navigation';
import BaseModel from 'Common/Models/BaseModel';
export interface ComponentProps {
onCallDutyPolicyId?: ObjectID | undefined; // if this is undefined. then it'll show logs for all policies.
@ -163,7 +163,7 @@ const ExecutionLogsTable: FunctionComponent<ComponentProps> = (
return (
<UserElement
user={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
item['acknowledgedByUser'] as JSONObject,
User
) as User

View File

@ -11,11 +11,11 @@ import OnCallDutyPolicyExecutionLogTimeline from 'Model/Models/OnCallDutyPolicyE
import OnCallDutyExecutionLogTimelineStatus from 'Common/Types/OnCallDutyPolicy/OnCalDutyExecutionLogTimelineStatus';
import UserElement from '../../User/User';
import User from 'Model/Models/User';
import JSONFunctions from 'Common/Types/JSONFunctions';
import EscalationRule from '../EscalationRule/EscalationRule';
import OnCallDutyPolicyEscalationRule from 'Model/Models/OnCallDutyPolicyEscalationRule';
import ObjectID from 'Common/Types/ObjectID';
import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
import BaseModel from 'Common/Models/BaseModel';
export interface ComponentProps {
onCallPolicyExecutionLogId: ObjectID;
@ -127,7 +127,7 @@ const ExecutionLogTimelineTable: FunctionComponent<ComponentProps> = (
return (
<UserElement
user={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
item[
'alertSentToUser'
] as JSONObject,

View File

@ -16,11 +16,11 @@ import Route from 'Common/Types/API/Route';
import StatusPage from 'Model/Models/StatusPage';
import StatusPagesElement from '../StatusPage/StatusPagesLabel';
import MonitorStatus from 'Model/Models/MonitorStatus';
import JSONFunctions from 'Common/Types/JSONFunctions';
import DashboardNavigation from '../../Utils/Navigation';
import OneUptimeDate from 'Common/Types/Date';
import Team from 'Model/Models/Team';
import ProjectUser from '../../Utils/ProjectUser';
import BaseModel from 'Common/Models/BaseModel';
export interface ComponentProps {
query?: Query<ScheduledMaintenance> | undefined;
@ -316,7 +316,7 @@ const ScheduledMaintenancesTable: FunctionComponent<ComponentProps> = (
return (
<MonitorsElement
monitors={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['monitors'] as JSONArray) || [],
Monitor
) as Array<Monitor>
@ -349,7 +349,7 @@ const ScheduledMaintenancesTable: FunctionComponent<ComponentProps> = (
return (
<StatusPagesElement
statusPages={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['statusPages'] as JSONArray) ||
[],
StatusPage
@ -398,7 +398,7 @@ const ScheduledMaintenancesTable: FunctionComponent<ComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) || [],
Label
) as Array<Label>

View File

@ -7,6 +7,7 @@ import URL from 'Common/Types/API/URL';
import { FILE_URL } from 'CommonUI/src/Config';
import BlankProfilePic from 'CommonUI/src/Images/users/blank-profile.svg';
import Route from 'Common/Types/API/Route';
import BaseModel from 'Common/Models/BaseModel';
export interface ComponentProps {
user?: User | JSONObject | undefined | null;
@ -23,7 +24,7 @@ const UserElement: FunctionComponent<ComponentProps> = (
let user: JSONObject | null | undefined = null;
if (props.user instanceof User) {
user = JSONFunctions.toJSONObject(props.user, User);
user = BaseModel.toJSONObject(props.user, User);
} else {
user = props.user;
}

View File

@ -14,7 +14,7 @@ import Monitor from 'Model/Models/Monitor';
import Color from 'Common/Types/Color';
import ProjectElement from '../../Components/Project/Project';
import Project from 'Model/Models/Project';
import JSONFunctions from 'Common/Types/JSONFunctions';
import BaseModel from 'Common/Models/BaseModel';
const Home: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@ -90,7 +90,7 @@ const Home: FunctionComponent<PageComponentProps> = (
return (
<ProjectElement
project={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['project'] as JSONObject) ||
[],
Project
@ -198,7 +198,7 @@ const Home: FunctionComponent<PageComponentProps> = (
return (
<MonitorsElement
monitors={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['monitors'] as JSONArray) ||
[],
Monitor

View File

@ -26,7 +26,6 @@ import BaseModel from 'Common/Models/BaseModel';
import IncidentSeverity from 'Model/Models/IncidentSeverity';
import Label from 'Model/Models/Label';
import LabelsElement from '../../../Components/Label/Labels';
import JSONFunctions from 'Common/Types/JSONFunctions';
import GlobalEvent from 'CommonUI/src/Utils/GlobalEvents';
import EventName from '../../../Utils/EventName';
import OnCallDutyPoliciesView from '../../../Components/OnCallPolicy/OnCallPolicies';
@ -271,7 +270,7 @@ const IncidentView: FunctionComponent<PageComponentProps> = (
return (
<MonitorsElement
monitors={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item[
'monitors'
] as JSONArray) || [],
@ -295,7 +294,7 @@ const IncidentView: FunctionComponent<PageComponentProps> = (
return (
<OnCallDutyPoliciesView
onCallPolicies={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item[
'onCallDutyPolicies'
] as JSONArray) || [],
@ -326,7 +325,7 @@ const IncidentView: FunctionComponent<PageComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) ||
[],
Label

View File

@ -7,6 +7,7 @@ import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import DashboardNavigation from '../../../Utils/Navigation';
import ObjectID from 'Common/Types/ObjectID';
import BaseModel from 'Common/Models/BaseModel';
import IncidentInternalNote from 'Model/Models/IncidentInternalNote';
import ModelTable, {
ShowTableAs,
@ -17,7 +18,6 @@ import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { JSONObject } from 'Common/Types/JSON';
import UserElement from '../../../Components/User/User';
import User from 'Model/Models/User';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Navigation from 'CommonUI/src/Utils/Navigation';
import AlignItem from 'CommonUI/src/Types/AlignItem';
import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal';
@ -67,7 +67,7 @@ const IncidentDelete: FunctionComponent<PageComponentProps> = (
if (incidentNoteTemplate) {
const initialValue: JSONObject = {
...JSONFunctions.toJSONObject(
...BaseModel.toJSONObject(
incidentNoteTemplate,
IncidentNoteTemplate
),
@ -228,7 +228,7 @@ const IncidentDelete: FunctionComponent<PageComponentProps> = (
return (
<UserElement
user={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
item['createdByUser'] as JSONObject,
User
) as User

View File

@ -5,6 +5,7 @@ import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import BaseModel from 'Common/Models/BaseModel';
import DashboardNavigation from '../../../Utils/Navigation';
import ObjectID from 'Common/Types/ObjectID';
import IncidentPublicNote from 'Model/Models/IncidentPublicNote';
@ -17,7 +18,6 @@ import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { JSONObject } from 'Common/Types/JSON';
import UserElement from '../../../Components/User/User';
import User from 'Model/Models/User';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Navigation from 'CommonUI/src/Utils/Navigation';
import AlignItem from 'CommonUI/src/Types/AlignItem';
import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal';
@ -68,7 +68,7 @@ const PublicNote: FunctionComponent<PageComponentProps> = (
if (incidentNoteTemplate) {
const initialValue: JSONObject = {
...JSONFunctions.toJSONObject(
...BaseModel.toJSONObject(
incidentNoteTemplate,
IncidentNoteTemplate
),
@ -243,7 +243,7 @@ const PublicNote: FunctionComponent<PageComponentProps> = (
return (
<UserElement
user={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
item['createdByUser'] as JSONObject,
User
) as User

View File

@ -5,6 +5,7 @@ import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import BaseModel from 'Common/Models/BaseModel';
import DashboardNavigation from '../../../Utils/Navigation';
import ObjectID from 'Common/Types/ObjectID';
import Monitor from 'Model/Models/Monitor';
@ -17,7 +18,6 @@ import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSc
import MonitorStatus from 'Model/Models/MonitorStatus';
import Incident from 'Model/Models/Incident';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Navigation from 'CommonUI/src/Utils/Navigation';
import IncidentSeverity from 'Model/Models/IncidentSeverity';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
@ -272,7 +272,7 @@ const MonitorIncidents: FunctionComponent<PageComponentProps> = (
return (
<MonitorsElement
monitors={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['monitors'] as JSONArray) ||
[],
Monitor

View File

@ -26,7 +26,6 @@ import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import SortOrder from 'Common/Types/BaseDatabase/SortOrder';
import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline';
import JSONFunctions from 'Common/Types/JSONFunctions';
import API from 'CommonUI/src/Utils/API/API';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
import MonitorType from 'Common/Types/Monitor/MonitorType';
@ -36,6 +35,7 @@ import UptimeUtil from 'CommonUI/src/Components/MonitorGraphs/UptimeUtil';
import MonitorStatus from 'Model/Models/MonitorStatus';
import { UptimePrecision } from 'Model/Models/StatusPageResource';
import ProjectUtil from 'CommonUI/src/Utils/Project';
import BaseModel from 'Common/Models/BaseModel';
const MonitorView: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@ -350,7 +350,7 @@ const MonitorView: FunctionComponent<PageComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) ||
[],
Label

View File

@ -12,12 +12,12 @@ import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSc
import Label from 'Model/Models/Label';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import LabelsElement from '../../Components/Label/Labels';
import JSONFunctions from 'Common/Types/JSONFunctions';
import MonitorGroup from 'Model/Models/MonitorGroup';
import Navigation from 'CommonUI/src/Utils/Navigation';
import CurrentStatusElement from '../../Components/MonitorGroup/CurrentStatus';
import ObjectID from 'Common/Types/ObjectID';
import BadDataException from 'Common/Types/Exception/BadDataException';
import BaseModel from 'Common/Models/BaseModel';
const MonitorGroupPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
@ -148,7 +148,7 @@ const MonitorGroupPage: FunctionComponent<PageComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) || [],
Label
) as Array<Label>

View File

@ -11,10 +11,10 @@ import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail
import Navigation from 'CommonUI/src/Utils/Navigation';
import Label from 'Model/Models/Label';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import BaseModel from 'Common/Models/BaseModel';
import ObjectID from 'Common/Types/ObjectID';
import LabelsElement from '../../../Components/Label/Labels';
import MonitorGroup from 'Model/Models/MonitorGroup';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Card from 'CommonUI/src/Components/Card/Card';
import MonitorUptimeGraph from 'CommonUI/src/Components/MonitorGraphs/Uptime';
import useAsyncEffect from 'use-async-effect';
@ -288,7 +288,7 @@ const MonitorGroupView: FunctionComponent<PageComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) ||
[],
Label

View File

@ -15,7 +15,6 @@ import BadDataException from 'Common/Types/Exception/BadDataException';
import Monitor from 'Model/Models/Monitor';
import { JSONObject } from 'Common/Types/JSON';
import MonitorElement from '../../../Components/Monitor/Monitor';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Navigation from 'CommonUI/src/Utils/Navigation';
import MonitorStatus from 'Model/Models/MonitorStatus';
import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
@ -26,6 +25,7 @@ import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble';
import Color from 'Common/Types/Color';
import MonitorGroup from 'Model/Models/MonitorGroup';
import BaseModel from 'Common/Models/BaseModel';
const MonitorGroupResources: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
@ -204,7 +204,7 @@ const MonitorGroupResources: FunctionComponent<PageComponentProps> = (
return (
<MonitorElement
monitor={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item[
'monitor'
] as JSONObject) || [],

View File

@ -11,10 +11,10 @@ import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSc
import Label from 'Model/Models/Label';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import LabelsElement from '../../Components/Label/Labels';
import JSONFunctions from 'Common/Types/JSONFunctions';
import DashboardNavigation from '../../Utils/Navigation';
import Navigation from 'CommonUI/src/Utils/Navigation';
import DashboardSideMenu from './SideMenu';
import BaseModel from 'Common/Models/BaseModel';
const OnCallDutyPage: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@ -141,7 +141,7 @@ const OnCallDutyPage: FunctionComponent<PageComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) || [],
Label
) as Array<Label>

View File

@ -5,6 +5,7 @@ import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import BaseModel from 'Common/Models/BaseModel';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
@ -14,7 +15,6 @@ import { JSONArray, JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import LabelsElement from '../../../Components/Label/Labels';
import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy';
import JSONFunctions from 'Common/Types/JSONFunctions';
const OnCallDutyPolicyView: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@ -143,7 +143,7 @@ const OnCallDutyPolicyView: FunctionComponent<PageComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) ||
[],
Label

View File

@ -27,7 +27,6 @@ import Label from 'Model/Models/Label';
import LabelsElement from '../../../Components/Label/Labels';
import StatusPage from 'Model/Models/StatusPage';
import StatusPagesElement from '../../../Components/StatusPage/StatusPagesLabel';
import JSONFunctions from 'Common/Types/JSONFunctions';
import OneUptimeDate from 'Common/Types/Date';
const ScheduledMaintenanceView: FunctionComponent<PageComponentProps> = (
@ -286,7 +285,7 @@ const ScheduledMaintenanceView: FunctionComponent<PageComponentProps> = (
return (
<MonitorsElement
monitors={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item[
'monitors'
] as JSONArray) || [],
@ -310,7 +309,7 @@ const ScheduledMaintenanceView: FunctionComponent<PageComponentProps> = (
return (
<StatusPagesElement
statusPages={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item[
'statusPages'
] as JSONArray) || [],
@ -355,7 +354,7 @@ const ScheduledMaintenanceView: FunctionComponent<PageComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) ||
[],
Label

View File

@ -17,7 +17,6 @@ import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { JSONObject } from 'Common/Types/JSON';
import UserElement from '../../../Components/User/User';
import User from 'Model/Models/User';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Navigation from 'CommonUI/src/Utils/Navigation';
import AlignItem from 'CommonUI/src/Types/AlignItem';
import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal';
@ -31,6 +30,7 @@ import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal';
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
import ScheduledMaintenanceNoteTemplate from 'Model/Models/ScheduledMaintenanceNoteTemplate';
import BaseModel from 'Common/Models/BaseModel';
const ScheduledMaintenanceDelete: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
@ -72,7 +72,7 @@ const ScheduledMaintenanceDelete: FunctionComponent<PageComponentProps> = (
if (scheduledMaintenanceNoteTemplate) {
const initialValue: JSONObject = {
...JSONFunctions.toJSONObject(
...BaseModel.toJSONObject(
scheduledMaintenanceNoteTemplate,
ScheduledMaintenanceNoteTemplate
),
@ -238,7 +238,7 @@ const ScheduledMaintenanceDelete: FunctionComponent<PageComponentProps> = (
return (
<UserElement
user={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
item['createdByUser'] as JSONObject,
User
) as User

View File

@ -5,6 +5,7 @@ import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import BaseModel from 'Common/Models/BaseModel';
import DashboardNavigation from '../../../Utils/Navigation';
import ObjectID from 'Common/Types/ObjectID';
import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote';
@ -17,7 +18,6 @@ import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { JSONObject } from 'Common/Types/JSON';
import UserElement from '../../../Components/User/User';
import User from 'Model/Models/User';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Navigation from 'CommonUI/src/Utils/Navigation';
import AlignItem from 'CommonUI/src/Types/AlignItem';
import { ModalWidth } from 'CommonUI/src/Components/Modal/Modal';
@ -73,7 +73,7 @@ const PublicNote: FunctionComponent<PageComponentProps> = (
if (scheduledMaintenanceNoteTemplate) {
const initialValue: JSONObject = {
...JSONFunctions.toJSONObject(
...BaseModel.toJSONObject(
scheduledMaintenanceNoteTemplate,
ScheduledMaintenanceNoteTemplate
),
@ -252,7 +252,7 @@ const PublicNote: FunctionComponent<PageComponentProps> = (
return (
<UserElement
user={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
item['createdByUser'] as JSONObject,
User
) as User

View File

@ -20,8 +20,9 @@ import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete';
import ObjectID from 'Common/Types/ObjectID';
import LabelsElement from '../../Components/Label/Labels';
import BadDataException from 'Common/Types/Exception/BadDataException';
import JSONFunctions from 'Common/Types/JSONFunctions';
import DashboardNavigation from '../../Utils/Navigation';
import BaseModel from 'Common/Models/BaseModel';
const APIKeyView: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
): ReactElement => {
@ -263,7 +264,7 @@ const APIKeyView: FunctionComponent<PageComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) || [],
Label
) as Array<Label>

View File

@ -5,6 +5,7 @@ import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import PageComponentProps from '../PageComponentProps';
import DashboardSideMenu from './SideMenu';
import BaseModel from 'Common/Models/BaseModel';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete';
import ObjectID from 'Common/Types/ObjectID';
@ -22,7 +23,6 @@ import BadDataException from 'Common/Types/Exception/BadDataException';
import Pill from 'CommonUI/src/Components/Pill/Pill';
import Color from 'Common/Types/Color';
import MonitorsElement from '../../Components/Monitor/Monitors';
import JSONFunctions from 'Common/Types/JSONFunctions';
import OnCallDutyPoliciesView from '../../Components/OnCallPolicy/OnCallPolicies';
import LabelsElement from '../../Components/Label/Labels';
import DashboardNavigation from '../../Utils/Navigation';
@ -320,7 +320,7 @@ const TeamView: FunctionComponent<PageComponentProps> = (
return (
<MonitorsElement
monitors={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item[
'monitors'
] as JSONArray) || [],
@ -344,7 +344,7 @@ const TeamView: FunctionComponent<PageComponentProps> = (
return (
<OnCallDutyPoliciesView
onCallPolicies={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item[
'onCallDutyPolicies'
] as JSONArray) || [],
@ -375,7 +375,7 @@ const TeamView: FunctionComponent<PageComponentProps> = (
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['labels'] as JSONArray) ||
[],
Label

View File

@ -5,6 +5,7 @@ import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import PageComponentProps from '../PageComponentProps';
import DashboardSideMenu from './SideMenu';
import BaseModel from 'Common/Models/BaseModel';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import DropdownUtil from 'CommonUI/src/Utils/Dropdown';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
@ -19,7 +20,6 @@ import Project from 'Model/Models/Project';
import Team from 'Model/Models/Team';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import TeamsElement from '../../Components/Team/TeamsElement';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Card from 'CommonUI/src/Components/Card/Card';
import Link from 'CommonUI/src/Components/Link/Link';
import URL from 'Common/Types/API/URL';
@ -276,7 +276,7 @@ const SSOPage: FunctionComponent<PageComponentProps> = (
return (
<TeamsElement
teams={
JSONFunctions.fromJSON(
BaseModel.fromJSON(
(item['teams'] as JSONArray) ||
[],
Team

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