@viplance/nestjs-logger 0.3.8 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -4
- package/dist/defaults.js +1 -1
- package/dist/entities/log.entity.d.ts +2 -2
- package/dist/entities/log.entity.js +1 -1
- package/dist/guards/access.guard.d.ts +1 -1
- package/dist/guards/access.guard.js +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/interceptors/log.interceptor.d.ts +4 -4
- package/dist/log.module.d.ts +1 -1
- package/dist/log.module.js +33 -7
- package/dist/log.module.js.map +1 -1
- package/dist/services/log.service.d.ts +15 -9
- package/dist/services/log.service.js +87 -30
- package/dist/services/log.service.js.map +1 -1
- package/dist/services/memory-db.service.d.ts +1 -1
- package/dist/services/memory-db.service.js +5 -5
- package/dist/services/ws.service.d.ts +18 -0
- package/dist/services/ws.service.js +106 -0
- package/dist/services/ws.service.js.map +1 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/options.type.d.ts +7 -0
- package/dist/utils/entity2table.d.ts +1 -1
- package/dist/utils/entity2table.js +6 -6
- package/package.json +9 -4
- package/public/index.html +7 -2
- package/public/scripts/common.js +74 -43
- package/public/scripts/details-popup.js +18 -9
- package/public/scripts/json-viewer.js +4 -4
- package/public/scripts/ws.js +83 -0
- package/public/styles/index.css +5 -0
- package/src/defaults.ts +1 -1
- package/src/entities/log.entity.ts +3 -3
- package/src/guards/access.guard.ts +5 -5
- package/src/index.ts +4 -4
- package/src/interceptors/log.interceptor.ts +5 -5
- package/src/log.module.ts +50 -17
- package/src/services/log.service.ts +118 -40
- package/src/services/memory-db.service.ts +9 -9
- package/src/services/ws.d.ts +1 -0
- package/src/services/ws.service.ts +110 -0
- package/src/types/index.ts +3 -3
- package/src/types/log.type.ts +5 -5
- package/src/types/options.type.ts +7 -0
- package/src/utils/entity2table.ts +8 -8
- package/public/json.html +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.WsService = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const ws_1 = require("ws");
|
|
12
|
+
const rxjs_1 = require("rxjs");
|
|
13
|
+
let WsService = class WsService {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.onMessage = new rxjs_1.Subject();
|
|
16
|
+
this.ws = null;
|
|
17
|
+
this.connected = false;
|
|
18
|
+
this.connectionTimeout = 500;
|
|
19
|
+
this.options = {
|
|
20
|
+
port: 8080,
|
|
21
|
+
host: 'localhost',
|
|
22
|
+
};
|
|
23
|
+
this.key = '';
|
|
24
|
+
this.handleError = () => {
|
|
25
|
+
const serverUrl = this.getServerUrl();
|
|
26
|
+
console.error(`Server ${serverUrl} is not available.`);
|
|
27
|
+
setTimeout(this.setupConnection, this.connectionTimeout);
|
|
28
|
+
};
|
|
29
|
+
this.closeConnection = (connection) => {
|
|
30
|
+
clearTimeout(connection.pingTimeout);
|
|
31
|
+
if (this.connected) {
|
|
32
|
+
console.log('Connection has been closed by server.');
|
|
33
|
+
this.connected = false;
|
|
34
|
+
this.handleError();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
this.ping = (connection) => {
|
|
38
|
+
console.log('Ping remote server.');
|
|
39
|
+
clearTimeout(connection.pingTimeout);
|
|
40
|
+
connection.pingTimeout = setTimeout(() => {
|
|
41
|
+
connection.terminate();
|
|
42
|
+
}, 30000 + this.connectionTimeout);
|
|
43
|
+
};
|
|
44
|
+
this.handleMessage = (message) => {
|
|
45
|
+
try {
|
|
46
|
+
const data = JSON.parse((message.data || message).toString());
|
|
47
|
+
if (this.key !== '' && data.key !== this.key) {
|
|
48
|
+
throw new Error('WebSocket unauthorized');
|
|
49
|
+
}
|
|
50
|
+
if (this.options)
|
|
51
|
+
if (data.action) {
|
|
52
|
+
this.onMessage.next(data);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error(err);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
this.getServerUrl = () => {
|
|
60
|
+
var _a, _b, _c;
|
|
61
|
+
return `${((_a = this.options) === null || _a === void 0 ? void 0 : _a.secure) ? 'wss' : 'ws'}://${(_b = this.options) === null || _b === void 0 ? void 0 : _b.host}:${(_c = this.options) === null || _c === void 0 ? void 0 : _c.port}`;
|
|
62
|
+
};
|
|
63
|
+
this.handleOpenConnection = async () => {
|
|
64
|
+
this.connected = true;
|
|
65
|
+
const serverUrl = this.getServerUrl();
|
|
66
|
+
console.log(`${serverUrl} has been connected.`);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
setupConnection(options, key = '') {
|
|
70
|
+
var _a;
|
|
71
|
+
this.options = {
|
|
72
|
+
...this.options,
|
|
73
|
+
...options,
|
|
74
|
+
};
|
|
75
|
+
this.key = key;
|
|
76
|
+
// Set up Web Socket server
|
|
77
|
+
if (this.ws) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const wsServer = new ws_1.WebSocketServer({
|
|
81
|
+
retryCount: 1,
|
|
82
|
+
reconnectInterval: 1,
|
|
83
|
+
handshakeTimeout: this.connectionTimeout,
|
|
84
|
+
port: (_a = this.options) === null || _a === void 0 ? void 0 : _a.port,
|
|
85
|
+
});
|
|
86
|
+
console.log(`Logs WebSocket server is listening on port ${this.options.port}`);
|
|
87
|
+
wsServer.on('error', this.handleError);
|
|
88
|
+
wsServer.on('open', () => this.handleOpenConnection());
|
|
89
|
+
wsServer.on('ping', () => this.ping(this.ws));
|
|
90
|
+
wsServer.on('close', () => this.closeConnection(this.ws));
|
|
91
|
+
wsServer.on('message', this.handleMessage);
|
|
92
|
+
wsServer.on('connection', (connection) => {
|
|
93
|
+
this.ws = connection;
|
|
94
|
+
connection.onmessage = this.handleMessage;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
sendMessage(message) {
|
|
98
|
+
var _a;
|
|
99
|
+
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify(message));
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
exports.WsService = WsService;
|
|
103
|
+
exports.WsService = WsService = __decorate([
|
|
104
|
+
(0, common_1.Injectable)()
|
|
105
|
+
], WsService);
|
|
106
|
+
//# sourceMappingURL=ws.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.service.js","sourceRoot":"","sources":["../../src/services/ws.service.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
1
|
+
export * from './context.type';
|
|
2
|
+
export * from './log.type';
|
|
3
|
+
export * from './options.type';
|
|
@@ -2,6 +2,7 @@ import { DataSourceOptions } from "typeorm";
|
|
|
2
2
|
export type LogModuleOptions = {
|
|
3
3
|
path?: string;
|
|
4
4
|
key?: string;
|
|
5
|
+
join?: boolean;
|
|
5
6
|
maxRecords?: number;
|
|
6
7
|
maxAge?: number;
|
|
7
8
|
maxSize?: number;
|
|
@@ -11,4 +12,10 @@ export type LogModuleOptions = {
|
|
|
11
12
|
table?: string;
|
|
12
13
|
collection?: string;
|
|
13
14
|
};
|
|
15
|
+
websocket?: {
|
|
16
|
+
port?: number;
|
|
17
|
+
namespace?: string;
|
|
18
|
+
host?: string;
|
|
19
|
+
secure?: boolean;
|
|
20
|
+
};
|
|
14
21
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { EntitySchema, Table } from
|
|
1
|
+
import { EntitySchema, Table } from 'typeorm';
|
|
2
2
|
export declare function entity2table(entity: EntitySchema): Table;
|
|
@@ -11,7 +11,7 @@ function entity2table(entity) {
|
|
|
11
11
|
type: resolveColumnType(col === null || col === void 0 ? void 0 : col.type),
|
|
12
12
|
isPrimary: !!(col === null || col === void 0 ? void 0 : col.primary),
|
|
13
13
|
isGenerated: !!(col === null || col === void 0 ? void 0 : col.generated),
|
|
14
|
-
generationStrategy: (col === null || col === void 0 ? void 0 : col.generated) ?
|
|
14
|
+
generationStrategy: (col === null || col === void 0 ? void 0 : col.generated) ? 'increment' : undefined,
|
|
15
15
|
isUnique: !!(col === null || col === void 0 ? void 0 : col.unique),
|
|
16
16
|
isNullable: !!(col === null || col === void 0 ? void 0 : col.nullable),
|
|
17
17
|
default: col === null || col === void 0 ? void 0 : col.default,
|
|
@@ -21,15 +21,15 @@ function entity2table(entity) {
|
|
|
21
21
|
function resolveColumnType(type) {
|
|
22
22
|
switch (type) {
|
|
23
23
|
case String:
|
|
24
|
-
return
|
|
24
|
+
return 'text';
|
|
25
25
|
case Number:
|
|
26
|
-
return
|
|
26
|
+
return 'int';
|
|
27
27
|
case Date:
|
|
28
|
-
return
|
|
28
|
+
return 'timestamp';
|
|
29
29
|
case Boolean:
|
|
30
|
-
return
|
|
30
|
+
return 'boolean';
|
|
31
31
|
default:
|
|
32
|
-
return
|
|
32
|
+
return 'text';
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
//# sourceMappingURL=entity2table.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viplance/nestjs-logger",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "NestJS internal logging system",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -21,17 +21,22 @@
|
|
|
21
21
|
"mariadb",
|
|
22
22
|
"sqlite",
|
|
23
23
|
"memory",
|
|
24
|
+
"websocket",
|
|
25
|
+
"realtime",
|
|
26
|
+
"UI",
|
|
24
27
|
"free"
|
|
25
28
|
],
|
|
26
29
|
"author": "Dzmitry Sharko",
|
|
27
30
|
"license": "ISC",
|
|
28
31
|
"peerDependencies": {
|
|
29
|
-
"@nestjs/common": "^
|
|
30
|
-
"@nestjs/core": "^
|
|
32
|
+
"@nestjs/common": "^11.0.0",
|
|
33
|
+
"@nestjs/core": "^11.0.0",
|
|
31
34
|
"@nestjs/typeorm": "^11.0.0",
|
|
35
|
+
"@nestjs/websockets": "^11.1.7",
|
|
32
36
|
"reflect-metadata": "^0.2.2",
|
|
33
37
|
"rxjs": "^7.8.2",
|
|
34
|
-
"typeorm": "^0.3.27"
|
|
38
|
+
"typeorm": "^0.3.27",
|
|
39
|
+
"ws": "^8.18.3"
|
|
35
40
|
},
|
|
36
41
|
"devDependencies": {
|
|
37
42
|
"@types/node": "^24.5.2",
|
package/public/index.html
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
<link rel="stylesheet" href="styles/reset.css" />
|
|
9
9
|
<link rel="stylesheet" href="styles/colors.css" />
|
|
10
10
|
<link rel="stylesheet" href="styles/index.css" />
|
|
11
|
+
<script
|
|
12
|
+
defer="defer"
|
|
13
|
+
src="scripts/ws.js
|
|
14
|
+
"
|
|
15
|
+
></script>
|
|
11
16
|
<script
|
|
12
17
|
defer="defer"
|
|
13
18
|
src="scripts/json-viewer.js
|
|
@@ -42,12 +47,12 @@
|
|
|
42
47
|
</ul>
|
|
43
48
|
</nav>
|
|
44
49
|
<input id="search" onkeyup="search(event)" type="text" placeholder="Search" />
|
|
45
|
-
<div onclick="getLogs()"><button class="white">Refresh</button></div>
|
|
50
|
+
<div id="refresh" onclick="getLogs()"><button class="white">Refresh</button></div>
|
|
51
|
+
<div id="freeze" onclick="toggleFreeze()"><button class="white">Freeze</button></div>
|
|
46
52
|
</div>
|
|
47
53
|
<div class="container table-header">
|
|
48
54
|
<div class="col">Type</div>
|
|
49
55
|
<div class="col">Info</div>
|
|
50
|
-
<div class="col">Trace</div>
|
|
51
56
|
<div class="col">Count</div>
|
|
52
57
|
</div>
|
|
53
58
|
</header>
|
package/public/scripts/common.js
CHANGED
|
@@ -10,11 +10,9 @@ const selectedLogTypes = {
|
|
|
10
10
|
const logTypes = Object.keys(selectedLogTypes).filter((key) => key !== `all`);
|
|
11
11
|
|
|
12
12
|
let logs = [];
|
|
13
|
-
let text =
|
|
13
|
+
let text = '';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
getLogs();
|
|
17
|
-
});
|
|
15
|
+
connectWebSocket();
|
|
18
16
|
|
|
19
17
|
document.addEventListener(`click`, (e) => {
|
|
20
18
|
const target = e.target;
|
|
@@ -107,12 +105,12 @@ function getDate(incomingDate) {
|
|
|
107
105
|
const date = new Date(incomingDate);
|
|
108
106
|
|
|
109
107
|
const formatter = new Intl.DateTimeFormat(undefined, {
|
|
110
|
-
hour:
|
|
111
|
-
minute:
|
|
112
|
-
second:
|
|
113
|
-
month:
|
|
114
|
-
day:
|
|
115
|
-
year:
|
|
108
|
+
hour: '2-digit',
|
|
109
|
+
minute: '2-digit',
|
|
110
|
+
second: '2-digit',
|
|
111
|
+
month: 'short',
|
|
112
|
+
day: '2-digit',
|
|
113
|
+
year: 'numeric',
|
|
116
114
|
hour12: false,
|
|
117
115
|
});
|
|
118
116
|
|
|
@@ -135,20 +133,20 @@ function getLogHtmlElement(log) {
|
|
|
135
133
|
<div class="log-info">${log.message}</div>
|
|
136
134
|
<div class="date">${getDate(log.updatedAt)}</div>
|
|
137
135
|
</div>
|
|
138
|
-
<div class="col context">${log.trace ||
|
|
136
|
+
<div class="col context">${log.trace || ''}</div>
|
|
139
137
|
<div class="col">${log.count}</div>
|
|
140
138
|
</div>`;
|
|
141
139
|
}
|
|
142
140
|
|
|
143
|
-
function renderLogs() {
|
|
144
|
-
let html =
|
|
141
|
+
function renderLogs(logList = logs) {
|
|
142
|
+
let html = '';
|
|
145
143
|
|
|
146
|
-
|
|
144
|
+
logList
|
|
147
145
|
.filter((log) => {
|
|
148
|
-
return selectedLogTypes[
|
|
146
|
+
return selectedLogTypes['all'] || selectedLogTypes[log.type];
|
|
149
147
|
})
|
|
150
148
|
.filter((log) => {
|
|
151
|
-
if (text ===
|
|
149
|
+
if (text === '') return true;
|
|
152
150
|
|
|
153
151
|
return (
|
|
154
152
|
log.message.toLowerCase().includes(text) ||
|
|
@@ -162,53 +160,86 @@ function renderLogs() {
|
|
|
162
160
|
html += getLogHtmlElement(log);
|
|
163
161
|
});
|
|
164
162
|
|
|
165
|
-
document.getElementById(
|
|
163
|
+
document.getElementById('logs').innerHTML = html;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function checkElementsVisibility(logList = logs) {
|
|
167
|
+
if (logList.length === 0) {
|
|
168
|
+
document.getElementById('no-logs').style.display = 'block';
|
|
169
|
+
document.getElementById('search').style.display = 'none';
|
|
170
|
+
document.querySelector('.table-header').style.display = 'none';
|
|
171
|
+
document.querySelector('nav').style.display = 'none';
|
|
172
|
+
} else {
|
|
173
|
+
document.getElementById('no-logs').style.display = 'none';
|
|
174
|
+
document.getElementById('search').style.display = 'inline-block';
|
|
175
|
+
document.querySelector('.table-header').style.display = 'flex';
|
|
176
|
+
document.querySelector('nav').style.display = 'flex';
|
|
177
|
+
}
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
async function getLogs() {
|
|
169
181
|
const { origin, pathname, search } = window.location;
|
|
182
|
+
const searchParams = new URLSearchParams(search);
|
|
183
|
+
const key = searchParams.get('key');
|
|
184
|
+
|
|
185
|
+
if (!!socket) {
|
|
186
|
+
socket.send(
|
|
187
|
+
JSON.stringify({
|
|
188
|
+
action: 'getLogs',
|
|
189
|
+
key,
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
} else {
|
|
193
|
+
const res = await fetch(`${origin}${pathname}api${search}`);
|
|
170
194
|
|
|
171
|
-
|
|
195
|
+
if (res.ok) {
|
|
196
|
+
logs = await res.json();
|
|
172
197
|
|
|
173
|
-
|
|
174
|
-
logs = await res.json();
|
|
198
|
+
checkElementsVisibility();
|
|
175
199
|
|
|
176
|
-
|
|
177
|
-
document.getElementById("no-logs").style.display = "block";
|
|
178
|
-
document.querySelector(".table-header").style.display = "none";
|
|
179
|
-
document.querySelector("nav").style.display = "none";
|
|
200
|
+
renderLogs();
|
|
180
201
|
} else {
|
|
181
|
-
|
|
182
|
-
document.querySelector(".table-header").style.display = "flex";
|
|
183
|
-
document.querySelector("nav").style.display = "flex";
|
|
202
|
+
alert('An error occurred while fetching logs.');
|
|
184
203
|
}
|
|
185
|
-
|
|
186
|
-
renderLogs();
|
|
187
|
-
} else {
|
|
188
|
-
alert("An error occurred while fetching logs.");
|
|
189
204
|
}
|
|
190
205
|
}
|
|
191
206
|
|
|
192
|
-
async function deleteLog(
|
|
207
|
+
async function deleteLog(_id) {
|
|
193
208
|
if (!confirm("Are you sure? It can't be undone.")) return;
|
|
194
209
|
|
|
195
210
|
const { origin, pathname, search: searchParams } = window.location;
|
|
196
211
|
|
|
197
212
|
const searchParamsWithId = new URLSearchParams(searchParams);
|
|
198
|
-
searchParamsWithId.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
const key = searchParamsWithId.get('key');
|
|
214
|
+
|
|
215
|
+
if (!!socket) {
|
|
216
|
+
socket.send(
|
|
217
|
+
JSON.stringify({
|
|
218
|
+
action: 'delete',
|
|
219
|
+
key,
|
|
220
|
+
data: {
|
|
221
|
+
_id,
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
);
|
|
208
225
|
closePopup();
|
|
209
226
|
getLogs();
|
|
210
227
|
} else {
|
|
211
|
-
|
|
228
|
+
searchParamsWithId.set('id', _id);
|
|
229
|
+
|
|
230
|
+
const res = await fetch(
|
|
231
|
+
`${origin}${pathname}api?${searchParamsWithId.toString()}`,
|
|
232
|
+
{
|
|
233
|
+
method: 'DELETE',
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (res.ok) {
|
|
238
|
+
closePopup();
|
|
239
|
+
getLogs();
|
|
240
|
+
} else {
|
|
241
|
+
alert('An error occurred while deleting log.');
|
|
242
|
+
}
|
|
212
243
|
}
|
|
213
244
|
}
|
|
214
245
|
|
|
@@ -3,18 +3,27 @@ function showLogDetails(log) {
|
|
|
3
3
|
const context = getObject(log.context);
|
|
4
4
|
const breadcrumbs = getObject(log.breadcrumbs);
|
|
5
5
|
|
|
6
|
+
const timeInfo =
|
|
7
|
+
log.updatedAt === log.createdAt
|
|
8
|
+
? getDate(log.updatedAt)
|
|
9
|
+
: `Updated: ${getDate(
|
|
10
|
+
log.updatedAt
|
|
11
|
+
)}. First seen: ${getDate(log.createdAt)}`;
|
|
12
|
+
|
|
6
13
|
popup.innerHTML = `
|
|
7
14
|
<div class="content center">
|
|
8
15
|
<div class="container">
|
|
9
|
-
<h2 class="popup-title ${log.type}">${log.type}: ${log.message}
|
|
10
|
-
|
|
16
|
+
<h2 class="popup-title ${log.type}">${log.type}: ${log.message} (${
|
|
17
|
+
log.count
|
|
18
|
+
})</h2>
|
|
19
|
+
<div class="mt-05">${timeInfo}</div>
|
|
11
20
|
${
|
|
12
21
|
log.trace
|
|
13
22
|
? `
|
|
14
23
|
<h3 class="mt-15">Trace</h3>
|
|
15
24
|
<p class="key pl-2"><span>${getTrace(log.trace)}</span></p>
|
|
16
25
|
`
|
|
17
|
-
:
|
|
26
|
+
: ''
|
|
18
27
|
}
|
|
19
28
|
${
|
|
20
29
|
context
|
|
@@ -22,7 +31,7 @@ function showLogDetails(log) {
|
|
|
22
31
|
<h3 class="mt-15">Context</h3>
|
|
23
32
|
<p>${jsonViewer(context)}</p>
|
|
24
33
|
`
|
|
25
|
-
:
|
|
34
|
+
: ''
|
|
26
35
|
}
|
|
27
36
|
${
|
|
28
37
|
breadcrumbs && breadcrumbs.length > 0
|
|
@@ -30,7 +39,7 @@ function showLogDetails(log) {
|
|
|
30
39
|
<h3 class="mt-15">Breadcrumbs</h3>
|
|
31
40
|
<p>${jsonViewer(breadcrumbs)}</p>
|
|
32
41
|
`
|
|
33
|
-
:
|
|
42
|
+
: ''
|
|
34
43
|
}
|
|
35
44
|
<div class="content">
|
|
36
45
|
<button class="white mt-2" onclick="closePopup()">Close</button>
|
|
@@ -41,11 +50,11 @@ function showLogDetails(log) {
|
|
|
41
50
|
</div>
|
|
42
51
|
<div>`;
|
|
43
52
|
|
|
44
|
-
popup.style.display =
|
|
53
|
+
popup.style.display = 'block';
|
|
45
54
|
}
|
|
46
55
|
|
|
47
56
|
function getObject(context) {
|
|
48
|
-
if (typeof context ===
|
|
57
|
+
if (typeof context === 'string') {
|
|
49
58
|
return JSON.parse(context);
|
|
50
59
|
}
|
|
51
60
|
|
|
@@ -53,10 +62,10 @@ function getObject(context) {
|
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
function getTrace(trace) {
|
|
56
|
-
return trace.replace(new RegExp(String.fromCharCode(10),
|
|
65
|
+
return trace.replace(new RegExp(String.fromCharCode(10), 'g'), '<br />');
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
function closePopup() {
|
|
60
69
|
const popup = document.getElementById(`popup`);
|
|
61
|
-
popup.style.display =
|
|
70
|
+
popup.style.display = 'none';
|
|
62
71
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
function jsonViewer(json, parentKey) {
|
|
2
2
|
if (!json) {
|
|
3
|
-
return
|
|
3
|
+
return '';
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
res = `<div>`;
|
|
@@ -8,7 +8,7 @@ function jsonViewer(json, parentKey) {
|
|
|
8
8
|
if (Array.isArray(json)) {
|
|
9
9
|
json.forEach((item) => {
|
|
10
10
|
res += `<div class="key pl-2">${
|
|
11
|
-
typeof item ===
|
|
11
|
+
typeof item === 'object' ? jsonViewer(item) : `<span>${item}</span>`
|
|
12
12
|
}</div>`;
|
|
13
13
|
});
|
|
14
14
|
|
|
@@ -23,7 +23,7 @@ function jsonViewer(json, parentKey) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
for (const key of keys) {
|
|
26
|
-
if (typeof json[key] ===
|
|
26
|
+
if (typeof json[key] === 'object') {
|
|
27
27
|
res += jsonViewer(json[key], key);
|
|
28
28
|
} else {
|
|
29
29
|
res += `<div class="key pl-2">${key}: <span>${json[key]}</span></div>`;
|
|
@@ -34,7 +34,7 @@ function jsonViewer(json, parentKey) {
|
|
|
34
34
|
res += `</div>`;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
res +=
|
|
37
|
+
res += '</div>';
|
|
38
38
|
|
|
39
39
|
return res;
|
|
40
40
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// WebSocket connection
|
|
2
|
+
let socket;
|
|
3
|
+
let frozen = false;
|
|
4
|
+
|
|
5
|
+
async function connectWebSocket() {
|
|
6
|
+
const { hostname, origin, pathname, search } = window.location;
|
|
7
|
+
|
|
8
|
+
const res = await fetch(`${origin}${pathname}settings${search}`);
|
|
9
|
+
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
alert('An error occurred while fetching settings.');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const settings = await res.json();
|
|
16
|
+
|
|
17
|
+
if (!settings.websocket) {
|
|
18
|
+
getLogs();
|
|
19
|
+
document.getElementById('refresh').style.display = 'block';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
document.getElementById('freeze').style.display = 'block';
|
|
23
|
+
|
|
24
|
+
if (!settings.websocket?.port) {
|
|
25
|
+
alert('WebSocket port is not configured.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const socketUrl = `ws://${hostname}:${settings.websocket.port}`;
|
|
30
|
+
socket = new WebSocket(socketUrl, 'json');
|
|
31
|
+
|
|
32
|
+
socket.onerror = (error) => {
|
|
33
|
+
console.error(error);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
socket.onopen = (event) => {
|
|
37
|
+
getLogs();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
socket.onclose = (event) => {
|
|
41
|
+
console.log(event);
|
|
42
|
+
setTimeout(connectWebSocket, 5000);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
socket.onmessage = (event) => {
|
|
46
|
+
const data = JSON.parse(event.data.toString());
|
|
47
|
+
|
|
48
|
+
if (data['action'] && !frozen) {
|
|
49
|
+
switch (data['action']) {
|
|
50
|
+
case 'list':
|
|
51
|
+
logs = data['data'];
|
|
52
|
+
checkElementsVisibility(logs);
|
|
53
|
+
renderLogs(logs);
|
|
54
|
+
break;
|
|
55
|
+
case 'insert':
|
|
56
|
+
getLogs();
|
|
57
|
+
break;
|
|
58
|
+
case 'update':
|
|
59
|
+
getLogs();
|
|
60
|
+
return;
|
|
61
|
+
case 'delete':
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function sendMessage(message) {
|
|
68
|
+
socket.send(JSON.stringify(message));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toggleFreeze() {
|
|
72
|
+
frozen = !frozen;
|
|
73
|
+
const button = document.querySelector('#freeze button');
|
|
74
|
+
button.innerHTML = frozen ? 'Frozen' : 'Freeze';
|
|
75
|
+
|
|
76
|
+
if (frozen) {
|
|
77
|
+
button.classList.remove('white');
|
|
78
|
+
button.classList.add('light');
|
|
79
|
+
} else {
|
|
80
|
+
button.classList.remove('light');
|
|
81
|
+
button.classList.add('white');
|
|
82
|
+
}
|
|
83
|
+
}
|
package/public/styles/index.css
CHANGED
package/src/defaults.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const defaultTable =
|
|
1
|
+
export const defaultTable = 'logs';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { DataSourceOptions, EntitySchema } from
|
|
1
|
+
import { DataSourceOptions, EntitySchema } from 'typeorm';
|
|
2
2
|
|
|
3
3
|
export function createLogEntity(
|
|
4
4
|
name: string,
|
|
5
|
-
dbType: DataSourceOptions[
|
|
5
|
+
dbType: DataSourceOptions['type'] | 'memory'
|
|
6
6
|
) {
|
|
7
7
|
return new EntitySchema({
|
|
8
8
|
name,
|
|
9
9
|
columns: {
|
|
10
10
|
_id: {
|
|
11
|
-
type: dbType ===
|
|
11
|
+
type: dbType === 'mongodb' ? String : Number,
|
|
12
12
|
objectId: true,
|
|
13
13
|
primary: true,
|
|
14
14
|
generated: true,
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
ExecutionContext,
|
|
4
4
|
HttpException,
|
|
5
5
|
Injectable,
|
|
6
|
-
} from
|
|
7
|
-
import { LogService } from
|
|
8
|
-
import querystring from
|
|
6
|
+
} from '@nestjs/common';
|
|
7
|
+
import { LogService } from '../services/log.service';
|
|
8
|
+
import querystring from 'node:querystring';
|
|
9
9
|
|
|
10
10
|
@Injectable()
|
|
11
11
|
export class LogAccessGuard implements CanActivate {
|
|
@@ -14,10 +14,10 @@ export class LogAccessGuard implements CanActivate {
|
|
|
14
14
|
? context.switchToHttp().getRequest()
|
|
15
15
|
: context; // hook for using as method
|
|
16
16
|
|
|
17
|
-
const params = querystring.parse(req.url.split(
|
|
17
|
+
const params = querystring.parse(req.url.split('?')[1]);
|
|
18
18
|
|
|
19
19
|
if (LogService.options.key && params.key !== LogService.options.key) {
|
|
20
|
-
throw new HttpException(
|
|
20
|
+
throw new HttpException('Unauthorized', 401);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
return true;
|