@underpostnet/underpost 2.7.83 → 2.7.92
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/.github/workflows/ghpkg.yml +41 -1
- package/.github/workflows/pwa-microservices-template.page.yml +54 -0
- package/.vscode/settings.json +7 -0
- package/CHANGELOG.md +64 -16
- package/bin/cron.js +47 -0
- package/bin/db.js +60 -7
- package/bin/deploy.js +358 -26
- package/bin/file.js +18 -1
- package/bin/hwt.js +59 -0
- package/bin/index.js +1 -1
- package/bin/util.js +31 -1
- package/conf.js +46 -8
- package/docker-compose.yml +1 -1
- package/package.json +4 -4
- package/src/api/core/core.router.js +9 -9
- package/src/api/core/core.service.js +12 -4
- package/src/api/default/default.service.js +4 -4
- package/src/api/file/file.service.js +3 -3
- package/src/api/user/user.service.js +10 -8
- package/src/client/components/core/404.js +20 -0
- package/src/client/components/core/500.js +20 -0
- package/src/client/{ssr/common → components/core}/Alert.js +13 -11
- package/src/client/components/core/CommonJs.js +3 -0
- package/src/client/components/core/CssCore.js +30 -3
- package/src/client/components/core/Docs.js +110 -10
- package/src/client/components/core/LoadingAnimation.js +4 -2
- package/src/client/components/core/Modal.js +223 -22
- package/src/client/components/core/Panel.js +1 -1
- package/src/client/components/core/PanelForm.js +2 -1
- package/src/client/components/core/Responsive.js +34 -5
- package/src/client/components/core/RichText.js +4 -2
- package/src/client/components/core/Translate.js +21 -5
- package/src/client/components/core/VanillaJs.js +2 -1
- package/src/client/components/core/WebComponent.js +44 -0
- package/src/client/components/core/Worker.js +15 -18
- package/src/client/components/default/MenuDefault.js +68 -0
- package/src/client/components/default/RoutesDefault.js +2 -0
- package/src/client/public/default/plantuml/client-conf.svg +1 -1
- package/src/client/public/default/plantuml/client-schema.svg +1 -1
- package/src/client/public/default/plantuml/cron-conf.svg +1 -1
- package/src/client/public/default/plantuml/cron-schema.svg +1 -1
- package/src/client/public/default/plantuml/server-conf.svg +1 -1
- package/src/client/public/default/plantuml/server-schema.svg +1 -1
- package/src/client/public/default/plantuml/ssr-conf.svg +1 -1
- package/src/client/public/default/plantuml/ssr-schema.svg +1 -1
- package/src/client/public/default/site.webmanifest +69 -0
- package/src/client/ssr/Render.js +1 -6
- package/src/client/ssr/{components/body → body}/CacheControl.js +1 -1
- package/src/client/ssr/head/Production.js +1 -0
- package/src/client/ssr/head/Pwa.js +146 -0
- package/src/client/ssr/head/Seo.js +14 -0
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +21 -0
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +17 -0
- package/src/client/ssr/offline/NoNetworkConnection.js +65 -0
- package/src/client/ssr/pages/Test.js +196 -0
- package/src/client/ssr/pages/maintenance.js +14 -0
- package/src/client/ssr/pages/offline.js +21 -0
- package/src/client/sw/default.sw.js +44 -165
- package/src/db/DataBaseProvider.js +12 -1
- package/src/db/mongo/MongooseDB.js +0 -1
- package/src/mailer/EmailRender.js +2 -4
- package/src/mailer/MailerProvider.js +4 -1
- package/src/runtime/lampp/Lampp.js +9 -9
- package/src/server/backup.js +82 -70
- package/src/server/client-build.js +133 -155
- package/src/server/client-formatted.js +2 -4
- package/src/server/conf.js +114 -23
- package/src/server/crypto.js +91 -0
- package/src/server/dns.js +48 -16
- package/src/server/network.js +94 -7
- package/src/server/proxy.js +26 -28
- package/src/server/runtime.js +42 -12
- package/src/server/ssl.js +2 -2
- package/src/client/ssr/common/SsrCore.js +0 -91
- package/src/client/ssr/common/Translate.js +0 -26
- package/src/client/ssr/common/Worker.js +0 -28
- package/src/client/ssr/components/head/PwaDefault.js +0 -60
- package/src/client/ssr/offline/default.index.js +0 -31
- package/src/cron.js +0 -30
- package/src/server/cron.js +0 -35
- /package/src/client/ssr/{components/body → body}/DefaultSplashScreen.js +0 -0
- /package/src/client/ssr/{components/email → email}/DefaultRecoverEmail.js +0 -0
- /package/src/client/ssr/{components/email → email}/DefaultVerifyEmail.js +0 -0
- /package/src/client/ssr/{components/head → head}/Css.js +0 -0
- /package/src/client/ssr/{components/head → head}/DefaultScripts.js +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { newInstance } from './CommonJs.js';
|
|
2
|
+
import { loggerFactory } from './Logger.js';
|
|
2
3
|
import { getResponsiveData } from './VanillaJs.js';
|
|
3
4
|
|
|
5
|
+
const logger = loggerFactory(import.meta);
|
|
6
|
+
|
|
4
7
|
const Responsive = {
|
|
5
8
|
Data: {},
|
|
6
9
|
Event: {},
|
|
@@ -18,9 +21,9 @@ const Responsive = {
|
|
|
18
21
|
ResponsiveDataAmplitude.height = ResponsiveDataAmplitude.height * dimAmplitude;
|
|
19
22
|
return ResponsiveDataAmplitude;
|
|
20
23
|
},
|
|
21
|
-
resizeCallback: function () {
|
|
24
|
+
resizeCallback: function (force) {
|
|
22
25
|
const Data = getResponsiveData();
|
|
23
|
-
if (Data.minValue !== Responsive.Data.minValue || Data.maxValue !== Responsive.Data.maxValue) {
|
|
26
|
+
if (force === true || Data.minValue !== Responsive.Data.minValue || Data.maxValue !== Responsive.Data.maxValue) {
|
|
24
27
|
Responsive.Data = Data;
|
|
25
28
|
Responsive.triggerEvents();
|
|
26
29
|
}
|
|
@@ -28,13 +31,13 @@ const Responsive = {
|
|
|
28
31
|
resize: 0,
|
|
29
32
|
Init: async function () {
|
|
30
33
|
Responsive.resizeCallback();
|
|
31
|
-
window.onresize = () => {
|
|
34
|
+
window.onresize = (e, force) => {
|
|
32
35
|
Responsive.resize++;
|
|
33
36
|
const resize = Responsive.resize;
|
|
34
|
-
Responsive.resizeCallback();
|
|
37
|
+
Responsive.resizeCallback(force);
|
|
35
38
|
setTimeout(() => {
|
|
36
39
|
if (resize === Responsive.resize) {
|
|
37
|
-
Responsive.resizeCallback();
|
|
40
|
+
Responsive.resizeCallback(force);
|
|
38
41
|
Responsive.resize = 0;
|
|
39
42
|
for (const event of Object.keys(Responsive.DelayEvent)) Responsive.DelayEvent[event]();
|
|
40
43
|
}
|
|
@@ -43,11 +46,37 @@ const Responsive = {
|
|
|
43
46
|
// alternative option
|
|
44
47
|
// this.Observer = new ResizeObserver(this.resizeCallback);
|
|
45
48
|
// this.Observer.observe(document.documentElement);
|
|
49
|
+
screen.orientation.addEventListener('change', (event) => {
|
|
50
|
+
const type = event.target.type; // landscape-primary | portrait-primary
|
|
51
|
+
const angle = event.target.angle; // 90 degrees.
|
|
52
|
+
logger.info(`ScreenOrientation change: ${type}, ${angle} degrees.`);
|
|
53
|
+
setTimeout(() => window.onresize({}, true));
|
|
54
|
+
Responsive.triggerEventsOrientation();
|
|
55
|
+
});
|
|
56
|
+
Responsive.matchMediaOrientationInstance = matchMedia('screen and (orientation:portrait)');
|
|
57
|
+
|
|
58
|
+
Responsive.matchMediaOrientationInstance.onchange = (e) => {
|
|
59
|
+
console.log('orientation change', Responsive.matchMediaOrientationInstance.matches ? 'portrait' : 'landscape');
|
|
60
|
+
// though beware square will be marked as landscape here,
|
|
61
|
+
// if you want to handle this special case
|
|
62
|
+
// create an other mediaquery (orientation:landscape)
|
|
63
|
+
setTimeout(() => window.onresize({}, true));
|
|
64
|
+
Responsive.triggerEventsOrientation();
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
triggerEventsOrientation: function () {
|
|
68
|
+
for (const event of Object.keys(this.orientationEvent)) this.orientationEvent[event]();
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
window.onresize();
|
|
71
|
+
for (const event of Object.keys(this.orientationDelayEvent)) this.orientationDelayEvent[event]();
|
|
72
|
+
}, 1500);
|
|
46
73
|
},
|
|
47
74
|
triggerEvents: function (keyEvent) {
|
|
48
75
|
if (keyEvent) return this.Event[keyEvent]();
|
|
49
76
|
return Object.keys(this.Event).map((key) => this.Event[key]());
|
|
50
77
|
},
|
|
78
|
+
orientationEvent: {},
|
|
79
|
+
orientationDelayEvent: {},
|
|
51
80
|
};
|
|
52
81
|
|
|
53
82
|
export { Responsive };
|
|
@@ -20,14 +20,16 @@ const RichText = {
|
|
|
20
20
|
s(`.${options.parentIdModal}`).style.top = '0px';
|
|
21
21
|
s(`.${options.parentIdModal}`).style.height = `${window.innerHeight}px`;
|
|
22
22
|
}
|
|
23
|
-
Modal.cleanUI();
|
|
23
|
+
// Modal.cleanUI();
|
|
24
|
+
if (s(`.slide-menu-top-bar`)) s(`.slide-menu-top-bar`).classList.add('hide');
|
|
24
25
|
} else {
|
|
25
26
|
if (options.parentIdModal) {
|
|
26
27
|
s(`.btn-bar-modal-container-${options.parentIdModal}`).classList.remove('hide');
|
|
27
28
|
s(`.${options.parentIdModal}`).style.top = top;
|
|
28
29
|
s(`.${options.parentIdModal}`).style.height = height;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
if (s(`.slide-menu-top-bar`)) s(`.slide-menu-top-bar`).classList.add('remove');
|
|
32
|
+
// Modal.restoreUI();
|
|
31
33
|
}
|
|
32
34
|
},
|
|
33
35
|
});
|
|
@@ -66,11 +66,7 @@ const Translate = {
|
|
|
66
66
|
|
|
67
67
|
const TranslateCore = {
|
|
68
68
|
Init: async function () {
|
|
69
|
-
s('html').lang =
|
|
70
|
-
? localStorage.getItem('lang')
|
|
71
|
-
: getLang() && getLang().match('es')
|
|
72
|
-
? 'es'
|
|
73
|
-
: 'en';
|
|
69
|
+
s('html').lang = getLang();
|
|
74
70
|
Translate.Data = {
|
|
75
71
|
...Translate.Data,
|
|
76
72
|
isEmpty: {
|
|
@@ -252,6 +248,26 @@ const TranslateCore = {
|
|
|
252
248
|
en: 'Comments',
|
|
253
249
|
es: 'Comentarios',
|
|
254
250
|
},
|
|
251
|
+
['server-maintenance']: {
|
|
252
|
+
en: "The server is under maintenance <br> we'll be back soon.",
|
|
253
|
+
es: 'El servidor está en mantenimiento <br> volveremos pronto.',
|
|
254
|
+
},
|
|
255
|
+
['no-internet-connection']: {
|
|
256
|
+
en: 'No internet connection <br> verify your network',
|
|
257
|
+
es: 'Sin conexión a internet <br> verifica tu red',
|
|
258
|
+
},
|
|
259
|
+
['page-not-found']: {
|
|
260
|
+
en: 'Page not found',
|
|
261
|
+
es: 'Página no encontrada',
|
|
262
|
+
},
|
|
263
|
+
['page-broken']: {
|
|
264
|
+
es: 'Algo salio mal',
|
|
265
|
+
en: 'Something went wrong',
|
|
266
|
+
},
|
|
267
|
+
['back']: {
|
|
268
|
+
en: 'Back to <br> homepage',
|
|
269
|
+
es: 'Volver a <br> la pagina principal',
|
|
270
|
+
},
|
|
255
271
|
},
|
|
256
272
|
};
|
|
257
273
|
Translate.Data['warning-upload-no-selects-file'] = {
|
|
@@ -428,7 +428,8 @@ const isDevInstance = () => location.origin.match('localhost') && location.port;
|
|
|
428
428
|
|
|
429
429
|
const getDataFromInputFile = async (file) => Array.from(new Uint8Array(await file.arrayBuffer()));
|
|
430
430
|
|
|
431
|
-
const getLang = () =>
|
|
431
|
+
const getLang = () =>
|
|
432
|
+
(localStorage.getItem('lang') || navigator.language || navigator.userLanguage || s('html').lang).slice(0, 2);
|
|
432
433
|
|
|
433
434
|
export {
|
|
434
435
|
s,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// https://javascript.info/
|
|
2
|
+
// https://javascript.info/web-components
|
|
3
|
+
|
|
4
|
+
class MyElement extends HTMLElement {
|
|
5
|
+
constructor() {
|
|
6
|
+
super();
|
|
7
|
+
// element created
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
connectedCallback() {
|
|
11
|
+
// browser calls this method when the element is added to the document
|
|
12
|
+
// (can be called many times if an element is repeatedly added/removed)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
disconnectedCallback() {
|
|
16
|
+
// browser calls this method when the element is removed from the document
|
|
17
|
+
// (can be called many times if an element is repeatedly added/removed)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static get observedAttributes() {
|
|
21
|
+
return [
|
|
22
|
+
/* array of attribute names to monitor for changes */
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
27
|
+
// called when one of attributes listed above is modified
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
adoptedCallback() {
|
|
31
|
+
// called when the element is moved to a new document
|
|
32
|
+
// (happens in document.adoptNode, very rarely used)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// there can be other element methods and properties
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
customElements.define('my-element', MyElement);
|
|
39
|
+
|
|
40
|
+
// Now for any HTML elements with tag <my-element>,
|
|
41
|
+
// an instance of MyElement is created, and the aforementioned
|
|
42
|
+
// methods are called.
|
|
43
|
+
|
|
44
|
+
export { MyElement };
|
|
@@ -13,6 +13,15 @@ const Worker = {
|
|
|
13
13
|
devMode: () => location.origin.match('localhost') || location.origin.match('127.0.0.1'),
|
|
14
14
|
instance: async function ({ router, render }) {
|
|
15
15
|
logger.warn('Init');
|
|
16
|
+
window.ononline = async () => {
|
|
17
|
+
logger.warn('ononline');
|
|
18
|
+
};
|
|
19
|
+
window.onoffline = async () => {
|
|
20
|
+
logger.warn('onoffline');
|
|
21
|
+
};
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
if ('onLine' in navigator && navigator.onLine) window.ononline();
|
|
24
|
+
});
|
|
16
25
|
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
17
26
|
logger.info('The controller of current browsing context has changed.');
|
|
18
27
|
});
|
|
@@ -48,11 +57,9 @@ const Worker = {
|
|
|
48
57
|
// channel.postMessage({ title: 'Hello from Client broadcast message' });
|
|
49
58
|
// channel.close();
|
|
50
59
|
});
|
|
60
|
+
this.RouterInstance = router();
|
|
51
61
|
const isInstall = await this.status();
|
|
52
62
|
if (!isInstall) await this.install();
|
|
53
|
-
else await this.update();
|
|
54
|
-
// else if (location.hostname === 'localhost') await this.update();
|
|
55
|
-
this.RouterInstance = router();
|
|
56
63
|
await render();
|
|
57
64
|
LoadRouter(this.RouterInstance);
|
|
58
65
|
LoadingAnimation.removeSplashScreen();
|
|
@@ -61,9 +68,9 @@ const Worker = {
|
|
|
61
68
|
// Dev mode
|
|
62
69
|
|
|
63
70
|
window.addEventListener('visibilitychange', (event) => {
|
|
64
|
-
if (document.visibilityState === 'visible') {
|
|
65
|
-
|
|
66
|
-
}
|
|
71
|
+
// if (document.visibilityState === 'visible') {
|
|
72
|
+
// Worker.reload(delayLiveReload);
|
|
73
|
+
// }
|
|
67
74
|
});
|
|
68
75
|
window.addEventListener('focus', function () {
|
|
69
76
|
// Worker.reload(delayLiveReload);
|
|
@@ -90,21 +97,11 @@ const Worker = {
|
|
|
90
97
|
if (isInstall) {
|
|
91
98
|
const cacheNames = await caches.keys();
|
|
92
99
|
for (const cacheName of cacheNames) {
|
|
93
|
-
if (
|
|
94
|
-
cacheName.match('components/') ||
|
|
95
|
-
cacheName.match('services/') ||
|
|
96
|
-
cacheName.match('.index.js') ||
|
|
97
|
-
cacheName.match('offline.')
|
|
98
|
-
) {
|
|
100
|
+
if (cacheName.match('components/') || cacheName.match('services/') || cacheName.match('.index.js')) {
|
|
99
101
|
await caches.delete(cacheName);
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
await this.updateServiceWorker();
|
|
103
|
-
try {
|
|
104
|
-
await fetch('/offline.html');
|
|
105
|
-
} catch (error) {
|
|
106
|
-
logger.error('error');
|
|
107
|
-
}
|
|
108
105
|
}
|
|
109
106
|
},
|
|
110
107
|
updateServiceWorker: async function () {},
|
|
@@ -143,7 +140,7 @@ const Worker = {
|
|
|
143
140
|
return new Promise((resolve, reject) => {
|
|
144
141
|
if ('serviceWorker' in navigator) {
|
|
145
142
|
navigator.serviceWorker
|
|
146
|
-
.register(
|
|
143
|
+
.register(`${getProxyPath()}sw.js`, {
|
|
147
144
|
// scope: getProxyPath(),
|
|
148
145
|
// scope: '/',
|
|
149
146
|
type: 'module',
|
|
@@ -17,6 +17,8 @@ import { Badge } from '../core/Badge.js';
|
|
|
17
17
|
import { Docs } from '../core/Docs.js';
|
|
18
18
|
import { Recover } from '../core/Recover.js';
|
|
19
19
|
import { DefaultManagement } from '../../services/default/default.management.js';
|
|
20
|
+
import { Page500 } from '../core/500.js';
|
|
21
|
+
import { Page404 } from '../core/404.js';
|
|
20
22
|
|
|
21
23
|
const MenuDefault = {
|
|
22
24
|
Data: {},
|
|
@@ -136,6 +138,28 @@ const MenuDefault = {
|
|
|
136
138
|
handleContainerClass: 'handle-btn-container',
|
|
137
139
|
tooltipHtml: await Badge.Render(buildBadgeToolTipMenuOption('default-management')),
|
|
138
140
|
})}
|
|
141
|
+
${await BtnIcon.Render({
|
|
142
|
+
class: 'in wfa main-btn-menu main-btn-404 hide',
|
|
143
|
+
label: renderMenuLabel({
|
|
144
|
+
icon: html`<i class="fa-solid fa-triangle-exclamation"></i>`,
|
|
145
|
+
text: html`<span class="menu-label-text">${Translate.Render('404')}</span>`,
|
|
146
|
+
}),
|
|
147
|
+
attrs: `data-id="404"`,
|
|
148
|
+
tabHref: `${getProxyPath()}404`,
|
|
149
|
+
handleContainerClass: 'handle-btn-container',
|
|
150
|
+
tooltipHtml: await Badge.Render(buildBadgeToolTipMenuOption('404')),
|
|
151
|
+
})}
|
|
152
|
+
${await BtnIcon.Render({
|
|
153
|
+
class: 'in wfa main-btn-menu main-btn-500 hide',
|
|
154
|
+
label: renderMenuLabel({
|
|
155
|
+
icon: html`<i class="fa-solid fa-circle-exclamation"></i>`,
|
|
156
|
+
text: html`<span class="menu-label-text">${Translate.Render('500')}</span>`,
|
|
157
|
+
}),
|
|
158
|
+
attrs: `data-id="500"`,
|
|
159
|
+
tabHref: `${getProxyPath()}500`,
|
|
160
|
+
handleContainerClass: 'handle-btn-container',
|
|
161
|
+
tooltipHtml: await Badge.Render(buildBadgeToolTipMenuOption('500')),
|
|
162
|
+
})}
|
|
139
163
|
</div>
|
|
140
164
|
`,
|
|
141
165
|
barConfig: newInstance(barConfig),
|
|
@@ -383,6 +407,50 @@ const MenuDefault = {
|
|
|
383
407
|
heightBottomBar,
|
|
384
408
|
});
|
|
385
409
|
});
|
|
410
|
+
|
|
411
|
+
EventsUI.onClick(`.main-btn-404`, async () => {
|
|
412
|
+
const { barConfig } = await Themes[Css.currentTheme]();
|
|
413
|
+
await Modal.Render({
|
|
414
|
+
id: 'modal-404',
|
|
415
|
+
route: '404',
|
|
416
|
+
barConfig,
|
|
417
|
+
title: renderViewTitle({
|
|
418
|
+
icon: html`<i class="fa-solid fa-triangle-exclamation"></i>`,
|
|
419
|
+
text: Translate.Render('404'),
|
|
420
|
+
}),
|
|
421
|
+
html: async () => await Page404.Render({ idModal: 'modal-404' }),
|
|
422
|
+
handleType: 'bar',
|
|
423
|
+
maximize: true,
|
|
424
|
+
mode: 'view',
|
|
425
|
+
slideMenu: 'modal-menu',
|
|
426
|
+
RouterInstance,
|
|
427
|
+
heightTopBar,
|
|
428
|
+
heightBottomBar,
|
|
429
|
+
observer: true,
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
EventsUI.onClick(`.main-btn-500`, async () => {
|
|
434
|
+
const { barConfig } = await Themes[Css.currentTheme]();
|
|
435
|
+
await Modal.Render({
|
|
436
|
+
id: 'modal-500',
|
|
437
|
+
route: '500',
|
|
438
|
+
barConfig,
|
|
439
|
+
title: renderViewTitle({
|
|
440
|
+
icon: html`<i class="fa-solid fa-circle-exclamation"></i>`,
|
|
441
|
+
text: Translate.Render('500'),
|
|
442
|
+
}),
|
|
443
|
+
html: async () => await Page500.Render({ idModal: 'modal-500' }),
|
|
444
|
+
handleType: 'bar',
|
|
445
|
+
maximize: true,
|
|
446
|
+
mode: 'view',
|
|
447
|
+
slideMenu: 'modal-menu',
|
|
448
|
+
RouterInstance,
|
|
449
|
+
heightTopBar,
|
|
450
|
+
heightBottomBar,
|
|
451
|
+
observer: true,
|
|
452
|
+
});
|
|
453
|
+
});
|
|
386
454
|
},
|
|
387
455
|
};
|
|
388
456
|
|
|
@@ -36,6 +36,8 @@ const RoutesDefault = () => {
|
|
|
36
36
|
render: () => s(`.main-btn-default-management`).click(),
|
|
37
37
|
translateTitle: true,
|
|
38
38
|
},
|
|
39
|
+
'/404': { title: '404 Not Found', render: () => s(`.main-btn-404`).click() },
|
|
40
|
+
'/500': { title: '500 Server Error', render: () => s(`.main-btn-500`).click() },
|
|
39
41
|
};
|
|
40
42
|
};
|
|
41
43
|
|