dce-expresskit 4.0.0-beta-logreviewer.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 (99) hide show
  1. package/.eslintrc.js +93 -0
  2. package/LICENSE +21 -0
  3. package/README.md +17 -0
  4. package/genEncodedSecret.ts +107 -0
  5. package/genSalt.ts +15 -0
  6. package/lib/constants/LOG_REVIEW_PAGE_SIZE.d.ts +6 -0
  7. package/lib/constants/LOG_REVIEW_PAGE_SIZE.js +9 -0
  8. package/lib/constants/LOG_REVIEW_PAGE_SIZE.js.map +1 -0
  9. package/lib/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.d.ts +6 -0
  10. package/lib/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.js +13 -0
  11. package/lib/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.js.map +1 -0
  12. package/lib/constants/LOG_REVIEW_STATUS_ROUTE.d.ts +7 -0
  13. package/lib/constants/LOG_REVIEW_STATUS_ROUTE.js +14 -0
  14. package/lib/constants/LOG_REVIEW_STATUS_ROUTE.js.map +1 -0
  15. package/lib/constants/LOG_ROUTE_PATH.d.ts +6 -0
  16. package/lib/constants/LOG_ROUTE_PATH.js +13 -0
  17. package/lib/constants/LOG_ROUTE_PATH.js.map +1 -0
  18. package/lib/constants/ROUTE_PATH_PREFIX.d.ts +6 -0
  19. package/lib/constants/ROUTE_PATH_PREFIX.js +9 -0
  20. package/lib/constants/ROUTE_PATH_PREFIX.js.map +1 -0
  21. package/lib/errors/ErrorWithCode.d.ts +9 -0
  22. package/lib/errors/ErrorWithCode.js +33 -0
  23. package/lib/errors/ErrorWithCode.js.map +1 -0
  24. package/lib/helpers/addDBEditorEndpoints/generateEndpointPath.d.ts +9 -0
  25. package/lib/helpers/addDBEditorEndpoints/generateEndpointPath.js +17 -0
  26. package/lib/helpers/addDBEditorEndpoints/generateEndpointPath.js.map +1 -0
  27. package/lib/helpers/addDBEditorEndpoints/index.d.ts +41 -0
  28. package/lib/helpers/addDBEditorEndpoints/index.js +134 -0
  29. package/lib/helpers/addDBEditorEndpoints/index.js.map +1 -0
  30. package/lib/helpers/dataSigner.d.ts +40 -0
  31. package/lib/helpers/dataSigner.js +242 -0
  32. package/lib/helpers/dataSigner.js.map +1 -0
  33. package/lib/helpers/genRouteHandler.d.ts +75 -0
  34. package/lib/helpers/genRouteHandler.js +662 -0
  35. package/lib/helpers/genRouteHandler.js.map +1 -0
  36. package/lib/helpers/getLogReviewerLogs.d.ts +27 -0
  37. package/lib/helpers/getLogReviewerLogs.js +238 -0
  38. package/lib/helpers/getLogReviewerLogs.js.map +1 -0
  39. package/lib/helpers/handleError.d.ts +18 -0
  40. package/lib/helpers/handleError.js +51 -0
  41. package/lib/helpers/handleError.js.map +1 -0
  42. package/lib/helpers/handleSuccess.d.ts +8 -0
  43. package/lib/helpers/handleSuccess.js +20 -0
  44. package/lib/helpers/handleSuccess.js.map +1 -0
  45. package/lib/helpers/initCrossServerCredentialCollection.d.ts +11 -0
  46. package/lib/helpers/initCrossServerCredentialCollection.js +15 -0
  47. package/lib/helpers/initCrossServerCredentialCollection.js.map +1 -0
  48. package/lib/helpers/initLogCollection.d.ts +11 -0
  49. package/lib/helpers/initLogCollection.js +26 -0
  50. package/lib/helpers/initLogCollection.js.map +1 -0
  51. package/lib/helpers/initServer.d.ts +45 -0
  52. package/lib/helpers/initServer.js +292 -0
  53. package/lib/helpers/initServer.js.map +1 -0
  54. package/lib/helpers/parseUserAgent.d.ts +17 -0
  55. package/lib/helpers/parseUserAgent.js +108 -0
  56. package/lib/helpers/parseUserAgent.js.map +1 -0
  57. package/lib/helpers/visitEndpointOnAnotherServer/index.d.ts +18 -0
  58. package/lib/helpers/visitEndpointOnAnotherServer/index.js +89 -0
  59. package/lib/helpers/visitEndpointOnAnotherServer/index.js.map +1 -0
  60. package/lib/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +23 -0
  61. package/lib/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.js +236 -0
  62. package/lib/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.js.map +1 -0
  63. package/lib/html/genErrorPage.d.ts +19 -0
  64. package/lib/html/genErrorPage.js +27 -0
  65. package/lib/html/genErrorPage.js.map +1 -0
  66. package/lib/html/genInfoPage.d.ts +13 -0
  67. package/lib/html/genInfoPage.js +16 -0
  68. package/lib/html/genInfoPage.js.map +1 -0
  69. package/lib/index.d.ts +11 -0
  70. package/lib/index.js +68 -0
  71. package/lib/index.js.map +1 -0
  72. package/lib/types/CrossServerCredential.d.ts +11 -0
  73. package/lib/types/CrossServerCredential.js +3 -0
  74. package/lib/types/CrossServerCredential.js.map +1 -0
  75. package/lib/types/ExpressKitErrorCode.d.ts +31 -0
  76. package/lib/types/ExpressKitErrorCode.js +38 -0
  77. package/lib/types/ExpressKitErrorCode.js.map +1 -0
  78. package/package.json +53 -0
  79. package/src/constants/LOG_REVIEW_PAGE_SIZE.ts +7 -0
  80. package/src/errors/ErrorWithCode.tsx +15 -0
  81. package/src/helpers/addDBEditorEndpoints/generateEndpointPath.ts +16 -0
  82. package/src/helpers/addDBEditorEndpoints/index.ts +130 -0
  83. package/src/helpers/dataSigner.ts +319 -0
  84. package/src/helpers/genRouteHandler.ts +920 -0
  85. package/src/helpers/getLogReviewerLogs.ts +259 -0
  86. package/src/helpers/handleError.ts +66 -0
  87. package/src/helpers/handleSuccess.ts +18 -0
  88. package/src/helpers/initCrossServerCredentialCollection.ts +19 -0
  89. package/src/helpers/initLogCollection.ts +30 -0
  90. package/src/helpers/initServer.ts +283 -0
  91. package/src/helpers/parseUserAgent.ts +108 -0
  92. package/src/helpers/visitEndpointOnAnotherServer/index.ts +70 -0
  93. package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +257 -0
  94. package/src/html/genErrorPage.ts +144 -0
  95. package/src/html/genInfoPage.ts +101 -0
  96. package/src/index.ts +125 -0
  97. package/src/types/CrossServerCredential.ts +16 -0
  98. package/src/types/ExpressKitErrorCode.ts +37 -0
  99. package/tsconfig.json +19 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Perform a rudimentary parsing of the user's browser agent string
