5htp-core 0.0.7 → 0.0.8-2
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/package.json +4 -3
- package/src/client/assets/css/components/button.less +1 -1
- package/src/client/context/index.ts +3 -2
- package/src/client/index.tsx +2 -1
- package/src/client/router/component.tsx +6 -6
- package/src/server/app/index.ts +70 -17
- package/src/server/data/Cache.ts +124 -74
- package/src/server/patch.ts +0 -11
- package/src/server/services/console/bugReporter.ts +31 -27
- package/src/server/services/console/html.ts +0 -0
- package/src/server/services/console/index.ts +1 -1
- package/src/server/services/socket/index.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "5-HTP, scientifically called 5-Hydroxytryptophan, is the precursor of happiness neurotransmitter.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.8-2",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
"nodemailer": "^6.6.3",
|
|
59
59
|
"path-to-regexp": "^6.2.0",
|
|
60
60
|
"picomatch": "^2.3.1",
|
|
61
|
-
"preact-render-to-string": "^5.1.19",
|
|
62
61
|
"react-scrollbars-custom": "^4.0.27",
|
|
63
62
|
"react-slider": "^2.0.1",
|
|
64
63
|
"react-textarea-autosize": "^8.3.3",
|
|
@@ -73,7 +72,9 @@
|
|
|
73
72
|
"uuid-by-string": "^3.0.4",
|
|
74
73
|
"validator": "^13.7.0",
|
|
75
74
|
"ws": "^8.2.2",
|
|
76
|
-
"yaml": "^1.10.2"
|
|
75
|
+
"yaml": "^1.10.2",
|
|
76
|
+
"preact": "^10.5.15",
|
|
77
|
+
"preact-render-to-string": "^5.1.19"
|
|
77
78
|
},
|
|
78
79
|
"devDependencies": {
|
|
79
80
|
"@types/cookie": "^0.4.1",
|
|
@@ -202,7 +202,7 @@ export class ClientContext {
|
|
|
202
202
|
public handleError(e: Error) {
|
|
203
203
|
switch (e.http) {
|
|
204
204
|
case 401:
|
|
205
|
-
this.page?.
|
|
205
|
+
this.page?.go('/');
|
|
206
206
|
break;
|
|
207
207
|
default:
|
|
208
208
|
this.toast.error(e.title || "Uh Oh ...", e.message, null, { autohide: false });
|
|
@@ -225,12 +225,13 @@ export class ClientContext {
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// Services
|
|
228
229
|
public modal = createDialog(this, false);
|
|
229
230
|
public toast = createDialog(this, true);
|
|
230
|
-
|
|
231
231
|
public socket = new SocketClient(this)
|
|
232
232
|
public captcha = new Recaptcha(this);
|
|
233
233
|
|
|
234
|
+
// Tracking
|
|
234
235
|
public event( name: string, params?: object ) {
|
|
235
236
|
if (!window.gtag) return;
|
|
236
237
|
if (name === 'pageview')
|
package/src/client/index.tsx
CHANGED
|
@@ -69,10 +69,10 @@ export default () => {
|
|
|
69
69
|
|
|
70
70
|
const [pages, setPages] = React.useState<{
|
|
71
71
|
current: undefined | PageResponse,
|
|
72
|
-
previous: undefined | PageResponse
|
|
72
|
+
//previous: undefined | PageResponse
|
|
73
73
|
}>({
|
|
74
74
|
current: gui.page,
|
|
75
|
-
previous: undefined
|
|
75
|
+
//previous: undefined
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
const resolvePage = async (request: ClientRequest, locationUpdate?: Update) => {
|
|
@@ -116,13 +116,13 @@ export default () => {
|
|
|
116
116
|
if (oldPage !== undefined) {
|
|
117
117
|
setTimeout(() => setPages({
|
|
118
118
|
current: newpage,
|
|
119
|
-
previous: undefined
|
|
119
|
+
//previous: undefined
|
|
120
120
|
}), 500);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
return {
|
|
124
124
|
current: newpage,
|
|
125
|
-
previous: oldPage
|
|
125
|
+
//previous: oldPage
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
128
|
}
|
|
@@ -175,9 +175,9 @@ export default () => {
|
|
|
175
175
|
|
|
176
176
|
// Render the page component
|
|
177
177
|
return <>
|
|
178
|
-
{pages.previous && (
|
|
178
|
+
{/*pages.previous && (
|
|
179
179
|
<Page page={pages.previous} key={pages.previous.id === undefined ? undefined : 'page_' + pages.previous.id} />
|
|
180
|
-
)}
|
|
180
|
+
)*/}
|
|
181
181
|
|
|
182
182
|
{pages.current && (
|
|
183
183
|
<Page page={pages.current} isCurrent key={pages.current.id === undefined ? undefined : 'page_' + pages.current.id} />
|
package/src/server/app/index.ts
CHANGED
|
@@ -15,7 +15,7 @@ import ConfigParser, { TEnvConfig } from './config';
|
|
|
15
15
|
----------------------------------*/
|
|
16
16
|
|
|
17
17
|
type THookName = 'ready' | 'cleanup' | 'error'
|
|
18
|
-
type THook = () => void
|
|
18
|
+
type THook = () => Promise<void>;
|
|
19
19
|
|
|
20
20
|
type TServiceOptions = {
|
|
21
21
|
instanciate: boolean
|
|
@@ -91,6 +91,20 @@ export class App {
|
|
|
91
91
|
----------------------------------*/
|
|
92
92
|
|
|
93
93
|
public constructor() {
|
|
94
|
+
|
|
95
|
+
// Gestion crash
|
|
96
|
+
process.on('unhandledRejection', (error: any, promise: any) => {
|
|
97
|
+
|
|
98
|
+
console.error("Unhandled promise rejection:", error);
|
|
99
|
+
|
|
100
|
+
// Send email report
|
|
101
|
+
if (this.isLoaded('console'))
|
|
102
|
+
$.console.bugReport.server(error);
|
|
103
|
+
else
|
|
104
|
+
console.error(`Unable to send bug report: console service not loaded.`);
|
|
105
|
+
|
|
106
|
+
});
|
|
107
|
+
|
|
94
108
|
// Load config files
|
|
95
109
|
const configParser = new ConfigParser( this.path.root );
|
|
96
110
|
this.env = configParser.env();
|
|
@@ -106,21 +120,47 @@ export class App {
|
|
|
106
120
|
console.log("Configure services with", this.config);
|
|
107
121
|
}
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
// Register a service
|
|
124
|
+
public register<TServiceName extends keyof Core.Services>(
|
|
125
|
+
id: TServiceName,
|
|
126
|
+
Service: TServiceClass,
|
|
127
|
+
options: Partial<TServiceOptions> = {}
|
|
128
|
+
) {
|
|
110
129
|
|
|
111
130
|
// Pas d'export default new Service pour chaque fichier de service,
|
|
112
131
|
// dissuaded'importer ms service sn'importe où, ce qui créé des références circulaires
|
|
113
|
-
console.log(`
|
|
132
|
+
console.log(`[services] Registering service ${id} ...`);
|
|
114
133
|
const service = options.instanciate !== false ? new Service() : Service;
|
|
115
|
-
this.services[id] = service;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
service
|
|
134
|
+
this.services[id as string] = service;
|
|
135
|
+
|
|
136
|
+
if ('load' in service) {
|
|
137
|
+
|
|
138
|
+
console.log(`[services] Starting service ${id} ...`);
|
|
139
|
+
|
|
140
|
+
// Lorsque service.load est async, une propriété loading doit etre présente
|
|
141
|
+
// De façon à ce que les autres services puissent savoir quand ce service est prêt
|
|
142
|
+
if ('loading' in service) {
|
|
143
|
+
|
|
144
|
+
console.log(`[services] Waiting service ${id} to be fully loaded ...`);
|
|
145
|
+
service.loading = service.load().then(() => {
|
|
146
|
+
console.info(`[service] Service ${id} successfully started.`);
|
|
147
|
+
}).catch(e => {
|
|
148
|
+
// Bug report via email
|
|
149
|
+
console.error(`[service] Error while starting the ${id} service:`, e);
|
|
150
|
+
e.message = `Start ${id} service: ` + e.message;
|
|
151
|
+
$.console.bugReport.server(e);
|
|
152
|
+
});;
|
|
153
|
+
|
|
154
|
+
this.loading.push(service.loading);
|
|
155
|
+
|
|
156
|
+
} else
|
|
157
|
+
service.load();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Test if a service was registered
|
|
162
|
+
public isLoaded( id: keyof Core.Services ) {
|
|
163
|
+
return id in this.services;
|
|
124
164
|
}
|
|
125
165
|
|
|
126
166
|
public on( name: THookName, callback: THook ) {
|
|
@@ -128,23 +168,36 @@ export class App {
|
|
|
128
168
|
return this;
|
|
129
169
|
}
|
|
130
170
|
|
|
171
|
+
public runHook( hookName: THookName ) {
|
|
172
|
+
console.info(`[hook] Run all ${hookName} hook (${this.hooks.ready.length}).`);
|
|
173
|
+
return Promise.all(
|
|
174
|
+
this.hooks.ready.map(
|
|
175
|
+
cb => cb().catch(e => {
|
|
176
|
+
console.error(`[hook] Error while executing hook ${hookName}:`, e);
|
|
177
|
+
})
|
|
178
|
+
)
|
|
179
|
+
).then(() => {
|
|
180
|
+
console.info(`[hook] Hooks ${hookName} executed with success.`);
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
131
184
|
/*----------------------------------
|
|
132
185
|
- LAUNCH
|
|
133
186
|
----------------------------------*/
|
|
134
187
|
public async launch() {
|
|
135
188
|
|
|
136
|
-
console.info(`Waiting for services to be ready ...`);
|
|
189
|
+
console.info(`[boot] Waiting for all services to be ready ...`);
|
|
137
190
|
await Promise.all( this.loading );
|
|
138
191
|
|
|
139
|
-
console.info(`Launching application ...`);
|
|
140
|
-
await
|
|
192
|
+
console.info(`[boot] Launching application ...`);
|
|
193
|
+
await this.runHook('ready');
|
|
141
194
|
|
|
142
195
|
// NOTE: Useless ?
|
|
143
196
|
/*if (this.hmr)
|
|
144
197
|
this.activateHMR();*/
|
|
145
198
|
|
|
146
|
-
console.info(`Application is ready.`);
|
|
147
|
-
|
|
199
|
+
console.info(`[boot] Application is ready.`);
|
|
200
|
+
|
|
148
201
|
this.launched = true;
|
|
149
202
|
|
|
150
203
|
}
|
package/src/server/data/Cache.ts
CHANGED
|
@@ -2,68 +2,123 @@
|
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
|
+
// Node
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
5
8
|
// Npm
|
|
6
9
|
import hInterval from 'human-interval';
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
|
|
12
|
+
// Core
|
|
13
|
+
import app from '@server/app';
|
|
7
14
|
|
|
8
15
|
// Libs
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
|
|
17
|
+
/*----------------------------------
|
|
18
|
+
- TYPES
|
|
19
|
+
----------------------------------*/
|
|
20
|
+
|
|
21
|
+
type TPrimitiveValue = string | boolean | number | undefined | {[key: string]: TPrimitiveValue} | TPrimitiveValue[];
|
|
22
|
+
|
|
23
|
+
type TExpirationDelay = string | number | Date | null;
|
|
24
|
+
|
|
25
|
+
type CacheEntry = {
|
|
26
|
+
// Value
|
|
27
|
+
value: TPrimitiveValue,
|
|
28
|
+
// Expiration Timestamp
|
|
29
|
+
expiration?: number
|
|
30
|
+
};
|
|
11
31
|
|
|
12
32
|
/*----------------------------------
|
|
13
33
|
- SERVICE
|
|
14
34
|
----------------------------------*/
|
|
15
|
-
|
|
35
|
+
class Cache {
|
|
36
|
+
|
|
37
|
+
private cacheFile = path.join(app.path.data, 'cache/mem.json');
|
|
38
|
+
|
|
39
|
+
private data: {[key: string]: CacheEntry | undefined} = {};
|
|
40
|
+
|
|
41
|
+
private changes: number = 0;
|
|
42
|
+
|
|
43
|
+
public constructor() {
|
|
44
|
+
|
|
45
|
+
setInterval(() => this.cleanMem(), 10000);
|
|
46
|
+
|
|
47
|
+
if (fs.existsSync(this.cacheFile))
|
|
48
|
+
this.data = fs.readJSONSync(this.cacheFile)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private cleanMem() {
|
|
52
|
+
|
|
53
|
+
console.log("[cache] Clean memory");
|
|
54
|
+
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
for (const key in this.data) {
|
|
57
|
+
const entry = this.data[ key ];
|
|
58
|
+
if (entry?.expiration && entry.expiration < now)
|
|
59
|
+
this.del(key);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (this.changes > 0)
|
|
63
|
+
fs.outputJSONSync(this.cacheFile, this.data);
|
|
64
|
+
}
|
|
16
65
|
|
|
17
66
|
// Expiration = Durée de vie en secondes ou date max
|
|
18
67
|
// Retourne null quand pas de valeur
|
|
19
|
-
|
|
68
|
+
public get<TValeur extends TPrimitiveValue>(
|
|
69
|
+
cle: string,
|
|
70
|
+
func?: (() => Promise<TValeur>),
|
|
71
|
+
expiration?: TExpirationDelay,
|
|
72
|
+
avecDetails?: true
|
|
73
|
+
): Promise<CacheEntry>;
|
|
74
|
+
|
|
75
|
+
public get<TValeur extends TPrimitiveValue>(
|
|
76
|
+
cle: string,
|
|
77
|
+
func: (() => Promise<TValeur>),
|
|
78
|
+
expiration?: TExpirationDelay,
|
|
79
|
+
avecDetails?: false
|
|
80
|
+
): Promise<null | TValeur>;
|
|
81
|
+
|
|
82
|
+
public async get<TValeur extends TPrimitiveValue>(
|
|
20
83
|
cle: string,
|
|
21
|
-
func
|
|
22
|
-
expiration
|
|
84
|
+
func?: (() => Promise<TValeur>),
|
|
85
|
+
expiration?: TExpirationDelay,
|
|
23
86
|
avecDetails?: boolean
|
|
24
|
-
): Promise<null | TValeur> {
|
|
87
|
+
): Promise<null | TValeur | CacheEntry> {
|
|
88
|
+
|
|
89
|
+
let retour: CacheEntry | undefined = this.data[cle];
|
|
90
|
+
|
|
91
|
+
console.log(`[cache] Get "${cle}".`);
|
|
25
92
|
|
|
26
|
-
|
|
93
|
+
// Expired
|
|
94
|
+
if (retour?.expiration && retour.expiration < Date.now()){
|
|
95
|
+
console.log(`[cache] Key ${cle} expired.`);
|
|
96
|
+
retour = undefined;
|
|
97
|
+
}
|
|
27
98
|
|
|
28
99
|
// Donnée inexistante
|
|
29
|
-
if (retour ===
|
|
100
|
+
if (retour === undefined && func !== undefined) {
|
|
30
101
|
|
|
31
102
|
// Rechargement
|
|
32
|
-
retour =
|
|
103
|
+
retour = {
|
|
104
|
+
value: await func(),
|
|
105
|
+
expiration: expiration
|
|
106
|
+
? this.delayToTimestamp(expiration)
|
|
107
|
+
: undefined
|
|
108
|
+
}
|
|
33
109
|
|
|
34
110
|
// undefined retourné = pas d'enregistrement
|
|
35
|
-
if (retour !== undefined)
|
|
111
|
+
if (retour.value !== undefined)
|
|
36
112
|
await this.set(cle, retour, expiration);
|
|
37
113
|
}
|
|
38
114
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
donnees: retour
|
|
42
|
-
}
|
|
43
|
-
: retour;
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
getVal<TValeur>(cle: string): Promise<TValeur | null> {
|
|
47
|
-
return new Promise((resolve) => {
|
|
48
|
-
|
|
49
|
-
redis.instance.get(cle, (err, val) => {
|
|
115
|
+
if (retour === undefined)
|
|
116
|
+
return null;
|
|
50
117
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
resolve( JSON.parse(val) )
|
|
56
|
-
} catch (error) {
|
|
57
|
-
|
|
58
|
-
console.warn(`Error while parsing JSON value from cache (id: ${cle})`, error, 'Raw value:', val);
|
|
59
|
-
resolve(null);
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
},
|
|
118
|
+
return avecDetails
|
|
119
|
+
? retour
|
|
120
|
+
: retour.value as TValeur;
|
|
121
|
+
};
|
|
67
122
|
|
|
68
123
|
/**
|
|
69
124
|
* Put in cache a JSON value, associated with an unique ID.
|
|
@@ -76,49 +131,44 @@ const Cache = {
|
|
|
76
131
|
* - null: no expiration (default)
|
|
77
132
|
* @returns A void promise
|
|
78
133
|
*/
|
|
79
|
-
set( cle: string, val:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
134
|
+
public set( cle: string, val: TPrimitiveValue, expiration: TExpirationDelay = null ): void {
|
|
135
|
+
|
|
136
|
+
console.log("[cache] Updating cache " + cle);
|
|
137
|
+
this.data[ cle ] = {
|
|
138
|
+
value: val,
|
|
139
|
+
expiration: this.delayToTimestamp(expiration)
|
|
140
|
+
}
|
|
86
141
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
redis.instance.set(cle, val, () => {
|
|
90
|
-
resolve()
|
|
91
|
-
});
|
|
92
|
-
else {
|
|
142
|
+
this.changes++;
|
|
143
|
+
};
|
|
93
144
|
|
|
94
|
-
|
|
145
|
+
public del( cle: string ): void {
|
|
146
|
+
this.data[ cle ] = undefined;
|
|
147
|
+
this.changes++;
|
|
148
|
+
}
|
|
95
149
|
|
|
96
|
-
// H expression
|
|
97
|
-
if (typeof expiration === 'string') {
|
|
98
150
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
151
|
+
/*----------------------------------
|
|
152
|
+
- UTILS
|
|
153
|
+
----------------------------------*/
|
|
154
|
+
private delayToTimestamp( delay: TExpirationDelay ): number {
|
|
102
155
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
secondes = expiration;
|
|
106
|
-
// Date limite
|
|
107
|
-
else
|
|
108
|
-
secondes = (expiration.getTime() - (new Date).getTime()) / 1000;
|
|
156
|
+
// H expression
|
|
157
|
+
if (typeof delay === 'string') {
|
|
109
158
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
},
|
|
159
|
+
const ms = hInterval(delay);
|
|
160
|
+
if (ms === undefined) throw new Error(`Invalid period string: ` + delay);
|
|
161
|
+
return Date.now() + ms;
|
|
116
162
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
163
|
+
// Via durée de vie en secondes
|
|
164
|
+
} else if (typeof delay === 'number')
|
|
165
|
+
return Date.now() + delay;
|
|
166
|
+
// Date limite
|
|
167
|
+
else if (delay !== null)
|
|
168
|
+
return delay.getTime();
|
|
169
|
+
else
|
|
170
|
+
return Date.now();
|
|
121
171
|
}
|
|
122
172
|
}
|
|
123
173
|
|
|
124
|
-
export default Cache;
|
|
174
|
+
export default new Cache;
|
package/src/server/patch.ts
CHANGED
|
@@ -4,17 +4,6 @@ moduleAlias.addAliases({
|
|
|
4
4
|
'react-dom': "preact/compat",
|
|
5
5
|
})
|
|
6
6
|
|
|
7
|
-
/*----------------------------------
|
|
8
|
-
- DEBUG
|
|
9
|
-
----------------------------------*/
|
|
10
|
-
|
|
11
|
-
// Gestion crash
|
|
12
|
-
process.on('unhandledRejection', (error: any, promise: any) => {
|
|
13
|
-
|
|
14
|
-
console.error("Unhandled promise rejection:", error);
|
|
15
|
-
|
|
16
|
-
});
|
|
17
|
-
|
|
18
7
|
/*----------------------------------
|
|
19
8
|
- DATES & TIMZEONE
|
|
20
9
|
----------------------------------*/
|
|
@@ -70,7 +70,7 @@ export default class BugReporter {
|
|
|
70
70
|
public async server( error: Error, request?: ServerRequest ) {
|
|
71
71
|
|
|
72
72
|
// error should be printed in the console, so they're acccessible from logs
|
|
73
|
-
console.error(error);
|
|
73
|
+
console.error(`Sending bug report for the following error:`, error);
|
|
74
74
|
|
|
75
75
|
// Prevent duplicates
|
|
76
76
|
if (!this.shouldSendReport('server', request?.user?.name, undefined, error.message))
|
|
@@ -79,7 +79,6 @@ export default class BugReporter {
|
|
|
79
79
|
// Get context
|
|
80
80
|
const now = new Date();
|
|
81
81
|
const hash = uuid();
|
|
82
|
-
const erroTitle = "Server Bug: " + error.message;
|
|
83
82
|
const { channelType, channelId } = this.console.getChannel();
|
|
84
83
|
|
|
85
84
|
// On envoi l'email avant l'insertion dans bla bdd
|
|
@@ -90,31 +89,36 @@ export default class BugReporter {
|
|
|
90
89
|
);
|
|
91
90
|
|
|
92
91
|
// Send notification
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
92
|
+
if (app.isLoaded('email'))
|
|
93
|
+
$.email.send({
|
|
94
|
+
to: app.identity.author.email,
|
|
95
|
+
subject: "Server bug: " + error.message,
|
|
96
|
+
html: `
|
|
97
|
+
<a href="${app.env.url}/admin/activity/requests/${channelId}">
|
|
98
|
+
View Request details & console
|
|
99
|
+
</a>
|
|
100
|
+
<br/>
|
|
101
|
+
${logsHtml}
|
|
102
|
+
`
|
|
103
|
+
});
|
|
104
|
+
else
|
|
105
|
+
console.error("Unable to send bug report: email service not loaded.");
|
|
106
|
+
|
|
107
|
+
/*if (app.isLoaded('sql'))
|
|
108
|
+
// Memorize
|
|
109
|
+
$.sql.insert('BugServer', {
|
|
110
|
+
// Context
|
|
111
|
+
hash: hash,
|
|
112
|
+
date: now,
|
|
113
|
+
channelType,
|
|
114
|
+
channelId,
|
|
115
|
+
// User
|
|
116
|
+
user: request?.user?.name,
|
|
117
|
+
ip: request?.ip,
|
|
118
|
+
// Error
|
|
119
|
+
stacktrace: error.stack || error.message,
|
|
120
|
+
logs: logsHtml
|
|
121
|
+
});*/
|
|
118
122
|
|
|
119
123
|
// Update error message
|
|
120
124
|
error.message = "A bug report has been sent to my personal mailbox. Sorry for the inconvenience.";
|
|
File without changes
|
|
@@ -256,7 +256,7 @@ export class Console {
|
|
|
256
256
|
let html = logs.map( log => logToHTML( log, this )).join('\n');
|
|
257
257
|
|
|
258
258
|
if (full) {
|
|
259
|
-
const consoleCss = `background: #000; padding: 20px; font-family: 'monospace'; font-size: 12px; line-height: 20px;`
|
|
259
|
+
const consoleCss = `background: #000; padding: 20px; font-family: 'Fira Mono', 'monospace', 'Monaco'; font-size: 12px; line-height: 20px;`
|
|
260
260
|
html = '<div style="' + consoleCss + '">' + html + '</div>';
|
|
261
261
|
}
|
|
262
262
|
|