@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.
Files changed (45) hide show
  1. package/README.md +37 -4
  2. package/dist/defaults.js +1 -1
  3. package/dist/entities/log.entity.d.ts +2 -2
  4. package/dist/entities/log.entity.js +1 -1
  5. package/dist/guards/access.guard.d.ts +1 -1
  6. package/dist/guards/access.guard.js +2 -2
  7. package/dist/index.d.ts +4 -4
  8. package/dist/interceptors/log.interceptor.d.ts +4 -4
  9. package/dist/log.module.d.ts +1 -1
  10. package/dist/log.module.js +33 -7
  11. package/dist/log.module.js.map +1 -1
  12. package/dist/services/log.service.d.ts +15 -9
  13. package/dist/services/log.service.js +87 -30
  14. package/dist/services/log.service.js.map +1 -1
  15. package/dist/services/memory-db.service.d.ts +1 -1
  16. package/dist/services/memory-db.service.js +5 -5
  17. package/dist/services/ws.service.d.ts +18 -0
  18. package/dist/services/ws.service.js +106 -0
  19. package/dist/services/ws.service.js.map +1 -0
  20. package/dist/types/index.d.ts +3 -3
  21. package/dist/types/options.type.d.ts +7 -0
  22. package/dist/utils/entity2table.d.ts +1 -1
  23. package/dist/utils/entity2table.js +6 -6
  24. package/package.json +9 -4
  25. package/public/index.html +7 -2
  26. package/public/scripts/common.js +74 -43
  27. package/public/scripts/details-popup.js +18 -9
  28. package/public/scripts/json-viewer.js +4 -4
  29. package/public/scripts/ws.js +83 -0
  30. package/public/styles/index.css +5 -0
  31. package/src/defaults.ts +1 -1
  32. package/src/entities/log.entity.ts +3 -3
  33. package/src/guards/access.guard.ts +5 -5
  34. package/src/index.ts +4 -4
  35. package/src/interceptors/log.interceptor.ts +5 -5
  36. package/src/log.module.ts +50 -17
  37. package/src/services/log.service.ts +118 -40
  38. package/src/services/memory-db.service.ts +9 -9
  39. package/src/services/ws.d.ts +1 -0
  40. package/src/services/ws.service.ts +110 -0
  41. package/src/types/index.ts +3 -3
  42. package/src/types/log.type.ts +5 -5
  43. package/src/types/options.type.ts +7 -0
  44. package/src/utils/entity2table.ts +8 -8
  45. 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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"}
@@ -1,3 +1,3 @@
1
- export * from "./context.type";
2
- export * from "./log.type";
3
- export * from "./options.type";
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 "typeorm";
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) ? "increment" : undefined,
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 "text";
24
+ return 'text';
25
25
  case Number:
26
- return "int";
26
+ return 'int';
27
27
  case Date:
28
- return "timestamp";
28
+ return 'timestamp';
29
29
  case Boolean:
30
- return "boolean";
30
+ return 'boolean';
31
31
  default:
32
- return "text";
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.8",
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": "^10.0.0",
30
- "@nestjs/core": "^10.0.0",
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>
@@ -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
- window.addEventListener("load", async () => {
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: "2-digit",
111
- minute: "2-digit",
112
- second: "2-digit",
113
- month: "short",
114
- day: "2-digit",
115
- year: "numeric",
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 || ""}</div>
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
- logs
144
+ logList
147
145
  .filter((log) => {
148
- return selectedLogTypes["all"] || selectedLogTypes[log.type];
146
+ return selectedLogTypes['all'] || selectedLogTypes[log.type];
149
147
  })
150
148
  .filter((log) => {
151
- if (text === "") return true;
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("logs").innerHTML = html;
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
- const res = await fetch(`${origin}${pathname}api${search}`);
195
+ if (res.ok) {
196
+ logs = await res.json();
172
197
 
173
- if (res.ok) {
174
- logs = await res.json();
198
+ checkElementsVisibility();
175
199
 
176
- if (logs.length === 0) {
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
- document.getElementById("no-logs").style.display = "none";
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(id) {
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.set("id", id);
199
-
200
- const res = await fetch(
201
- `${origin}${pathname}api?${searchParamsWithId.toString()}`,
202
- {
203
- method: "DELETE",
204
- }
205
- );
206
-
207
- if (res.ok) {
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
- alert("An error occurred while deleting log.");
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
+ )}.&nbsp;&nbsp;&nbsp;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}</h2>
10
- <div class="mt-05">${getDate(log.updatedAt)}</div>
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 = "block";
53
+ popup.style.display = 'block';
45
54
  }
46
55
 
47
56
  function getObject(context) {
48
- if (typeof context === "string") {
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), "g"), "<br />");
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 = "none";
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 === "object" ? jsonViewer(item) : `<span>${item}</span>`
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] === "object") {
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 += "</div>";
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
+ }
@@ -190,6 +190,11 @@ nav ul li:hover {
190
190
  font-size: 1rem;
191
191
  }
192
192
 
193
+ #refresh,
194
+ #freeze {
195
+ display: none;
196
+ }
197
+
193
198
  /* table */
194
199
  .table-header,
195
200
  .row {
package/src/defaults.ts CHANGED
@@ -1 +1 @@
1
- export const defaultTable = "logs";
1
+ export const defaultTable = 'logs';
@@ -1,14 +1,14 @@
1
- import { DataSourceOptions, EntitySchema } from "typeorm";
1
+ import { DataSourceOptions, EntitySchema } from 'typeorm';
2
2
 
3
3
  export function createLogEntity(
4
4
  name: string,
5
- dbType: DataSourceOptions["type"] | "memory"
5
+ dbType: DataSourceOptions['type'] | 'memory'
6
6
  ) {
7
7
  return new EntitySchema({
8
8
  name,
9
9
  columns: {
10
10
  _id: {
11
- type: dbType === "mongodb" ? String : Number,
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 "@nestjs/common";
7
- import { LogService } from "../services/log.service";
8
- import querystring from "node:querystring";
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("?")[1]);
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("Unauthorized", 401);
20
+ throw new HttpException('Unauthorized', 401);
21
21
  }
22
22
 
23
23
  return true;