3
+ * @author Gabe Abrams
4
+ * @param userAgent the user's browser agent
5
+ * @returns user info
6
+ */
7
+ const parseUserAgent = (userAgent: string) => {
8
+ /* ------------- Browser ------------ */
9
+
10
+ let browser: { name: string, version: string } = {
11
+ name: 'Unknown',
12
+ version: 'Unknown',
13
+ };
14
+
15
+ // Parse user agent
16
+ let verOffset: number;
17
+ let nameOffset: number;
18
+ if ((verOffset = userAgent.indexOf('Opera')) !== -1) {
19
+ // In Opera, the true version is after 'Opera' or after 'Version'
20
+ browser = {
21
+ name: 'Opera',
22
+ version: userAgent.substring(verOffset + 6),
23
+ };
24
+ if ((verOffset = userAgent.indexOf('Version')) !== -1) {
25
+ browser.version = userAgent.substring(verOffset + 8);
26
+ }
27
+ } else if ((verOffset = userAgent.indexOf('MSIE')) !== -1) {
28
+ // In MSIE, the true version is after 'MSIE' in userAgent
29
+ browser = {
30
+ name: 'Internet Explorer',
31
+ version: userAgent.substring(verOffset + 5),
32
+ };
33
+ } else if ((verOffset = userAgent.indexOf('Chrome')) !== -1) {
34
+ // In Chrome, the true version is after 'Chrome'
35
+ browser = {
36
+ name: 'Chrome',
37
+ version: userAgent.substring(verOffset + 7),
38
+ };
39
+ } else if ((verOffset = userAgent.indexOf('Safari')) !== -1) {
40
+ // In Safari, the true version is after 'Safari' or after 'Version'
41
+ browser = {
42
+ name: 'Safari',
43
+ version: userAgent.substring(verOffset + 7),
44
+ };
45
+ if ((verOffset = userAgent.indexOf('Version')) !== -1) {
46
+ browser.version = userAgent.substring(verOffset + 8);
47
+ }
48
+ } else if ((verOffset = userAgent.indexOf('Firefox')) != -1) {
49
+ // In Firefox, the true version is after 'Firefox'
50
+ browser = {
51
+ name: 'Firefox',
52
+ version: userAgent.substring(verOffset + 8),
53
+ };
54
+ } else if (
55
+ (nameOffset = userAgent.lastIndexOf(' ') + 1)
56
+ < (verOffset = userAgent.lastIndexOf('/'))
57
+ ) {
58
+ browser = {
59
+ name: userAgent.substring(nameOffset, verOffset),
60
+ version: userAgent.substring(verOffset + 1),
61
+ };
62
+ }
63
+
64
+ // Postprocess version
65
+ // trim the fullVersion string at semicolon/space if present
66
+ let ix: number;
67
+ if ((ix = browser.version.indexOf(';')) !== -1) {
68
+ browser.version = browser.version.substring(0, ix);
69
+ }
70
+ if ((ix = browser.version.indexOf(' ')) !== -1) {
71
+ browser.version = browser.version.substring(0, ix);
72
+ }
73
+
74
+ /* ------------- Device ------------- */
75
+
76
+ // Detect os
77
+ let os = 'Unknown';
78
+ if (userAgent.includes('Linux')) {
79
+ os = 'Linux';
80
+ } else if (userAgent.includes('like Mac')) {
81
+ os = 'iOS';
82
+ } else if (userAgent.includes('Mac')) {
83
+ os = 'Mac';
84
+ } else if (userAgent.includes('Android')) {
85
+ os = 'Android';
86
+ } else if (userAgent.includes('Win')) {
87
+ os = 'Win';
88
+ }
89
+
90
+ // Check if mobile
91
+ const isMobile = !!userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/);
92
+
93
+ // Device
94
+ const device = {
95
+ isMobile,
96
+ os,
97
+ };
98
+
99
+ /* ------------- Finish ------------- */
100
+
101
+ // Return info
102
+ return {
103
+ browser,
104
+ device,
105
+ };
106
+ };
107
+
108
+ export default parseUserAgent;
@@ -0,0 +1,70 @@
1
+ // Import dce-reactkit
2
+ import {
3
+ ErrorWithCode,
4
+ ReactKitErrorCode,
5
+ } from 'dce-reactkit';
6
+
7
+ // Import shared types
8
+ import sendServerToServerRequest from './sendServerToServerRequest';
9
+
10
+ /*------------------------------------------------------------------------*/
11
+ /* -------------------------------- Main -------------------------------- */
12
+ /*------------------------------------------------------------------------*/
13
+
14
+ /**
15
+ * Visit an endpoint on another server
16
+ * @author Gabe Abrams
17
+ * @param opts object containing all arguments
18
+ * @param opts.method the method of the endpoint
19
+ * @param opts.path the path of the other server's endpoint
20
+ * @param opts.host the host of the other server
21
+ * @param [opts.params={}] query/body parameters to include
22
+ * @param [opts.responseType=JSON] the response type from the other server
23
+ */
24
+ const visitEndpointOnAnotherServer = async (
25
+ opts: {
26
+ method: 'GET' | 'POST' | 'DELETE' | 'PUT',
27
+ path: string,
28
+ host: string,
29
+ params?: { [key in string]: any },
30
+ responseType?: 'JSON' | 'Text',
31
+ },
32
+ ): Promise<any> => {
33
+ // Send the request
34
+ const response = await sendServerToServerRequest({
35
+ path: opts.path,
36
+ host: opts.host,
37
+ method: opts.method,
38
+ params: opts.params,
39
+ responseType: opts.responseType,
40
+ });
41
+
42
+ // Check for failure
43
+ if (!response || !response.body) {
44
+ throw new ErrorWithCode(
45
+ 'We didn\'t get a response from the other server. Please check the network between the two connection.',
46
+ ReactKitErrorCode.NoResponse,
47
+ );
48
+ }
49
+ if (!response.body.success) {
50
+ // Other errors
51
+ throw new ErrorWithCode(
52
+ (
53
+ response.body.message
54
+ || 'An unknown error occurred. Please contact an admin.'
55
+ ),
56
+ (
57
+ response.body.code
58
+ || ReactKitErrorCode.NoCode
59
+ ),
60
+ );
61
+ }
62
+
63
+ // Success! Extract the body
64
+ const { body } = response.body;
65
+
66
+ // Return
67
+ return body;
68
+ };
69
+
70
+ export default visitEndpointOnAnotherServer;
@@ -0,0 +1,257 @@
1
+ // Import libs
2
+ import qs from 'qs';
3
+
4
+ // Import dce-reactkit
5
+ import {
6
+ ErrorWithCode,
7
+ } from 'dce-reactkit';
8
+
9
+ // Import data signer
10
+ import { signRequest } from '../dataSigner';
11
+
12
+ // Import shared types
13
+ import ExpressKitErrorCode from '../../types/ExpressKitErrorCode';
14
+
15
+ /*------------------------------------------------------------------------*/
16
+ /* ----------------------------- Credentials ---------------------------- */
17
+ /*------------------------------------------------------------------------*/
18
+
19
+ /*
20
+ DCEKIT_CROSS_SERVER_CREDENTIALS format:
21
+ |host:key:secret||host:key:secret|...
22
+ */
23
+
24
+ const credentials: {
25
+ host: string,
26
+ key: string,
27
+ secret: string,
28
+ }[] = (
29
+ (process.env.DCEKIT_CROSS_SERVER_CREDENTIALS ?? '')
30
+ // Replace multiple | with a single one
31
+ .replace(/\|+/g, '|')
32
+ // Split by |
33
+ .split('|')
34
+ // Remove empty strings
35
+ .filter((str) => {
36
+ return str.trim().length > 0;
37
+ })
38
+ // Process each credential
39
+ .map((str) => {
40
+ // Split by :
41
+ const parts = str.split(':');
42
+
43
+ // Check for errors
44
+ if (parts.length !== 3) {
45
+ throw new ErrorWithCode(
46
+ 'Invalid DCEKIT_CROSS_SERVER_CREDENTIALS format. Each credential must be in the format |host:key:secret|',
47
+ ExpressKitErrorCode.InvalidCrossServerCredentialsFormat,
48
+ );
49
+ }
50
+
51
+ // Return the credential
52
+ return {
53
+ host: parts[0].trim(),
54
+ key: parts[1].trim(),
55
+ secret: parts[2].trim(),
56
+ };
57
+ })
58
+ );
59
+
60
+ /*------------------------------------------------------------------------*/
61
+ /* ------------------------------- Helpers ------------------------------ */
62
+ /*------------------------------------------------------------------------*/
63
+
64
+ /**
65
+ * Get the credential to use for the request to another server
66
+ * @author Gabe Abrams
67
+ * @param host the host of the other server
68
+ * @return the credential to use
69
+ */
70
+ const getCrossServerCredential = (host: string) => {
71
+ // Find the credential
72
+ const credential = credentials.find((cred) => {
73
+ return cred.host.toLowerCase() === host.toLowerCase();
74
+ });
75
+ if (!credential) {
76
+ throw new ErrorWithCode(
77
+ 'Cannot send cross-server signed request there was no credential that matched the host that the request is being sent to.',
78
+ ExpressKitErrorCode.CrossServerNoCredentialsToSignWith,
79
+ );
80
+ }
81
+
82
+ // Return credential
83
+ return credential;
84
+ };
85
+
86
+ /*------------------------------------------------------------------------*/
87
+ /* -------------------------------- Main -------------------------------- */
88
+ /*------------------------------------------------------------------------*/
89
+
90
+ /**
91
+ * Sends and retries an http request
92
+ * @author Gabriel Abrams
93
+ * @param opts object containing all arguments
94
+ * @param opts.path path to send request to
95
+ * @param [opts.host] host to send request to
96
+ * @param [opts.method=GET] http method to use
97
+ * @param [opts.params] body/data to include in the request
98
+ * @param [opts.responseType=JSON] expected response type
99
+ * @returns { body, status, headers } on success
100
+ */
101
+ const sendServerToServerRequest = async (
102
+ opts: {
103
+ path: string,
104
+ host?: string,
105
+ method?: ('GET' | 'POST' | 'PUT' | 'DELETE'),
106
+ params?: { [k in string]: any },
107
+ responseType?: 'Text' | 'JSON',
108
+ },
109
+ ): Promise<{
110
+ body: any,
111
+ status: number,
112
+ headers: { [k in string]: any },
113
+ }> => {
114
+ // Process method
115
+ const method: ('GET' | 'POST' | 'PUT' | 'DELETE') = (opts.method || 'GET');
116
+
117
+ // Encode objects within params
118
+ let params: {
119
+ [k in string]: any
120
+ } | undefined;
121
+ if (opts.params) {
122
+ params = {};
123
+ Object.entries(opts.params).forEach(([key, val]) => {
124
+ if (typeof val === 'object' && !Array.isArray(val)) {
125
+ (params as any)[key] = JSON.stringify(val);
126
+ } else {
127
+ (params as any)[key] = val;
128
+ }
129
+ });
130
+ }
131
+
132
+ // Get cross-server credential
133
+ const credential = getCrossServerCredential(opts.host);
134
+
135
+ // Sign the request, get new params
136
+ params = await signRequest({
137
+ method: opts.method,
138
+ path: opts.path,
139
+ params: params ?? {},
140
+ key: credential.key,
141
+ secret: credential.secret,
142
+ });
143
+
144
+ // Stringify parameters
145
+ const stringifiedParams = qs.stringify(
146
+ params || {},
147
+ {
148
+ encodeValuesOnly: true,
149
+ arrayFormat: 'brackets',
150
+ },
151
+ );
152
+
153
+ // Create url (include query if GET)
154
+ const query = (method === 'GET' ? `?${stringifiedParams}` : '');
155
+ let url;
156
+ if (!opts.host) {
157
+ // No host included at all. Just send to a path
158
+ url = `${opts.path}${query}`;
159
+ } else {
160
+ url = `https://${opts.host}${opts.path}${query}`;
161
+ }
162
+
163
+ // Update headers
164
+ const headers: {
165
+ [k: string]: any,
166
+ } = {};
167
+ let data: string | null | { [k: string]: any } | undefined = null;
168
+ if (!headers['Content-Type']) {
169
+ // Form encoded
170
+ headers['Content-Type'] = 'application/x-www-form-urlencoded';
171
+ // Add data if applicable
172
+ data = (method !== 'GET' ? stringifiedParams : null);
173
+ } else {
174
+ // JSON encode
175
+ data = params;
176
+ }
177
+
178
+ // Encode data
179
+ let encodedData: URLSearchParams | string | undefined;
180
+ if (data) {
181
+ if (headers['Content-Type'] === 'application/x-www-form-urlencoded') {
182
+ encodedData = new URLSearchParams(params);
183
+ } else {
184
+ encodedData = JSON.stringify(data);
185
+ }
186
+ }
187
+
188
+ // Send request
189
+ try {
190
+ const response = await fetch(
191
+ url,
192
+ {
193
+ method,
194
+ mode: 'cors',
195
+ headers: headers ?? {},
196
+ body: (
197
+ (method !== 'GET' && encodedData)
198
+ ? encodedData
199
+ : undefined
200
+ ),
201
+ redirect: 'follow',
202
+ },
203
+ );
204
+
205
+ // Get headers map
206
+ const responseHeaders: {
207
+ [k in string]: string
208
+ } = {};
209
+ response.headers.forEach((value, key) => {
210
+ responseHeaders[key] = value;
211
+ });
212
+
213
+ // Process response based on responseType
214
+ try {
215
+ // Parse response
216
+ let responseBody: any;
217
+ if (
218
+ opts.responseType
219
+ && opts.responseType === 'Text'
220
+ ) {
221
+ // Response type is text
222
+ responseBody = await response.text();
223
+ } else {
224
+ // Response type is JSON
225
+ responseBody = await response.json();
226
+ }
227
+
228
+ // Return response
229
+ return {
230
+ body: responseBody,
231
+ status: response.status,
232
+ headers: responseHeaders,
233
+ };
234
+ } catch (err) {
235
+ throw new ErrorWithCode(
236
+ `Failed to parse response as ${opts.responseType}: ${(err as any)?.message}`,
237
+ ExpressKitErrorCode.ResponseParseError,
238
+ );
239
+ }
240
+ } catch (err) {
241
+ // Self-signed certificate error:
242
+ if ((err as any)?.message?.includes('self signed certificate')) {
243
+ throw new ErrorWithCode(
244
+ 'We refused to send a request because the receiver has self-signed certificates.',
245
+ ExpressKitErrorCode.SelfSigned,
246
+ );
247
+ }
248
+
249
+ // No tries left
250
+ throw new ErrorWithCode(
251
+ `We encountered an error when trying to send a network request. If this issue persists, contact an admin. Error: ${(err as any)?.message}`,
252
+ ExpressKitErrorCode.NotConnected,
253
+ );
254
+ }
255
+ };
256
+
257
+ export default sendServerToServerRequest;
@@ -0,0 +1,144 @@
1
+ // Import dce-reactkit
2
+ import { ReactKitErrorCode } from 'dce-reactkit';
3
+
4
+ // Import shared types
5
+ import ExpressKitErrorCode from '../types/ExpressKitErrorCode';
6
+
7
+ /**
8
+ * Generate a static error page
9
+ * @author Gabe Abrams
10
+ * @param opts object containing all arguments
11
+ * @param [opts.title=An Error Occurred] title of the error box
12
+ * @param [opts.description=An unknown server error occurred. Please contact support.]
13
+ * a human-readable description of the error
14
+ * @param [opts.code=ReactKitErrorCode.NoCode] error code to show
15
+ * @param [opts.pageTitle=opts.title] title of the page/tab if it differs from
16
+ * the title of the error
17
+ * @returns html of the page
18
+ */
19
+ const genErrorPage = (
20
+ opts: {
21
+ title?: string,
22
+ description?: string,
23
+ code?: string,
24
+ pageTitle?: string,
25
+ } = {},
26
+ ): string => {
27
+ const title = (opts.title ?? 'An Error Occurred');
28
+ const pageTitle = (opts.pageTitle ?? title);
29
+ const description = (
30
+ opts.description
31
+ ?? 'An unknown server error occurred. Please contact support.'
32
+ );
33
+ const code = (opts.code ?? ReactKitErrorCode.NoCode);
34
+
35
+ return `
36
+ <head>
37
+ <!-- Metadata -->
38
+ <meta
39
+ name="viewport"
40
+ content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0"
41
+ >
42
+
43
+ <!-- Title -->
44
+ <title>${pageTitle}</title>
45
+
46
+ <!-- Bootstrap -->
47
+ <link
48
+ rel="stylesheet"
49
+ href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/css/bootstrap.min.css"
50
+ integrity="sha512-siwe/oXMhSjGCwLn+scraPOWrJxHlUgMBMZXdPe2Tnk3I0x3ESCoLz7WZ5NTH6SZrywMY+PB1cjyqJ5jAluCOg=="
51
+ crossorigin="anonymous"
52
+ referrerpolicy="no-referrer"
53
+ />
54
+ <script
55
+ src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/js/bootstrap.min.js"
56
+ integrity="sha512-vyRAVI0IEm6LI/fVSv/Wq/d0KUfrg3hJq2Qz5FlfER69sf3ZHlOrsLriNm49FxnpUGmhx+TaJKwJ+ByTLKT+Yg=="
57
+ crossorigin="anonymous"
58
+ referrerpolicy="no-referrer"
59
+ ></script>
60
+
61
+ <!-- FontAwesome -->
62
+ <link
63
+ rel="stylesheet"
64
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
65
+ integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
66
+ crossorigin="anonymous"
67
+ referrerpolicy="no-referrer"
68
+ />
69
+
70
+ <!-- Style -->
71
+ <style>
72
+ .DCEReactKit-pop-in {
73
+ animation-name: DCEReactKit-pop-in;
74
+ animation-duration: 0.5s;
75
+ animation-iteration-count: 1;
76
+ animation-timing-function: ease-out;
77
+ animation-fill-mode: both;
78
+
79
+ transform-origin: center;
80
+ }
81
+
82
+ @keyframes DCEReactKit-pop-in {
83
+ 0% {
84
+ opacity: 0;
85
+ transform: scale(0.9);
86
+ }
87
+ 100% {
88
+ opacity: 1;
89
+ transform: scale(1);
90
+ }
91
+ }
92
+
93
+ .DCEReactKit-slide-in {
94
+ animation-name: DCEReactKit-slide-in;
95
+ animation-duration: 1s;
96
+ animation-iteration-count: 1;
97
+ animation-timing-function: ease-out;
98
+ animation-fill-mode: both;
99
+ animation-delay: 0.2s;
100
+ }
101
+
102
+ @keyframes DCEReactKit-slide-in {
103
+ 0% {
104
+ opacity: 0;
105
+ transform: translate(0, 0.3em);
106
+ }
107
+ 100% {
108
+ opacity: 1;
109
+ transform: translate(0, 0);
110
+ }
111
+ }
112
+ </style>
113
+ </head>
114
+
115
+ <!-- Body -->
116
+ <body class="bg-dark text-center pt-3 ps-3 pe-3">
117
+ <!-- Alert -->
118
+ <div
119
+ class="DCEReactKit-pop-in alert alert-warning d-inline-block"
120
+ style="width: 50em; max-width: 100%"
121
+ >
122
+ <!-- Title -->
123
+ <h2>
124
+ <i class="me-1 fa-solid fa-triangle-exclamation"></i>
125
+ ${title}
126
+ </h2>
127
+ <!-- Description -->
128
+ <div>
129
+ ${description}
130
+ </div>
131
+ </div>
132
+
133
+ <!-- Error Code -->
134
+ <div class="DCEReactKit-slide-in text-light">
135
+ <strong>
136
+ Error Code:
137
+ </strong>
138
+ ${code}
139
+ </div>
140
+ </body>
141
+ `;
142
+ };
143
+
144
+ export default genErrorPage;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Generate a static info page
3
+ * @author Gabe Abrams
4
+ * @param opts object containing all arguments
5
+ * @param opts.title title of the info box
6
+ * @param opts.body a human-readable text body for the info alert
7
+ * @returns the HTML for the info page
8
+ */
9
+ const genInfoPage = (
10
+ opts: {
11
+ title: string,
12
+ body: string,
13
+ },
14
+ ): string => {
15
+ const {
16
+ title,
17
+ body,
18
+ } = opts;
19
+
20
+ return `
21
+ <head>
22
+ <!-- Metadata -->
23
+ <meta
24
+ name="viewport"
25
+ content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0"
26
+ >
27
+
28
+ <!-- Title -->
29
+ <title>${title}</title>
30
+
31
+ <!-- Bootstrap -->
32
+ <link
33
+ rel="stylesheet"
34
+ href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/css/bootstrap.min.css"
35
+ integrity="sha512-siwe/oXMhSjGCwLn+scraPOWrJxHlUgMBMZXdPe2Tnk3I0x3ESCoLz7WZ5NTH6SZrywMY+PB1cjyqJ5jAluCOg=="
36
+ crossorigin="anonymous"
37
+ referrerpolicy="no-referrer"
38
+ />
39
+ <script
40
+ src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.1/js/bootstrap.min.js"
41
+ integrity="sha512-vyRAVI0IEm6LI/fVSv/Wq/d0KUfrg3hJq2Qz5FlfER69sf3ZHlOrsLriNm49FxnpUGmhx+TaJKwJ+ByTLKT+Yg=="
42
+ crossorigin="anonymous"
43
+ referrerpolicy="no-referrer"
44
+ ></script>
45
+
46
+ <!-- FontAwesome -->
47
+ <link
48
+ rel="stylesheet"
49
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
50
+ integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
51
+ crossorigin="anonymous"
52
+ referrerpolicy="no-referrer"
53
+ />
54
+
55
+ <!-- Style -->
56
+ <style>
57
+ .DCEReactKit-pop-in {
58
+ animation-name: DCEReactKit-pop-in;
59
+ animation-duration: 0.5s;
60
+ animation-iteration-count: 1;
61
+ animation-timing-function: ease-out;
62
+ animation-fill-mode: both;
63
+
64
+ transform-origin: center;
65
+ }
66
+
67
+ @keyframes DCEReactKit-pop-in {
68
+ 0% {
69
+ opacity: 0;
70
+ transform: scale(0.9);
71
+ }
72
+ 100% {
73
+ opacity: 1;
74
+ transform: scale(1);
75
+ }
76
+ }
77
+ </style>
78
+ </head>
79
+
80
+ <!-- Body -->
81
+ <body class="bg-dark text-center pt-3 ps-3 pe-3">
82
+ <!-- Alert -->
83
+ <div
84
+ class="DCEReactKit-pop-in alert alert-info d-inline-block"
85
+ style="width: 50em; max-width: 100%"
86
+ >
87
+ <!-- Title -->
88
+ <h2>
89
+ <i class="me-1 fa-solid fa-circle-info"></i>
90
+ ${title}
91
+ </h2>
92
+ <!-- Body -->
93
+ <div>
94
+ ${body}
95
+ </div>
96
+ </div>
97
+ </body>
98
+ `;
99
+ };
100
+
101
+ export default genInfoPage;