This commit is contained in:
Simon Larsen
2022-07-08 14:04:17 +01:00
parent a15a805d99
commit 9306020235
16 changed files with 148 additions and 77 deletions

4
.vscode/launch.json vendored
View File

@@ -12,8 +12,8 @@
},
{
"address": "0.0.0.0",
"localRoot": "${workspaceFolder}/backend",
"name": "Backend: Debug with Docker",
"localRoot": "${workspaceFolder}/DashboardAPI",
"name": "Dashboard API: Debug with Docker",
"port": 9232,
"remoteRoot": "/usr/src/app",
"request": "attach",

View File

@@ -9,12 +9,28 @@ import TableColumn from '../Types/Database/TableColumn';
import CrudApiEndpoint from '../Types/Database/CrudApiEndpoint';
import Route from '../Types/API/Route';
import TableColumnType from '../Types/Database/TableColumnType';
import UserRecordPermissions from '../Types/Database/AccessControls/User/UserRecordPermissions';
import UserColumnPermissions from '../Types/Database/AccessControls/User/UserColumnPermissions';
@CrudApiEndpoint(new Route('/project'))
@UserRecordPermissions({
create: true,
readAsList: false,
readAsItem: false,
update: false,
delete: false,
})
@Entity({
name: 'Project',
})
export default class Model extends BaseModel {
@UserColumnPermissions({
create: true,
readAsList: false,
readAsItem: false,
update: false,
delete: false,
})
@TableColumn({ required: true, type: TableColumnType.ShortText })
@Column({
nullable: false,

View File

@@ -2,6 +2,6 @@ import Route from '../API/Route';
export default (apiPath: Route) => {
return (ctr: Function) => {
ctr.prototype.CrudApiPath = apiPath;
ctr.prototype.crudApiPath = apiPath;
};
};

View File

@@ -1,9 +1,10 @@
enum Role {
Owner = 'Owner',
Administrator = 'Administrator',
Member = 'Member',
Viewer = 'Viewer',
Public = 'Public',
Owner = 'Owner', // owner of a project. An owner owns all the billing info.
Administrator = 'Administrator', // admin of a project
Member = 'Member', // member of a project
Viewer = 'Viewer', // user who is a viewer in a project
User = 'User', // registered-user but does not belong to a project
Public = 'Public', // non-registered user.
}
export const RoleArray: Array<string> = [...new Set(Object.keys(Role))]; // Returns ["Owner", "Administrator"...]

View File

@@ -28,35 +28,35 @@ export default class BaseAPI<
// Create
router.post(
`${this.entityType.name}/`,
`/${new this.entityType().getCrudApiPath()?.toString()}`,
UserMiddleware.getUserMiddleware,
this.createItem
);
// List
router.get(
`${this.entityType.name}/list`,
`/${new this.entityType().getCrudApiPath()?.toString()}/list`,
UserMiddleware.getUserMiddleware,
this.getList
);
// Get Item
router.get(
`${this.entityType.name}/:id`,
`/${new this.entityType().getCrudApiPath()?.toString()}/:id`,
UserMiddleware.getUserMiddleware,
this.getItem
);
// Update
router.put(
`${this.entityType.name}/:id`,
`/${new this.entityType().getCrudApiPath()?.toString()}/:id`,
UserMiddleware.getUserMiddleware,
this.updateItem
);
// Delete
router.delete(
`${this.entityType.name}/:id`,
`/${new this.entityType().getCrudApiPath()?.toString()}/:id`,
UserMiddleware.getUserMiddleware,
this.deleteItem
);

View File

@@ -38,17 +38,13 @@ const logRequest: RequestHandler = (
const method: string = req.method;
const url: string = req.url;
const header_info: string = `Request ID: ${
(req as OneUptimeRequest).id
} -- POD NAME: ${
process.env['POD_NAME'] || 'NONE'
} -- METHOD: ${method} -- URL: ${url.toString()}`;
const header_info: string = `Request ID: ${(req as OneUptimeRequest).id
} -- POD NAME: ${process.env['POD_NAME'] || 'NONE'
} -- METHOD: ${method} -- URL: ${url.toString()}`;
const body_info: string = `Request ID: ${
(req as OneUptimeRequest).id
} -- Request Body: ${
req.body ? JSON.stringify(req.body, null, 2) : 'EMPTY'
}`;
const body_info: string = `Request ID: ${(req as OneUptimeRequest).id
} -- Request Body: ${req.body ? JSON.stringify(req.body, null, 2) : 'EMPTY'
}`;
logger.info(header_info + '\n ' + body_info);
next();
@@ -115,6 +111,22 @@ const init: Function = async (appName: string): Promise<ExpressApplication> => {
}
);
app.post('*', function (_req, res) {
res.status(404).json({ "error": "API not found" });
});
app.put('*', function (_req, res) {
res.status(404).json({ "error": "API not found" });
});
app.delete('*', function (_req, res) {
res.status(404).json({ "error": "API not found" });
});
app.get('*', function (_req, res) {
res.status(404).json({ "error": "API not found" });
});
return app;
};

View File

@@ -1,7 +1,7 @@
REALTIME_ROUTE=/realtime
MAIL_ROUTE=/mail
DASHBOARD_ROUTE=dashboard
DASHBOARD_API_ROUTE=/dashboard-api
DASHBOARD_ROUTE=/dashboard
DASHBOARD_API_ROUTE=/api
PROBE_API_ROUTE=/probe-api
DATA_INGESTOR_ROUTE=/data-ingestor
ACCOUNTS_ROUTE=/accounts

View File

@@ -1,5 +1,5 @@
import React, { ReactElement } from 'react';
import { ErrorMessage, Field, Form, Formik, FormikErrors } from 'formik';
import React, { MutableRefObject, ReactElement, useRef } from 'react';
import { ErrorMessage, Field, Form, Formik, FormikErrors, FormikProps, FormikValues } from 'formik';
import Button, { ButtonStyleType } from '../Button/Button';
import FormValues from './Types/FormValues';
import Fields from './Types/Fields';
@@ -35,6 +35,7 @@ export interface ComponentProps<T extends Object> {
maxPrimaryButtonWidth?: boolean;
error: string | null;
hideSubmitButton?: boolean;
formRef?: MutableRefObject<FormikProps<FormikValues>>
}
function getFieldType(fieldType: FormFieldSchemaType): string {
@@ -53,6 +54,7 @@ function getFieldType(fieldType: FormFieldSchemaType): string {
const BasicForm: Function = <T extends Object>(
props: ComponentProps<T>
): ReactElement => {
const getFormField: Function = (
field: DataField<T>,
index: number,
@@ -246,12 +248,15 @@ const BasicForm: Function = <T extends Object>(
return { ...errors, ...customValidateResult } as FormikErrors<
FormValues<T>
>;
};
};
const formRef: any = useRef<any>(null);
return (
<div className="row">
<div className="col-lg-12">
<Formik
innerRef={props.formRef ? props.formRef : formRef}
initialValues={props.initialValues}
validate={validate}
validateOnChange={true}

View File

@@ -1,5 +1,5 @@
import React, { ReactElement } from 'react';
import { FormikErrors } from 'formik';
import React, { MutableRefObject, ReactElement } from 'react';
import { FormikErrors, FormikProps, FormikValues } from 'formik';
import BaseModel from 'Common/Models/BaseModel';
import FormValues from './Types/FormValues';
import Fields from './Types/Fields';
@@ -24,6 +24,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
maxPrimaryButtonWidth?: boolean;
error: string | null;
hideSubmitButton?: boolean;
formRef?: MutableRefObject<FormikProps<FormikValues>>
}
const BasicModelForm: Function = <TBaseModel extends BaseModel>(
@@ -81,6 +82,7 @@ const BasicModelForm: Function = <TBaseModel extends BaseModel>(
maxPrimaryButtonWidth={props.maxPrimaryButtonWidth || false}
error={props.error}
hideSubmitButton={props.hideSubmitButton}
formRef={props.formRef}
></BasicForm>
);
};

View File

@@ -1,5 +1,5 @@
import React, { ReactElement, useState } from 'react';
import { FormikErrors } from 'formik';
import React, { MutableRefObject, ReactElement, useState } from 'react';
import { FormikErrors, FormikProps, FormikValues } from 'formik';
import BaseModel from 'Common/Models/BaseModel';
import FormValues from './Types/FormValues';
import Fields from './Types/Fields';
@@ -39,6 +39,8 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
apiUrl?: URL;
formType: FormType;
hideSubmitButton?: boolean;
formRef?: MutableRefObject<FormikProps<FormikValues>>;
onLoadingChange?: (isLoading: boolean) => void;
}
const ModelForm: Function = <TBaseModel extends BaseModel>(
@@ -51,6 +53,9 @@ const ModelForm: Function = <TBaseModel extends BaseModel>(
// Ping an API here.
setError('');
setLoading(true);
if (props.onLoadingChange) {
props.onLoadingChange(true);
}
let apiUrl: URL | null = props.apiUrl || null;
if (!apiUrl) {
@@ -61,7 +66,7 @@ const ModelForm: Function = <TBaseModel extends BaseModel>(
);
}
apiUrl = DASHBOARD_API_URL.addRoute(apiPath);
apiUrl = URL.fromURL(DASHBOARD_API_URL).addRoute(apiPath);
}
const result: HTTPResponse<
@@ -77,6 +82,9 @@ const ModelForm: Function = <TBaseModel extends BaseModel>(
);
setLoading(false);
if (props.onLoadingChange) {
props.onLoadingChange(false);
}
if (result.isSuccess()) {
if (props.onSuccess) {
@@ -105,6 +113,7 @@ const ModelForm: Function = <TBaseModel extends BaseModel>(
maxPrimaryButtonWidth={props.maxPrimaryButtonWidth}
error={error}
hideSubmitButton={props.hideSubmitButton}
formRef={props.formRef}
></BasicModelForm>
);
};

View File

@@ -13,6 +13,7 @@ export interface ComponentProps {
onSubmit: () => void;
submitButtonStyleType?: ButtonStyleType;
submitButtonType?: ButtonType;
isLoading?: boolean;
}
const Modal: FunctionComponent<ComponentProps> = (
@@ -51,6 +52,7 @@ const Modal: FunctionComponent<ComponentProps> = (
onClose={
props.onClose ? props.onClose : undefined
}
isLoading={props.isLoading || false}
/>
</div>
</div>

View File

@@ -8,6 +8,7 @@ export interface ComponentProps {
onSubmit: () => void;
submitButtonStyleType?: ButtonStyleType;
submitButtonType?: ButtonType;
isLoading?: boolean
}
const ModalFooter: FunctionComponent<ComponentProps> = (
@@ -23,6 +24,7 @@ const ModalFooter: FunctionComponent<ComponentProps> = (
onClick={() => {
props.onClose && props.onClose();
}}
isLoading={props.isLoading || false}
/>
) : (
<></>
@@ -43,6 +45,7 @@ const ModalFooter: FunctionComponent<ComponentProps> = (
onClick={() => {
props.onSubmit();
}}
isLoading={props.isLoading || false}
type={props.submitButtonType ? props.submitButtonType : ButtonType.Button}
/>
) : (

View File

@@ -1,15 +1,19 @@
import React, { ReactElement } from 'react';
import React, { ReactElement, useRef, useState } from 'react';
import { ButtonStyleType } from '../Button/Button';
import Modal from '../Modal/Modal';
import ModelForm, { ComponentProps as ModelFormComponentProps} from '../Forms/ModelForm';
import BaseModel from 'Common/Models/BaseModel';
import ButtonType from '../Button/ButtonTypes';
import { JSONObjectOrArray } from 'Common/Types/JSON';
import { FormikProps, FormikValues } from 'formik';
export interface ComponentProps<TBaseModel extends BaseModel> {
title: string;
onClose?: () => void;
submitButtonText?: string;
onSubmit: () => void;
onSuccess?: (
data: TBaseModel | JSONObjectOrArray | Array<TBaseModel>
) => void;
submitButtonStyleType?: ButtonStyleType;
formProps: ModelFormComponentProps<TBaseModel>;
}
@@ -17,9 +21,21 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
const ModelFromModal: Function = <TBaseModel extends BaseModel> (
props: ComponentProps<TBaseModel>
): ReactElement => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const formRef = useRef<FormikProps<FormikValues>>(null);
return (
<Modal {...props} submitButtonType={ButtonType.Submit}>
<ModelForm<TBaseModel> {...props.formProps} hideSubmitButton={true}/>
<Modal {...props} submitButtonType={ButtonType.Submit} isLoading={isLoading} onSubmit={() => {
formRef.current && formRef.current.handleSubmit();
}}>
<ModelForm<TBaseModel> {...props.formProps} hideSubmitButton={true} onLoadingChange={(isFormLoading: boolean) => { setIsLoading(isFormLoading) }} formRef={formRef} onSuccess={
(
data: TBaseModel | JSONObjectOrArray | Array<TBaseModel>
) => {
props.onSuccess && props.onSuccess(data);
}
} />
</Modal>
);
};

View File

@@ -18,7 +18,7 @@ export const IS_SAAS_SERVICE: boolean = env('IS_SAAS_SERVICE') === 'true';
export const DISABLE_SIGNUP: boolean = env('DISABLE_SIGNUP') === 'true';
export const VERSION: Version = new Version(env('VERSION') || '1.0.0');
export const DASHBOARD_API_ROUTE: Route = new Route(env('DASHBOARD_API_Route'));
export const DASHBOARD_API_ROUTE: Route = new Route(env('DASHBOARD_API_ROUTE'));
export const IDENTITY_ROUTE: Route = new Route(env('IDENTITY_ROUTE'));

View File

@@ -1,42 +1,43 @@
import 'ejs';
import { PostgresAppInstance } from 'CommonServer/Infrastructure/PostgresDatabase';
import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
// import Express, { ExpressApplication } from 'CommonServer/Utils/Express';
import logger from 'CommonServer/Utils/Logger';
import BaseAPI from "CommonServer/API/BaseAPI";
// import BaseAPI from "CommonServer/API/BaseAPI";
import App from 'CommonServer/Utils/StartServer';
import User from 'Common/Models/User';
import UserService, {
Service as UserServiceType,
} from 'CommonServer/Services/UserService';
// import User from 'Common/Models/User';
// import UserService, {
// Service as UserServiceType,
// } from 'CommonServer/Services/UserService';
import Project from 'Common/Models/Project';
import ProjectService, {
Service as ProjectServiceType,
} from 'CommonServer/Services/ProjectService';
// import Project from 'Common/Models/Project';
// import ProjectService, {
// Service as ProjectServiceType,
// } from 'CommonServer/Services/ProjectService';
import Probe from 'Common/Models/Probe';
import ProbeService, {
Service as ProbeServiceType,
} from 'CommonServer/Services/ProbeService';
// import Probe from 'Common/Models/Probe';
// import ProbeService, {
// Service as ProbeServiceType,
// } from 'CommonServer/Services/ProbeService';
import EmailVerificationToken from 'Common/Models/EmailVerificationToken';
import EmailVerificationTokenService, {
Service as EmailVerificationTokenServiceType,
} from 'CommonServer/Services/EmailVerificationTokenService';
// import EmailVerificationToken from 'Common/Models/EmailVerificationToken';
// import EmailVerificationTokenService, {
// Service as EmailVerificationTokenServiceType,
// } from 'CommonServer/Services/EmailVerificationTokenService';
const app: ExpressApplication = Express.getExpressApp();
// const app: ExpressApplication = Express.getExpressApp();
const APP_NAME: string = 'dashboard-api';
const APP_NAME: string = 'api';
//attach api's
app.use(new User().getCrudApiPath()?.toString()!, new BaseAPI<User, UserServiceType>(User, UserService).getRouter());
app.use(new Project().getCrudApiPath()?.toString()!, new BaseAPI<Project, ProjectServiceType>(Project, ProjectService).getRouter());
app.use(new Probe().getCrudApiPath()?.toString()!, new BaseAPI<Probe, ProbeServiceType>(Probe, ProbeService).getRouter());
app.use(new Probe().getCrudApiPath()?.toString()!, new BaseAPI<Probe, ProbeServiceType>(Probe, ProbeService).getRouter());
app.use(new EmailVerificationToken().getCrudApiPath()?.toString()!, new BaseAPI<EmailVerificationToken, EmailVerificationTokenServiceType>(EmailVerificationToken, EmailVerificationTokenService).getRouter());
// //attach api's
// app.use('/api', new BaseAPI<User, UserServiceType>(User, UserService).getRouter());
// app.use('/api',new BaseAPI<Project, ProjectServiceType>(Project, ProjectService).getRouter());
// app.use('/api',new BaseAPI<Probe, ProbeServiceType>(Probe, ProbeService).getRouter());
// app.use('/api',new BaseAPI<Probe, ProbeServiceType>(Probe, ProbeService).getRouter());
// app.use('/api',new BaseAPI<EmailVerificationToken, EmailVerificationTokenServiceType>(EmailVerificationToken, EmailVerificationTokenService).getRouter());
const init: Function = async (): Promise<void> => {
try {
// init the app
await App(APP_NAME);

View File

@@ -6,6 +6,10 @@ upstream identity {
server identity:3087 weight=10 max_fails=3 fail_timeout=30s;
}
upstream dashboard-api {
server dashboard-api:3002 weight=10 max_fails=3 fail_timeout=30s;
}
upstream dashboard {
server dashboard:3009 weight=10 max_fails=3 fail_timeout=30s;
}
@@ -65,19 +69,6 @@ server {
proxy_pass http://home/;
}
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# enable WebSockets (for ws://sockjs not connected error in the accounts source: https://stackoverflow.com/questions/41381444/websocket-connection-failed-error-during-websocket-handshake-unexpected-respon)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://dashboard-api/;
}
location /accounts {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
@@ -116,4 +107,17 @@ server {
proxy_set_header Connection "upgrade";
proxy_pass http://identity/;
}
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# enable WebSockets (for ws://sockjs not connected error in the accounts source: https://stackoverflow.com/questions/41381444/websocket-connection-failed-error-during-websocket-handshake-unexpected-respon)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://dashboard-api/;
}
}