5htp-core 0.3.2 → 0.3.3
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 +2 -2
- package/src/client/assets/css/components/button.less +2 -1
- package/src/client/components/Dialog/Manager.tsx +1 -1
- package/src/client/services/router/components/Page.tsx +14 -25
- package/src/client/services/router/components/router.tsx +24 -12
- package/src/client/services/router/request/api.ts +3 -3
- package/src/client/services/router/response/page.ts +0 -4
- package/src/common/router/index.ts +4 -4
- package/src/common/router/request/api.ts +6 -6
- package/src/common/router/response/page.ts +1 -1
- package/src/server/app/service/container.ts +2 -1
- package/src/server/app/service/index.ts +23 -8
- package/src/server/app.tsconfig.json +1 -1
- package/src/server/services/console/html.ts +21 -20
- package/src/server/services/console/index.ts +113 -138
- package/src/server/services/cron/CronTask.ts +8 -8
- package/src/server/services/cron/index.ts +1 -3
- package/src/server/services/database/metas.ts +3 -5
- package/src/server/services/router/index.ts +3 -0
- package/src/server/services/router/response/index.ts +25 -10
- package/src/types/global/modules.d.ts +1 -1
- package/src/types/global/extensions.d.ts +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.3",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -61,7 +61,6 @@
|
|
|
61
61
|
"mysql2": "^2.3.0",
|
|
62
62
|
"nodemailer": "^6.6.3",
|
|
63
63
|
"object-sizeof": "^1.6.3",
|
|
64
|
-
"ololog": "^1.1.175",
|
|
65
64
|
"path-to-regexp": "^6.2.0",
|
|
66
65
|
"picomatch": "^2.3.1",
|
|
67
66
|
"preact": "^10.5.15",
|
|
@@ -75,6 +74,7 @@
|
|
|
75
74
|
"request": "^2.88.2",
|
|
76
75
|
"sharp": "^0.29.1",
|
|
77
76
|
"sql-formatter": "^4.0.2",
|
|
77
|
+
"tslog": "^4.9.1",
|
|
78
78
|
"uuid": "^8.3.2",
|
|
79
79
|
"uuid-by-string": "^3.0.4",
|
|
80
80
|
"validator": "^13.7.0",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
gap: @spacing / 2;
|
|
66
66
|
min-width: 4em;
|
|
67
67
|
font-size: 1rem;
|
|
68
|
+
z-index: 1; // Make the label on top of ::before for example
|
|
68
69
|
|
|
69
70
|
li > & {
|
|
70
71
|
flex: 1;
|
|
@@ -111,7 +112,7 @@
|
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
> i {
|
|
114
|
-
color: inherit
|
|
115
|
+
color: inherit;
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
}
|
|
@@ -14,45 +14,34 @@ import type Page from '../response/page';
|
|
|
14
14
|
- PAGE STATE
|
|
15
15
|
----------------------------------*/
|
|
16
16
|
|
|
17
|
-
export default ({ page
|
|
17
|
+
export default ({ page }: { page: Page }) => {
|
|
18
18
|
|
|
19
|
+
/*----------------------------------
|
|
20
|
+
- CONTEXT
|
|
21
|
+
----------------------------------*/
|
|
19
22
|
const context = useContext();
|
|
20
23
|
|
|
21
24
|
const [apiData, setApiData] = React.useState<{[k: string]: any} | null>(
|
|
22
|
-
page.
|
|
25
|
+
page.data || {}
|
|
23
26
|
);
|
|
24
27
|
page.setAllData = setApiData;
|
|
25
28
|
context.data = apiData;
|
|
26
|
-
|
|
27
|
-
React.useEffect(() => {
|
|
28
|
-
|
|
29
|
-
// Fetch the data asynchronously for the first time
|
|
30
|
-
if (/*apiData === null && */isCurrent)
|
|
31
|
-
page.fetchData().then( loadedData => {
|
|
32
|
-
page.loading = false;
|
|
33
|
-
setApiData(loadedData);
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
}, [page]);
|
|
37
|
-
|
|
38
|
-
/*
|
|
39
|
-
<div
|
|
40
|
-
class={"page" + (isCurrent ? ' current' : '')}
|
|
41
|
-
id={page.chunkId === undefined ? undefined : 'page_' + page.chunkId}
|
|
42
|
-
>
|
|
43
|
-
*/
|
|
44
29
|
|
|
30
|
+
/*----------------------------------
|
|
31
|
+
- RENDER
|
|
32
|
+
----------------------------------*/
|
|
45
33
|
// Make request parameters and api data accessible from the page component
|
|
46
34
|
return page.renderer ? (
|
|
47
35
|
|
|
48
36
|
<page.renderer
|
|
49
37
|
// Services
|
|
50
38
|
{...context}
|
|
51
|
-
// URL params
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
// API data & URL params
|
|
40
|
+
data={{
|
|
41
|
+
...apiData,
|
|
42
|
+
...context.request.data
|
|
43
|
+
}}
|
|
55
44
|
/>
|
|
56
45
|
|
|
57
|
-
) :
|
|
46
|
+
) : 'Renderer missing'
|
|
58
47
|
}
|
|
@@ -38,16 +38,22 @@ export default ({ service: router }: { service: Router }) => {
|
|
|
38
38
|
|
|
39
39
|
const [pages, setPages] = React.useState<{
|
|
40
40
|
current: undefined | Page,
|
|
41
|
-
|
|
41
|
+
loading: boolean
|
|
42
42
|
}>({
|
|
43
43
|
current: context.page,
|
|
44
|
-
|
|
44
|
+
loading: false
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
const resolvePage = async (request: ClientRequest, locationUpdate?: Update) => {
|
|
48
48
|
|
|
49
49
|
// WARNING: Don"t try to play with pages here, since the object will not be updated
|
|
50
50
|
// If needed to play with pages, do it in the setPages callback below
|
|
51
|
+
|
|
52
|
+
// Set.loading state
|
|
53
|
+
setPages( oldState => ({
|
|
54
|
+
...oldState,
|
|
55
|
+
loading: true,
|
|
56
|
+
}));
|
|
51
57
|
|
|
52
58
|
// Load the route chunks
|
|
53
59
|
context.request = request;
|
|
@@ -63,9 +69,8 @@ export default ({ service: router }: { service: Router }) => {
|
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
newpage.loading = <i src="spin" />
|
|
72
|
+
const data = context.data = await newpage.fetchData();
|
|
73
|
+
|
|
69
74
|
// Add page container
|
|
70
75
|
setPages( pages => {
|
|
71
76
|
|
|
@@ -74,7 +79,7 @@ export default ({ service: router }: { service: Router }) => {
|
|
|
74
79
|
// Check if the page changed
|
|
75
80
|
if (currentRoute?.path === request.path) {
|
|
76
81
|
console.warn(LogPrefix, "Canceling navigation to the same page:", {...request});
|
|
77
|
-
return pages
|
|
82
|
+
return { ...pages, loading: false }
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
// If if the layout changed
|
|
@@ -88,7 +93,7 @@ export default ({ service: router }: { service: Router }) => {
|
|
|
88
93
|
// Find a way to unload the previous layout / page resources before to load the new one
|
|
89
94
|
console.log(LogPrefix, `Changing layout. Before:`, curLayout, 'New layout:', newLayout);
|
|
90
95
|
window.location.replace(request.url);
|
|
91
|
-
return pages
|
|
96
|
+
return { ...pages, loading: false }
|
|
92
97
|
|
|
93
98
|
context.app.setLayout(newLayout);
|
|
94
99
|
}
|
|
@@ -98,13 +103,13 @@ export default ({ service: router }: { service: Router }) => {
|
|
|
98
103
|
if (oldPage !== undefined) {
|
|
99
104
|
setTimeout(() => setPages({
|
|
100
105
|
current: newpage,
|
|
101
|
-
|
|
106
|
+
loading: false
|
|
102
107
|
}), 500);
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
return {
|
|
106
111
|
current: newpage,
|
|
107
|
-
|
|
112
|
+
loading: false
|
|
108
113
|
}
|
|
109
114
|
});
|
|
110
115
|
}
|
|
@@ -116,7 +121,7 @@ export default ({ service: router }: { service: Router }) => {
|
|
|
116
121
|
inline: "nearest"
|
|
117
122
|
})
|
|
118
123
|
|
|
119
|
-
// First
|
|
124
|
+
// First render
|
|
120
125
|
React.useEffect(() => {
|
|
121
126
|
|
|
122
127
|
// Resolve page if it wasn't done via SSR
|
|
@@ -158,9 +163,16 @@ export default ({ service: router }: { service: Router }) => {
|
|
|
158
163
|
|
|
159
164
|
{pages.current && (
|
|
160
165
|
<PageComponent page={pages.current}
|
|
161
|
-
|
|
162
|
-
|
|
166
|
+
/* Create a new instance of the Page component every time the page change
|
|
167
|
+
Otherwise the page will memorise the data of the previous page */
|
|
168
|
+
key={pages.current.chunkId === undefined ? undefined : 'page_' + pages.current.chunkId}
|
|
163
169
|
/>
|
|
164
170
|
)}
|
|
171
|
+
|
|
172
|
+
{pages.loading && (
|
|
173
|
+
<div id="loading">
|
|
174
|
+
<i src="spin" />
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
165
177
|
</>
|
|
166
178
|
}
|
|
@@ -48,9 +48,9 @@ export default class ApiClient implements ApiClientService {
|
|
|
48
48
|
- HIGH LEVEL
|
|
49
49
|
----------------------------------*/
|
|
50
50
|
|
|
51
|
-
public fetch<
|
|
52
|
-
fetchers:
|
|
53
|
-
): TDataReturnedByFetchers<
|
|
51
|
+
public fetch<FetchersList extends TFetcherList = TFetcherList>(
|
|
52
|
+
fetchers: FetchersList
|
|
53
|
+
): TDataReturnedByFetchers<FetchersList> {
|
|
54
54
|
throw new Error("api.fetch shouldn't be called here.");
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -24,8 +24,6 @@ import type ClientRouter from '..';
|
|
|
24
24
|
|
|
25
25
|
export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRouter> {
|
|
26
26
|
|
|
27
|
-
public isLoading: boolean = false;
|
|
28
|
-
public loading: false | ComponentChild;
|
|
29
27
|
public scrollToId: string;
|
|
30
28
|
|
|
31
29
|
public constructor(
|
|
@@ -45,11 +43,9 @@ export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRo
|
|
|
45
43
|
|
|
46
44
|
// Add the page to the context
|
|
47
45
|
this.context.page = this;
|
|
48
|
-
this.isLoading = true;
|
|
49
46
|
|
|
50
47
|
// Data succesfully loaded
|
|
51
48
|
this.data = data || await this.fetchData();
|
|
52
|
-
this.isLoading = false;
|
|
53
49
|
|
|
54
50
|
return this;
|
|
55
51
|
}
|
|
@@ -91,15 +91,15 @@ export type TRouteOptions = {
|
|
|
91
91
|
// Resolving
|
|
92
92
|
domain?: string,
|
|
93
93
|
accept?: string,
|
|
94
|
-
|
|
95
|
-
// Access Restriction
|
|
96
94
|
auth?: TUserRole | boolean,
|
|
97
|
-
|
|
95
|
+
|
|
96
|
+
// Rendering
|
|
97
|
+
static?: boolean,
|
|
98
98
|
layout?: false | string, // The nale of the layout
|
|
99
99
|
|
|
100
|
+
// To cleanup
|
|
100
101
|
TESTING?: boolean,
|
|
101
102
|
logging?: boolean,
|
|
102
|
-
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export type TRouteModule<TRegisteredRoute = any> = {
|
|
@@ -14,11 +14,7 @@ import type { FileToUpload } from '@client/components/inputv3/file';
|
|
|
14
14
|
// By example if we want to fetch an api endpoint only if the url contains a certain url parameter
|
|
15
15
|
export type TFetcherList = { [id: string]: TFetcher | undefined }
|
|
16
16
|
|
|
17
|
-
export type
|
|
18
|
-
|
|
19
|
-
export type TPostDataWithFile = {[key: string]: PrimitiveValue | FileToUpload}
|
|
20
|
-
|
|
21
|
-
export type TFetcher<TData extends unknown = unknown> = {
|
|
17
|
+
export type TFetcher<TData extends any = unknown> = {
|
|
22
18
|
|
|
23
19
|
// For async calls: api.post(...).then((data) => ...)
|
|
24
20
|
then: (callback: (data: TData) => void) => Promise<TData>,
|
|
@@ -44,12 +40,16 @@ export type TApiFetchOptions = {
|
|
|
44
40
|
encoding?: 'json' | 'multipart'
|
|
45
41
|
}
|
|
46
42
|
|
|
43
|
+
export type TPostData = {[key: string]: PrimitiveValue}
|
|
44
|
+
|
|
45
|
+
export type TPostDataWithFile = {[key: string]: PrimitiveValue | FileToUpload}
|
|
46
|
+
|
|
47
47
|
// https://stackoverflow.com/questions/44851268/typescript-how-to-extract-the-generic-parameter-from-a-type
|
|
48
48
|
type TypeWithGeneric<T> = TFetcher<T>
|
|
49
49
|
type extractGeneric<Type> = Type extends TypeWithGeneric<infer X> ? X : never
|
|
50
50
|
|
|
51
51
|
export type TDataReturnedByFetchers<TProvidedData extends TFetcherList = {}> = {
|
|
52
|
-
[Property in keyof TProvidedData]:
|
|
52
|
+
[Property in keyof TProvidedData]: (extractGeneric<TProvidedData[Property]> extends ((...args: any[]) => any)
|
|
53
53
|
? ThenArg<ReturnType< extractGeneric<TProvidedData[Property]> >>
|
|
54
54
|
: extractGeneric<TProvidedData[Property]>
|
|
55
55
|
)
|
|
@@ -106,7 +106,8 @@ export class ServicesContainer<
|
|
|
106
106
|
...Object.getOwnPropertyNames( Object.getPrototypeOf( instance )),
|
|
107
107
|
...Object.getOwnPropertyNames( instance ),
|
|
108
108
|
// service.launch() isn't included, maybe because parent abstract class
|
|
109
|
-
'launch'
|
|
109
|
+
'launch',
|
|
110
|
+
'bindServices'
|
|
110
111
|
];
|
|
111
112
|
|
|
112
113
|
for (const method of methods)
|
|
@@ -83,8 +83,7 @@ export default abstract class Service<
|
|
|
83
83
|
: app
|
|
84
84
|
|
|
85
85
|
// Instanciate subservices
|
|
86
|
-
|
|
87
|
-
this.bindService( localName, services[localName] as unknown as TRegisteredService );
|
|
86
|
+
this.bindServices( services );
|
|
88
87
|
|
|
89
88
|
}
|
|
90
89
|
|
|
@@ -155,7 +154,7 @@ export default abstract class Service<
|
|
|
155
154
|
ReturnType< TServiceClass["getServiceInstance"] >
|
|
156
155
|
&
|
|
157
156
|
{
|
|
158
|
-
new (...args: any[]): TServiceClass & { services: TSubServices },
|
|
157
|
+
new (...args: any[]): TServiceClass["getServiceInstance"] & { services: TSubServices },
|
|
159
158
|
services: TSubServices
|
|
160
159
|
}
|
|
161
160
|
/*Omit<TServiceClass, 'services'> & {
|
|
@@ -169,12 +168,17 @@ export default abstract class Service<
|
|
|
169
168
|
throw new Error(`Unable to use service "${serviceId}": This one hasn't been setup.`);
|
|
170
169
|
|
|
171
170
|
// Bind subservices
|
|
172
|
-
|
|
171
|
+
if (subServices !== undefined)
|
|
172
|
+
registered.subServices = {
|
|
173
|
+
...registered.subServices,
|
|
174
|
+
...subServices
|
|
175
|
+
};
|
|
173
176
|
|
|
174
177
|
// Check if not already instanciated
|
|
175
178
|
const existing = ServicesContainer.allServices[ serviceId ];
|
|
176
179
|
if (existing !== undefined) {
|
|
177
180
|
console.info("Service", serviceId, "already instanciated through another service.");
|
|
181
|
+
existing.bindServices( registered.subServices );
|
|
178
182
|
return existing;
|
|
179
183
|
}
|
|
180
184
|
|
|
@@ -183,14 +187,25 @@ export default abstract class Service<
|
|
|
183
187
|
const ServiceClass = registered.metas.class().default;
|
|
184
188
|
|
|
185
189
|
// Create class instance
|
|
186
|
-
const service = new ServiceClass(
|
|
187
|
-
|
|
190
|
+
const service = new ServiceClass(
|
|
191
|
+
this,
|
|
192
|
+
registered.config,
|
|
193
|
+
registered.subServices,
|
|
194
|
+
this.app || this
|
|
195
|
+
)
|
|
196
|
+
const serviceInstance = service.getServiceInstance();
|
|
188
197
|
|
|
189
198
|
// Bind his own metas
|
|
190
199
|
service.metas = registered.metas;
|
|
191
|
-
ServicesContainer.allServices[ registered.metas.id ] =
|
|
200
|
+
ServicesContainer.allServices[ registered.metas.id ] = serviceInstance;
|
|
201
|
+
|
|
202
|
+
return serviceInstance;
|
|
203
|
+
}
|
|
192
204
|
|
|
193
|
-
|
|
205
|
+
public bindServices( services: TServicesIndex ) {
|
|
206
|
+
|
|
207
|
+
for (const localName in services)
|
|
208
|
+
this.bindService( localName, services[localName] as unknown as TRegisteredService );
|
|
194
209
|
}
|
|
195
210
|
|
|
196
211
|
public bindService( localName: string, service: AnyService ) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"baseUrl": "..",
|
|
6
6
|
"paths": {
|
|
7
7
|
|
|
8
|
-
"@/server/models": ["./server/.generated/models"],
|
|
8
|
+
"@/server/models": ["./server/.generated/models.d.ts"],
|
|
9
9
|
|
|
10
10
|
"@client/*": ["../node_modules/5htp-core/src/client/*"],
|
|
11
11
|
"@common/*": ["../node_modules/5htp-core/src/common/*"],
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
// Npm
|
|
5
5
|
import Ansi2Html from 'ansi-to-html';
|
|
6
|
-
import {
|
|
7
|
-
import type { Console } from '.';;
|
|
6
|
+
import { formatWithOptions } from 'util';
|
|
7
|
+
import type { default as Console, TJsonLog } from '.';;
|
|
8
8
|
|
|
9
9
|
var ansi2Html = new Ansi2Html({
|
|
10
10
|
newline: true,
|
|
@@ -35,30 +35,31 @@ var ansi2Html = new Ansi2Html({
|
|
|
35
35
|
/*----------------------------------
|
|
36
36
|
- METHOD
|
|
37
37
|
----------------------------------*/
|
|
38
|
-
export default (log:
|
|
38
|
+
export default (log: TJsonLog, c: Console) => {
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// Print metas as ANSI
|
|
41
|
+
const logMetaMarkup = c.logger._prettyFormatLogObjMeta({
|
|
42
|
+
date: log.time,
|
|
43
|
+
logLevelId: c.getLogLevelId( log.level ),
|
|
44
|
+
logLevelName: log.level,
|
|
45
|
+
// We consider that having the path is useless in this case
|
|
46
|
+
path: undefined,
|
|
47
|
+
});
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// BUG: log.date pas pris encompte, affiche la date actuelle
|
|
53
|
-
// https://github.com/fullstack-build/tslog/blob/master/src/LoggerWithoutCallSite.ts#L509
|
|
54
|
-
|
|
55
|
-
let ansi: string = '';
|
|
56
|
-
const myStd = { write: (message: string) => ansi += message }
|
|
57
|
-
c.logger.printPrettyLog(myStd, log);
|
|
49
|
+
// Print args as ANSI
|
|
50
|
+
const logArgsAndErrorsMarkup = c.logger.runtime.prettyFormatLogObj( log.args, c.logger.settings);
|
|
51
|
+
const logErrors = logArgsAndErrorsMarkup.errors;
|
|
52
|
+
const logArgs = logArgsAndErrorsMarkup.args;
|
|
53
|
+
const logErrorsStr = (logErrors.length > 0 && logArgs.length > 0 ? "\n" : "") + logErrors.join("\n");
|
|
54
|
+
c.logger.settings.prettyInspectOptions.colors = c.logger.settings.stylePrettyLogs;
|
|
55
|
+
let ansi = logMetaMarkup + formatWithOptions(c.logger.settings.prettyInspectOptions, ...logArgs) + logErrorsStr;
|
|
58
56
|
|
|
57
|
+
// Use HTML spaces
|
|
59
58
|
ansi = ansi.replace(/ {2}/g, ' ');
|
|
60
59
|
ansi = ansi.replace(/\t/g, ' '.repeat(8));
|
|
60
|
+
ansi = ansi.replace(/\n/g, '<br/>');
|
|
61
61
|
|
|
62
|
+
// Convert ANSI to HTML
|
|
62
63
|
const html = ansi2Html.toHtml(ansi)
|
|
63
64
|
|
|
64
65
|
return html;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/*----------------------------------
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Node
|
|
6
|
+
import { serialize } from 'v8';
|
|
7
|
+
import { formatWithOptions } from 'util';
|
|
8
|
+
|
|
4
9
|
// Npm
|
|
5
10
|
import { v4 as uuid } from 'uuid';
|
|
6
|
-
import
|
|
11
|
+
import { Logger, IMeta, ILogObj, ISettings } from 'tslog';
|
|
7
12
|
import { format as formatSql } from 'sql-formatter';
|
|
8
13
|
import highlight from 'cli-highlight';
|
|
9
14
|
|
|
@@ -85,8 +90,14 @@ export type TDbQueryLog = ChannelInfos & {
|
|
|
85
90
|
time: number,
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
export type
|
|
93
|
+
export type TLogLevel = keyof typeof logLevels
|
|
89
94
|
|
|
95
|
+
export type TJsonLog = {
|
|
96
|
+
time: Date,
|
|
97
|
+
level: TLogLevel,
|
|
98
|
+
args: unknown[],
|
|
99
|
+
channel: ChannelInfos
|
|
100
|
+
}
|
|
90
101
|
|
|
91
102
|
/*----------------------------------
|
|
92
103
|
- CONST
|
|
@@ -96,21 +107,12 @@ const LogPrefix = '[console]'
|
|
|
96
107
|
|
|
97
108
|
const errorMailInterval = (1 * 60 * 60 * 1000); // 1 hour
|
|
98
109
|
|
|
99
|
-
const
|
|
100
|
-
'
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
'methodName',
|
|
106
|
-
'functionName',
|
|
107
|
-
'typeName',
|
|
108
|
-
|
|
109
|
-
'filePath',
|
|
110
|
-
'lineNumber',
|
|
111
|
-
'argumentsArray',
|
|
112
|
-
'stack',
|
|
113
|
-
] as const
|
|
110
|
+
const logLevels = {
|
|
111
|
+
'log': 0,
|
|
112
|
+
'info': 3,
|
|
113
|
+
'warn': 4,
|
|
114
|
+
'error': 5
|
|
115
|
+
} as const
|
|
114
116
|
|
|
115
117
|
/*----------------------------------
|
|
116
118
|
- LOGGER
|
|
@@ -118,21 +120,16 @@ const logFields = [
|
|
|
118
120
|
export default class Console extends Service<Config, Hooks, Application, Services> {
|
|
119
121
|
|
|
120
122
|
// Services
|
|
121
|
-
public logger!: Logger
|
|
122
|
-
|
|
123
|
+
public logger!: Logger<ILogObj>;
|
|
123
124
|
// Buffers
|
|
124
|
-
public logs:
|
|
125
|
-
public clients: TGuestLogs[] = [];
|
|
126
|
-
public requests: TRequestLogs[] = [];
|
|
127
|
-
public sqlQueries: TDbQueryLog[] = [];
|
|
125
|
+
public logs: TJsonLog[] = [];
|
|
128
126
|
// Bug ID => Timestamp latest send
|
|
129
127
|
private sentBugs: {[bugId: string]: number} = {};
|
|
130
128
|
|
|
131
|
-
//
|
|
132
|
-
public
|
|
133
|
-
public
|
|
134
|
-
public
|
|
135
|
-
public error = console.error;
|
|
129
|
+
// Old (still useful???)
|
|
130
|
+
/*public clients: TGuestLogs[] = [];
|
|
131
|
+
public requests: TRequestLogs[] = [];
|
|
132
|
+
public sqlQueries: TDbQueryLog[] = [];*/
|
|
136
133
|
|
|
137
134
|
/*----------------------------------
|
|
138
135
|
- LIFECYCLE
|
|
@@ -140,34 +137,62 @@ export default class Console extends Service<Config, Hooks, Application, Service
|
|
|
140
137
|
|
|
141
138
|
protected async start() {
|
|
142
139
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
/*const origConsole = console;
|
|
146
|
-
console.log = (...args: unknown[]) => log(...args)*/
|
|
140
|
+
const origLog = console.log
|
|
147
141
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
//type: this.app.env.profile === 'dev' ? 'pretty' : 'hidden',
|
|
151
|
-
requestId: (): string => {
|
|
152
|
-
const { channelType, channelId } = this.getChannel();
|
|
153
|
-
return channelId === undefined ? channelType : channelType + ':' + channelId;
|
|
154
|
-
},
|
|
155
|
-
displayRequestId: false,
|
|
142
|
+
this.logger = new Logger({
|
|
143
|
+
// Use to improve performance in production
|
|
156
144
|
hideLogPositionForProduction: this.app.env.profile === 'prod',
|
|
145
|
+
type: 'pretty',
|
|
157
146
|
prettyInspectOptions: {
|
|
158
147
|
depth: 2
|
|
148
|
+
},
|
|
149
|
+
overwrite: {
|
|
150
|
+
formatMeta: (meta?: IMeta) => {
|
|
151
|
+
|
|
152
|
+
// Shorten file paths
|
|
153
|
+
if (meta?.path !== undefined) {
|
|
154
|
+
meta.path.filePathWithLine = this.shortenFilePath( meta.path.filePathWithLine );
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return this.logger._prettyFormatLogObjMeta( meta );
|
|
158
|
+
},
|
|
159
|
+
transportFormatted: (
|
|
160
|
+
logMetaMarkup: string,
|
|
161
|
+
logArgs: unknown[],
|
|
162
|
+
logErrors: string[],
|
|
163
|
+
settings: ISettings<ILogObj>
|
|
164
|
+
) => {
|
|
165
|
+
const logErrorsStr = (logErrors.length > 0 && logArgs.length > 0 ? "\n" : "") + logErrors.join("\n");
|
|
166
|
+
settings.prettyInspectOptions.colors = settings.stylePrettyLogs;
|
|
167
|
+
origLog(logMetaMarkup + formatWithOptions(settings.prettyInspectOptions, ...logArgs) + logErrorsStr);
|
|
168
|
+
},
|
|
159
169
|
}
|
|
160
170
|
});
|
|
161
171
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
if (console["_wrapped"] !== undefined)
|
|
173
|
+
return;
|
|
174
|
+
|
|
175
|
+
for (const logLevel in logLevels) {
|
|
176
|
+
console[ logLevel ] = (...args: any[]) => {
|
|
177
|
+
|
|
178
|
+
// Dev mode = no care about performance = rich logging
|
|
179
|
+
if (this.app.env.profile === 'dev')
|
|
180
|
+
//this.logger[ logLevel ](...args);
|
|
181
|
+
origLog(...args);
|
|
182
|
+
// Prod mode = minimal logging
|
|
183
|
+
|
|
184
|
+
const channel = this.getChannel();
|
|
185
|
+
|
|
186
|
+
this.logs.push({
|
|
187
|
+
time: new Date,
|
|
188
|
+
level: logLevel,
|
|
189
|
+
args,
|
|
190
|
+
channel
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console["_wrapped"] = true;
|
|
171
196
|
|
|
172
197
|
setInterval(() => this.clean(), 10000);
|
|
173
198
|
}
|
|
@@ -180,15 +205,50 @@ export default class Console extends Service<Config, Hooks, Application, Service
|
|
|
180
205
|
|
|
181
206
|
}
|
|
182
207
|
|
|
208
|
+
/*----------------------------------
|
|
209
|
+
- LOGS FORMATTING
|
|
210
|
+
----------------------------------*/
|
|
211
|
+
|
|
212
|
+
public shortenFilePath( filepath?: string ) {
|
|
213
|
+
|
|
214
|
+
if (filepath === undefined)
|
|
215
|
+
return undefined;
|
|
216
|
+
|
|
217
|
+
const projectRoot = this.app.container.path.root;
|
|
218
|
+
if (filepath.startsWith( projectRoot ))
|
|
219
|
+
filepath = filepath.substring( projectRoot.length )
|
|
220
|
+
|
|
221
|
+
const frameworkRoot = '/node_modules/5htp-core/src/';
|
|
222
|
+
if (filepath.startsWith( frameworkRoot ))
|
|
223
|
+
filepath = '@' + filepath.substring( frameworkRoot.length )
|
|
224
|
+
|
|
225
|
+
return filepath;
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
183
230
|
/*----------------------------------
|
|
184
231
|
- ACTIONS
|
|
185
232
|
----------------------------------*/
|
|
186
233
|
|
|
234
|
+
public getLogLevelId( logLevelName: TLogLevel ) {
|
|
235
|
+
return logLevels[ logLevelName ]
|
|
236
|
+
}
|
|
237
|
+
|
|
187
238
|
private clean() {
|
|
188
|
-
|
|
239
|
+
|
|
240
|
+
if (this.config.debug) {
|
|
241
|
+
console.log(
|
|
242
|
+
LogPrefix,
|
|
243
|
+
`Clean logs buffer. Current size:`,
|
|
244
|
+
this.logs.length, '/', this.config.bufferLimit,
|
|
245
|
+
'Memory Size:', serialize(this.logs).byteLength
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
189
249
|
const bufferOverflow = this.logs.length - this.config.bufferLimit;
|
|
190
250
|
if (bufferOverflow > 0)
|
|
191
|
-
this.logs = this.logs.slice(bufferOverflow)
|
|
251
|
+
this.logs = this.logs.slice(bufferOverflow);
|
|
192
252
|
}
|
|
193
253
|
|
|
194
254
|
public async createBugReport( error: Error, request?: ServerRequest ) {
|
|
@@ -223,7 +283,7 @@ export default class Console extends Service<Config, Hooks, Application, Service
|
|
|
223
283
|
// On envoi l'email avant l'insertion dans bla bdd
|
|
224
284
|
// Car cette denrière a plus de chances de provoquer une erreur
|
|
225
285
|
const logsHtml = this.printHtml(
|
|
226
|
-
this.logs
|
|
286
|
+
this.logs.filter( e => e.channel.channelId === channelId).slice(-100),
|
|
227
287
|
true
|
|
228
288
|
);
|
|
229
289
|
|
|
@@ -252,95 +312,10 @@ export default class Console extends Service<Config, Hooks, Application, Service
|
|
|
252
312
|
}
|
|
253
313
|
}
|
|
254
314
|
|
|
255
|
-
private logEntry(entry: ILogObject) {
|
|
256
|
-
|
|
257
|
-
// Don't keep logs from the admin sashboard
|
|
258
|
-
const [channelType, channelId] = entry.requestId?.split(':') || ['master'];
|
|
259
|
-
if (entry.requestId === 'admin')
|
|
260
|
-
return;
|
|
261
|
-
|
|
262
|
-
// Only keep data required by printPrettyLog
|
|
263
|
-
// https://github.com/fullstack-build/tslog/blob/4f045d61333230bd0f9db0e0d59cb1e81fc03aa6/src/LoggerWithoutCallSite.ts#L509
|
|
264
|
-
const miniLog: TObjetDonnees = { channelType, channelId };
|
|
265
|
-
for (const k of logFields)
|
|
266
|
-
miniLog[k] = entry[k];
|
|
267
|
-
|
|
268
|
-
this.logs.push(miniLog as TLog);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
public client( client: TGuestLogs ) {
|
|
272
|
-
this.clients.push(client);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
public request( request: TRequestLogs ) {
|
|
276
|
-
|
|
277
|
-
if (request.id === 'admin')
|
|
278
|
-
return;
|
|
279
|
-
|
|
280
|
-
this.requests.push( request );
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
public database( dbQuery: TDbQueryLog ) {
|
|
284
|
-
|
|
285
|
-
this.requests.push( dbQuery );
|
|
286
|
-
}
|
|
287
|
-
|
|
288
315
|
/*----------------------------------
|
|
289
316
|
- READ
|
|
290
317
|
----------------------------------*/
|
|
291
318
|
|
|
292
|
-
/*public getClients() {
|
|
293
|
-
return sql`
|
|
294
|
-
SELECT * FROM logs.Clients
|
|
295
|
-
ORDER BY activity DESC
|
|
296
|
-
LIMIT 100
|
|
297
|
-
`.all();
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
public async getClient(clientId: string) {
|
|
301
|
-
return (
|
|
302
|
-
this.clients.find(c => c.id === clientId)
|
|
303
|
-
||
|
|
304
|
-
await sql`
|
|
305
|
-
SELECT * FROM logs.Clients
|
|
306
|
-
WHERE id = ${clientId}
|
|
307
|
-
`.first()
|
|
308
|
-
)
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
public getRequests(clientId?: string) {
|
|
312
|
-
return sql`
|
|
313
|
-
SELECT * FROM logs.Requests
|
|
314
|
-
ORDER BY date DESC
|
|
315
|
-
LIMIT 100
|
|
316
|
-
`.all();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
public async getRequest(requestId: string) {
|
|
320
|
-
return (
|
|
321
|
-
this.requests.find(r => r.id === requestId)
|
|
322
|
-
||
|
|
323
|
-
await sql`
|
|
324
|
-
SELECT * FROM logs.Requests
|
|
325
|
-
WHERE id = ${requestId}
|
|
326
|
-
`.first()
|
|
327
|
-
)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
public getQueries( channelType: ChannelInfos["channelType"], channelId?: string ) {
|
|
331
|
-
|
|
332
|
-
const filters: Partial<TDbQueryLog> = { channelType };
|
|
333
|
-
if (channelId !== undefined)
|
|
334
|
-
filters.channelId = channelId;
|
|
335
|
-
|
|
336
|
-
return sql`
|
|
337
|
-
SELECT * FROM logs.Queries
|
|
338
|
-
WHERE :${filters}
|
|
339
|
-
ORDER BY date DESC
|
|
340
|
-
LIMIT 100
|
|
341
|
-
`.all();
|
|
342
|
-
}*/
|
|
343
|
-
|
|
344
319
|
public async getLogs( channelType: ChannelInfos["channelType"], channelId?: string ) {
|
|
345
320
|
|
|
346
321
|
const filters: Partial<TDbQueryLog> = { channelType };
|
|
@@ -373,7 +348,7 @@ export default class Console extends Service<Config, Hooks, Application, Service
|
|
|
373
348
|
return this.printHtml( entries );
|
|
374
349
|
}
|
|
375
350
|
|
|
376
|
-
public printHtml(logs:
|
|
351
|
+
public printHtml( logs: TJsonLog[], full: boolean = false ): string {
|
|
377
352
|
|
|
378
353
|
let html = logs.map( logEntry => logToHTML( logEntry, this )).join('\n');
|
|
379
354
|
|
|
@@ -9,7 +9,7 @@ import cronParser, { CronExpression } from 'cron-parser';
|
|
|
9
9
|
- TYPES
|
|
10
10
|
----------------------------------*/
|
|
11
11
|
|
|
12
|
-
import type
|
|
12
|
+
import type CronManager from '.';
|
|
13
13
|
|
|
14
14
|
export type TFrequence = string | Date;
|
|
15
15
|
export type TRunner = () => Promise<any>
|
|
@@ -22,17 +22,15 @@ export default class CronTask {
|
|
|
22
22
|
public cron?: CronExpression
|
|
23
23
|
public nextInvocation?: Date;
|
|
24
24
|
|
|
25
|
-
private debug?: boolean;
|
|
26
|
-
|
|
27
25
|
public constructor(
|
|
28
|
-
manager: CronManager,
|
|
26
|
+
private manager: CronManager,
|
|
29
27
|
public nom: string,
|
|
30
28
|
next: TFrequence,
|
|
31
29
|
public runner: TRunner,
|
|
32
30
|
public autoexec?: boolean
|
|
33
31
|
) {
|
|
34
32
|
|
|
35
|
-
console.info(`[cron][${this.nom}] Enregistrement de la tâche`);
|
|
33
|
+
this.manager.config.debug && console.info(`[cron][${this.nom}] Enregistrement de la tâche`);
|
|
36
34
|
|
|
37
35
|
this.schedule(next);
|
|
38
36
|
|
|
@@ -48,13 +46,15 @@ export default class CronTask {
|
|
|
48
46
|
this.cron = cronParser.parseExpression(next);
|
|
49
47
|
this.nextInvocation = this.cron.next().toDate();
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
this.manager.config.debug &&
|
|
50
|
+
console.info(`[cron][${this.nom}] Planifié pour ${this.nextInvocation.toISOString()} via cron ${next}`);
|
|
52
51
|
|
|
53
52
|
// Date
|
|
54
53
|
} else {
|
|
55
54
|
|
|
56
55
|
this.nextInvocation = next;
|
|
57
|
-
|
|
56
|
+
this.manager.config.debug &&
|
|
57
|
+
console.info(`[cron][${this.nom}] Planifié pour ${this.nextInvocation.toISOString()} via date`);
|
|
58
58
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -80,7 +80,7 @@ export default class CronTask {
|
|
|
80
80
|
|
|
81
81
|
// Execution
|
|
82
82
|
this.runner().then(() => {
|
|
83
|
-
console.info(`
|
|
83
|
+
this.manager.config.debug && console.info(`Task runned.`);
|
|
84
84
|
})
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -21,7 +21,7 @@ export { default as CronTask } from './CronTask';
|
|
|
21
21
|
----------------------------------*/
|
|
22
22
|
|
|
23
23
|
export type Config = {
|
|
24
|
-
|
|
24
|
+
debug?: boolean
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export type Hooks = {
|
|
@@ -46,8 +46,6 @@ export default class CronManager extends Service<Config, Hooks, Application, Ser
|
|
|
46
46
|
----------------------------------*/
|
|
47
47
|
|
|
48
48
|
protected async start() {
|
|
49
|
-
|
|
50
|
-
this.app.on('cleanup', () => this.cleanup());
|
|
51
49
|
|
|
52
50
|
clearInterval(CronManager.timer);
|
|
53
51
|
CronManager.timer = setInterval(() => {
|
|
@@ -111,8 +111,6 @@ const LogPrefix = '[database][meta]';
|
|
|
111
111
|
const sqlTypeParamsReg = /\'([^\']+)\'\,?/gi;
|
|
112
112
|
const typeViaCommentReg = /\[type=([a-z]+)\]/g;
|
|
113
113
|
|
|
114
|
-
const modelsTypesPath = process.cwd() + '/src/server/models.ts';
|
|
115
|
-
|
|
116
114
|
/*----------------------------------
|
|
117
115
|
- FUNCTIONS
|
|
118
116
|
----------------------------------*/
|
|
@@ -368,11 +366,11 @@ export default class MySQLMetasParser {
|
|
|
368
366
|
}
|
|
369
367
|
}
|
|
370
368
|
|
|
369
|
+
// Given that this file is updated during run time,
|
|
370
|
+
// We output a typescript ambient file, so the file change doest trigger infinite app reload
|
|
371
371
|
fs.outputFileSync(
|
|
372
|
-
path.join( Container.path.server.generated, 'models.ts'),
|
|
372
|
+
path.join( Container.path.server.generated, 'models.d.ts'),
|
|
373
373
|
types.join('\n')
|
|
374
374
|
);
|
|
375
|
-
this.debug && console.log(LogPrefix, `Wrote database types to ${modelsTypesPath}`);
|
|
376
|
-
|
|
377
375
|
}
|
|
378
376
|
}
|
|
@@ -126,6 +126,9 @@ export default class ServerRouter<
|
|
|
126
126
|
public errors: { [code: number]: TErrorRoute } = {};
|
|
127
127
|
public ssrRoutes: TSsrUnresolvedRoute[] = [];
|
|
128
128
|
|
|
129
|
+
// Cache (ex: for static pages)
|
|
130
|
+
public cache: {[pageId: string]: string} = {}
|
|
131
|
+
|
|
129
132
|
/*----------------------------------
|
|
130
133
|
- SERVICE
|
|
131
134
|
----------------------------------*/
|
|
@@ -97,25 +97,40 @@ export default class ServerResponse<
|
|
|
97
97
|
// Create response context for controllers
|
|
98
98
|
const context = await this.createContext(route);
|
|
99
99
|
|
|
100
|
+
// Static rendering
|
|
101
|
+
const chunkId = route.options["id"];
|
|
102
|
+
if (route.options.static &&
|
|
103
|
+
chunkId !== undefined
|
|
104
|
+
&&
|
|
105
|
+
this.router.cache[ chunkId ] !== undefined
|
|
106
|
+
) {
|
|
107
|
+
await this.html( this.router.cache[ chunkId ] );
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
// Run controller
|
|
101
|
-
const
|
|
112
|
+
const content = await this.route.controller( context );
|
|
102
113
|
|
|
103
|
-
// Handle
|
|
104
|
-
if (
|
|
114
|
+
// Handle content type
|
|
115
|
+
if (content === undefined)
|
|
105
116
|
return;
|
|
106
117
|
|
|
107
|
-
// No need to process the
|
|
108
|
-
if (
|
|
118
|
+
// No need to process the content
|
|
119
|
+
if (content instanceof ServerResponse)
|
|
109
120
|
return;
|
|
110
121
|
// Render react page to html
|
|
111
|
-
else if (
|
|
112
|
-
await this.render(
|
|
122
|
+
else if (content instanceof Page)
|
|
123
|
+
await this.render(content, context, additionnalData);
|
|
113
124
|
// Return HTML
|
|
114
|
-
else if (typeof
|
|
115
|
-
await this.html(
|
|
125
|
+
else if (typeof content === 'string' && this.route.options.accept === 'html')
|
|
126
|
+
await this.html(content);
|
|
116
127
|
// Return JSON
|
|
117
128
|
else
|
|
118
|
-
await this.json(
|
|
129
|
+
await this.json(content);
|
|
130
|
+
|
|
131
|
+
// Cache
|
|
132
|
+
if (route.options.static)
|
|
133
|
+
this.router.cache[ chunkId ] = this.data;
|
|
119
134
|
}
|
|
120
135
|
|
|
121
136
|
/*----------------------------------
|
|
File without changes
|