mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
Fix api
This commit is contained in:
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,6 +2,6 @@ import Route from '../API/Route';
|
||||
|
||||
export default (apiPath: Route) => {
|
||||
return (ctr: Function) => {
|
||||
ctr.prototype.CrudApiPath = apiPath;
|
||||
ctr.prototype.crudApiPath = apiPath;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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"...]
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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'));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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/;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user