Add RealtimeRoute and socket.io-client dependency

This commit is contained in:
Simon Larsen
2023-11-15 16:07:04 +00:00
parent 092d5ec270
commit ee84a082dc
10 changed files with 303 additions and 45 deletions

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');

View File

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

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

@@ -0,0 +1,20 @@
import DatabaseType from "../Types/BaseDatabase/DatabaseType";
import { JSONObject } from "../Types/JSON";
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;
}

View File

@@ -0,0 +1,9 @@
import io from "../Infrastructure/SocketIO";
export default class Realtime {
public static joinRoom()
}
io.on('connection', (socket) => {
});

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

@@ -25,76 +25,114 @@ const LogItem: FunctionComponent<ComponentProps> = (
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);
}}>
<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">
{OneUptimeDate.getDateAsFormattedString(props.log.time)}{' '}
{OneUptimeDate.getDateAsLocalFormattedString(props.log.time)}{' '}
&nbsp;{' '}
</div>
)}
{props.log.severityText === 'Information' && (
<div className="text-sky-400 courier-prime">[INFO] &nbsp;</div>
<div className="text-sky-400 courier-prime">
[INFO] &nbsp;
</div>
)}
{props.log.severityText === 'Warning' && (
<div className="text-amber-400 courier-prime">[WARN] &nbsp;</div>
<div className="text-amber-400 courier-prime">
[WARN] &nbsp;
</div>
)}
{props.log.severityText === 'Error' && (
<div className="text-rose-400 courier-prime">[ERROR] &nbsp;</div>
<div className="text-rose-400 courier-prime">
[ERROR] &nbsp;
</div>
)}
<div className={`${bodyColor} courier-prime`}>{props.log.body?.toString()}</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);
}}>
<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 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 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">[ERROR] &nbsp;</div>
<div className="flex">
<div className="font-medium text-slate-200 courier-prime mr-2">
SEVERITY:
</div>
<div className="text-rose-400 courier-prime">
[ERROR] &nbsp;
</div>
</div>
)}
<div className='flex'>
<div className='font-medium text-slate-200 courier-prime mr-2'>MESSAGE:</div>
<div className={`${bodyColor} courier-prime`}>{props.log.body?.toString()}</div>
<div className="flex">
<div className="font-medium text-slate-200 courier-prime mr-2">
MESSAGE:
</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:</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:</div>
<div className={`${bodyColor} courier-prime`}>{props.log.spanId?.toString()}</div>
</div>}
{props.log.traceId && (
<div className="flex">
<div className="font-medium text-slate-200 courier-prime mr-2">
TRACE:
</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:
</div>
<div className={`${bodyColor} courier-prime`}>
{props.log.spanId?.toString()}
</div>
</div>
)}
</div>
);
};

View File

@@ -9,9 +9,10 @@ export interface ComponentProps {
const LogsViewer: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const [screenHeight, setScreenHeight] = React.useState<number>(window.innerHeight);
const [ autoScroll, setAutoScroll] = React.useState<boolean>(true);
const [screenHeight, setScreenHeight] = React.useState<number>(
window.innerHeight
);
const [autoScroll, setAutoScroll] = React.useState<boolean>(true);
const logsViewerRef = React.useRef<HTMLDivElement>(null);
// Update the screen height when the window is resized
@@ -50,8 +51,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
// Keep scroll to the bottom of the log
React.useEffect(() => {
if(!autoScroll) {
if (!autoScroll) {
return;
}
@@ -63,9 +63,13 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
}, [props.logs]);
return (
<div ref={logsViewerRef} className="shadow-xl rounded-xl bg-slate-800 p-5 overflow-hidden hover:overflow-y-auto" style={{
height: screenHeight - 330,
}}>
<div
ref={logsViewerRef}
className="shadow-xl rounded-xl bg-slate-800 p-5 overflow-hidden hover:overflow-y-auto"
style={{
height: screenHeight - 330,
}}
>
{props.logs.map((log: Log, i: number) => {
return <LogItem key={i} log={log} />;
})}

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,63 @@
import AnalyticsBaseModel from "Common/AnalyticsModels/BaseModel";
import BaseModel from "Common/Models/BaseModel";
import AnalyticsQuery from "./AnalyticsModelAPI/Query";
import Query from "./ModelAPI/Query";
import { EventName, ListenToModelEventJSON, ModelEventType } from "Common/Utils/Realtime";
import ObjectID from "Common/Types/ObjectID";
import SocketIO, { Socket } from "socket.io-client";
import { REALTIME_URL } from "../Config";
import URL from "Common/Types/API/URL";
import JSONFunctions from "Common/Types/JSONFunctions";
import DatabaseType from "Common/Types/BaseDatabase/DatabaseType";
export interface ListenToAnalyticsModelEvent<Model extends AnalyticsBaseModel> {
modelType: { new(): Model },
query: AnalyticsQuery<Model>,
eventType: ModelEventType,
tenantId: ObjectID
}
export interface ListenToModelEvent<Model extends BaseModel> {
modelType: { new(): Model },
query: Query<Model>,
tenantId: ObjectID
eventType: ModelEventType,
}
export default class Reatime {
private socket!: Socket;
public constructor(){
const socket: Socket = SocketIO(URL.fromString(REALTIME_URL.toString()).addRoute("/socket.io").toString());
this.socket = socket;
}
public listenToModelEvent<Model extends BaseModel>(listenToModelEvent: ListenToModelEvent<Model>) {
// conver this to json and send it to the server.
const listenToModelEventJSON: ListenToModelEventJSON = {
eventType: listenToModelEvent.eventType,
modelType: DatabaseType.Database,
modelName: listenToModelEvent.modelType.name,
query: JSONFunctions.serialize(listenToModelEvent.query),
tenantId: listenToModelEvent.tenantId.toString()
}
this.socket.emit(EventName.ListenToModalEvent, listenToModelEventJSON);
}
public listenToAnalyticsModelEvent<Model extends AnalyticsBaseModel>(listenToModelEvent: ListenToAnalyticsModelEvent<Model>) {
const listenToModelEventJSON: ListenToModelEventJSON = {
eventType: listenToModelEvent.eventType,
modelType: DatabaseType.AnalyticsDatabase,
modelName: listenToModelEvent.modelType.name,
query: JSONFunctions.serialize(listenToModelEvent.query),
tenantId: listenToModelEvent.tenantId.toString()
}
this.socket.emit(EventName.ListenToModalEvent, listenToModelEventJSON);
}
}