mango-cms 0.3.21 → 0.3.22

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/cli.js CHANGED
@@ -55,6 +55,57 @@ async function ensureSrcExists(mangoCmsRoot) {
55
55
  }
56
56
  }
57
57
 
58
+ // Helper function to download and extract the versioned Mango UI (Nuxt admin) bundle.
59
+ // Mirrors ensureSrcExists: the UI ships hidden, pulled on demand from S3 and kept
60
+ // out of the npm tarball, exactly like the proprietary `src` core.
61
+ async function ensureUiExists(mangoCmsRoot) {
62
+ const uiDir = path.join(mangoCmsRoot, 'mango-ui');
63
+ if (fs.existsSync(path.join(uiDir, 'nuxt.config.ts')) || fs.existsSync(path.join(uiDir, 'package.json'))) {
64
+ return uiDir;
65
+ }
66
+
67
+ const packageJsonPath = path.join(mangoCmsRoot, 'package.json');
68
+ let version = '0.1.24';
69
+ try {
70
+ if (fs.existsSync(packageJsonPath)) {
71
+ version = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).version;
72
+ }
73
+ } catch (error) {
74
+ console.log('Error reading package.json:', error.message);
75
+ }
76
+
77
+ const zipFileName = `mango-ui-v${version}.zip`;
78
+ console.log(`Downloading Mango UI (version ${version})...`);
79
+
80
+ const response = await axios({
81
+ method: 'get',
82
+ url: `https://mango-cms.s3.amazonaws.com/${zipFileName}`,
83
+ responseType: 'arraybuffer'
84
+ });
85
+
86
+ const tempZipPath = path.join(mangoCmsRoot, zipFileName);
87
+ fs.writeFileSync(tempZipPath, response.data);
88
+
89
+ console.log('Extracting Mango UI...');
90
+ const zip = new AdmZip(tempZipPath);
91
+ zip.extractAllTo(mangoCmsRoot, true);
92
+
93
+ fs.unlinkSync(tempZipPath);
94
+ console.log(`Mango UI (version ${version}) installed successfully.`);
95
+ return uiDir;
96
+ }
97
+
98
+ // Resolve which Mango UI directory to use. A project-local "mango-ui" folder
99
+ // (created by `mango ui eject`) always wins so users can fully customize it;
100
+ // otherwise we fall back to the bundled/hidden copy inside the package.
101
+ function resolveUiDir(mangoCmsRoot, userProjectRoot) {
102
+ const ejected = path.join(userProjectRoot, 'mango-ui');
103
+ if (fs.existsSync(path.join(ejected, 'nuxt.config.ts')) || fs.existsSync(path.join(ejected, 'package.json'))) {
104
+ return { dir: ejected, ejected: true };
105
+ }
106
+ return { dir: path.join(mangoCmsRoot, 'mango-ui'), ejected: false };
107
+ }
108
+
58
109
  // Helper function to validate and prompt for license if needed
59
110
  async function ensureLicenseExists(configPath) {
60
111
  let settings = {};
@@ -205,6 +256,7 @@ program
205
256
  siteName: answers.projectName,
206
257
  siteDomain: `${projectSlug}.com`,
207
258
  mangoDomain: `api.${projectSlug}.com`,
259
+ uiDomain: `admin.${projectSlug}.com`,
208
260
  database: `${projectSlug}`,
209
261
  s3Bucket: `${projectSlug}`
210
262
  };
@@ -284,31 +336,93 @@ program
284
336
  }
285
337
  });
286
338
 
287
- // --- Mango UI Dev Command ---
339
+ // --- Mango UI Command ---
340
+ // The Mango UI is a Nuxt app. It reads the project's mango/config (collections +
341
+ // settings) at build time and talks to the backend API. By default it lives hidden
342
+ // inside the package; `mango ui eject` pops it into the project for customization.
288
343
  program
289
344
  .command('ui <action>')
290
- .description('Run Mango UI commands (e.g., dev, build, preview)')
345
+ .description('Run the Mango UI (dev, build, preview, start, eject)')
291
346
  .action(async (action) => {
292
- const allowed = ['dev', 'build', 'preview'];
347
+ const allowed = ['dev', 'build', 'preview', 'start', 'eject'];
293
348
  if (!allowed.includes(action)) {
294
- console.error(`Unknown UI action: ${action}`);
349
+ console.error(`Unknown UI action: ${action}. Use: ${allowed.join(', ')}`);
295
350
  process.exit(1);
296
351
  }
297
- const uiDir = path.join(__dirname, 'ui');
298
- const viteConfig = path.join(uiDir, 'vite.config.js');
299
- // Pass through the Mango config context via cwd and env
352
+
353
+ const mangoCmsRoot = path.resolve(__dirname);
354
+ const userProjectRoot = process.cwd();
355
+
356
+ // Read UI port from project settings (falls back to Nuxt default).
357
+ const settingsPath = path.join(userProjectRoot, 'mango/config/settings.json');
358
+ let settings = {};
300
359
  try {
301
- execSync(
302
- `npx vite ${action} --config "${viteConfig}"`,
303
- {
304
- cwd: uiDir,
305
- stdio: 'inherit',
306
- env: {
307
- ...process.env,
308
- // Optionally add more env vars here if needed
360
+ if (fs.existsSync(settingsPath)) {
361
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
362
+ }
363
+ } catch (error) {
364
+ console.log('Error reading settings file:', error.message);
365
+ }
366
+ const uiPort = settings.uiPort || 3001;
367
+
368
+ try {
369
+ // Make sure a UI bundle is available (downloads the hidden copy if needed).
370
+ await ensureUiExists(mangoCmsRoot);
371
+ const { dir: uiDir, ejected } = resolveUiDir(mangoCmsRoot, userProjectRoot);
372
+
373
+ if (action === 'eject') {
374
+ const dest = path.join(userProjectRoot, 'mango-ui');
375
+ if (ejected) {
376
+ console.log('⚠️ Mango UI is already ejected at ./mango-ui. Remove it first to re-eject.');
377
+ process.exit(1);
378
+ }
379
+ console.log('Ejecting Mango UI into ./mango-ui ...');
380
+ fs.copySync(uiDir, dest, {
381
+ filter: (src) => {
382
+ const base = path.basename(src);
383
+ return !['node_modules', '.git', '.nuxt', '.output', '.data', '.nitro', '.cache'].includes(base);
309
384
  }
385
+ });
386
+ console.log(`
387
+ ✨ Mango UI ejected to ./mango-ui
388
+ You now own the admin UI source and can customize it freely.
389
+
390
+ cd mango-ui
391
+ npm install
392
+
393
+ Then run it from your project root as usual:
394
+ mango ui dev
395
+ `);
396
+ return;
397
+ }
398
+
399
+ // Ensure UI dependencies are installed before running Nuxt.
400
+ if (!fs.existsSync(path.join(uiDir, 'node_modules'))) {
401
+ console.log('Installing Mango UI dependencies...');
402
+ execSync('npm install', { cwd: uiDir, stdio: 'inherit' });
403
+ }
404
+
405
+ // Map mango actions to Nuxt commands. `start` serves a production build.
406
+ const nuxtCmd = {
407
+ dev: `npx nuxt dev --port ${uiPort}`,
408
+ build: 'npx nuxt build',
409
+ preview: `npx nuxt preview --port ${uiPort}`,
410
+ start: `node .output/server/index.mjs`
411
+ }[action];
412
+
413
+ execSync(nuxtCmd, {
414
+ cwd: uiDir,
415
+ stdio: 'inherit',
416
+ env: {
417
+ ...process.env,
418
+ // Tell nuxt.config where the project (and its mango/config) lives,
419
+ // since the bundled UI sits inside node_modules, not the project.
420
+ MANGO_PROJECT_ROOT: userProjectRoot,
421
+ MANGO_UI_PORT: String(uiPort),
422
+ NUXT_PORT: String(uiPort),
423
+ PORT: String(uiPort)
310
424
  }
311
- );
425
+ });
312
426
  } catch (error) {
313
427
  console.error(`Error running Mango UI (${action}):`, error.message);
314
428
  process.exit(1);
@@ -406,6 +520,29 @@ function generateNginxConfig(settings, buildPath) {
406
520
  const domain = settings.mangoDomain;
407
521
  const port = settings.port || 3002;
408
522
  const frontPort = settings.frontPort || 3001;
523
+ const uiDomain = settings.uiDomain;
524
+ const uiPort = settings.uiPort || 3001;
525
+
526
+ // Reverse-proxy the Mango UI (Nuxt SSR server) on its own subdomain, when configured.
527
+ const uiServerBlock = uiDomain ? `
528
+ server {
529
+ server_name ${uiDomain};
530
+
531
+ client_max_body_size 50M;
532
+
533
+ location / {
534
+ proxy_http_version 1.1;
535
+ proxy_cache_bypass $http_upgrade;
536
+ proxy_set_header Upgrade $http_upgrade;
537
+ proxy_set_header Connection 'upgrade';
538
+ proxy_set_header Host $host;
539
+ proxy_set_header X-Real-IP $remote_addr;
540
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
541
+ proxy_set_header X-Forwarded-Proto $scheme;
542
+ proxy_pass http://localhost:${uiPort};
543
+ }
544
+ }
545
+ ` : '';
409
546
 
410
547
  return `server {
411
548
  root ${buildPath};
@@ -470,7 +607,7 @@ server {
470
607
  proxy_pass http://localhost:${frontPort};
471
608
  }
472
609
  }
473
- `;
610
+ ${uiServerBlock}`;
474
611
  }
475
612
 
476
613
  // Helper function to generate apache config
@@ -478,6 +615,27 @@ function generateApacheConfig(settings, buildPath) {
478
615
  const domain = settings.mangoDomain;
479
616
  const port = settings.port || 3002;
480
617
  const frontPort = settings.frontPort || 3001;
618
+ const uiDomain = settings.uiDomain;
619
+ const uiPort = settings.uiPort || 3001;
620
+
621
+ // Reverse-proxy the Mango UI (Nuxt SSR server) on its own subdomain, when configured.
622
+ const uiVirtualHost = uiDomain ? `
623
+ <VirtualHost *:80>
624
+ ServerName ${uiDomain}
625
+
626
+ ProxyRequests Off
627
+ ProxyPreserveHost On
628
+
629
+ ProxyPass / http://localhost:${uiPort}/
630
+ ProxyPassReverse / http://localhost:${uiPort}/
631
+
632
+ RequestHeader set X-Forwarded-Proto "https"
633
+ RequestHeader set X-Forwarded-Port "443"
634
+
635
+ ErrorLog \${APACHE_LOG_DIR}/${uiDomain}-error.log
636
+ CustomLog \${APACHE_LOG_DIR}/${uiDomain}-access.log combined
637
+ </VirtualHost>
638
+ ` : '';
481
639
 
482
640
  return `<VirtualHost *:80>
483
641
  ServerName ${domain}
@@ -529,7 +687,7 @@ function generateApacheConfig(settings, buildPath) {
529
687
  ErrorLog \${APACHE_LOG_DIR}/${settings.siteDomain}-error.log
530
688
  CustomLog \${APACHE_LOG_DIR}/${settings.siteDomain}-access.log combined
531
689
  </VirtualHost>
532
- `;
690
+ ${uiVirtualHost}`;
533
691
  }
534
692
 
535
693
  // Helper function to configure web server
@@ -632,6 +790,46 @@ async function managePM2Process(settings) {
632
790
  }
633
791
  }
634
792
 
793
+ // Helper function to build and (re)start the Mango UI (Nuxt) under PM2
794
+ async function manageUiPM2Process(settings, mangoCmsRoot, userProjectRoot) {
795
+ if (!settings.uiDomain) {
796
+ console.log('No uiDomain configured, skipping Mango UI deploy.');
797
+ return;
798
+ }
799
+
800
+ const { dir: uiDir, ejected } = resolveUiDir(mangoCmsRoot, userProjectRoot);
801
+ const uiPort = settings.uiPort || 3001;
802
+ const processName = `${settings.database.toLowerCase()}-ui`;
803
+
804
+ // Install deps + build the Nuxt app (reads this project's mango/config).
805
+ if (!fs.existsSync(path.join(uiDir, 'node_modules'))) {
806
+ console.log('Installing Mango UI dependencies...');
807
+ execSync('npm install', { cwd: uiDir, stdio: 'inherit' });
808
+ }
809
+ console.log(`Building Mango UI (${ejected ? 'ejected' : 'bundled'})...`);
810
+ execSync('npx nuxt build', {
811
+ cwd: uiDir,
812
+ stdio: 'inherit',
813
+ env: { ...process.env, MANGO_PROJECT_ROOT: userProjectRoot, MANGO_UI_PORT: String(uiPort) }
814
+ });
815
+
816
+ const serverEntry = path.join(uiDir, '.output/server/index.mjs');
817
+ const processListOutput = execSync('pm2 list', { encoding: 'utf8' });
818
+
819
+ if (processListOutput.includes(processName)) {
820
+ console.log(`Reloading PM2 process: ${processName}`);
821
+ execSync(`pm2 reload ${processName} --update-env`, {
822
+ env: { ...process.env, PORT: String(uiPort), NODE_ENV: 'production' }
823
+ });
824
+ } else {
825
+ console.log(`Starting new PM2 process: ${processName}`);
826
+ execSync(`pm2 start "${serverEntry}" --name ${processName}`, {
827
+ cwd: uiDir,
828
+ env: { ...process.env, PORT: String(uiPort), NODE_ENV: 'production' }
829
+ });
830
+ }
831
+ }
832
+
635
833
  program
636
834
  .command('deploy')
637
835
  .description('Build and deploy Mango CMS using PM2')
@@ -639,14 +837,20 @@ program
639
837
  try {
640
838
  const configPath = path.join(process.cwd(), 'mango/config/settings.json');
641
839
  const settings = JSON.parse(fs.readFileSync(configPath, 'utf8'));
840
+ const mangoCmsRoot = path.resolve(__dirname);
841
+ const userProjectRoot = process.cwd();
642
842
 
643
843
  // Run build command
644
844
  console.log('Building Mango CMS...');
645
845
  execSync('npm run mango build', { stdio: 'inherit' });
646
846
 
847
+ // Make sure a UI bundle is available for deploy.
848
+ await ensureUiExists(mangoCmsRoot);
849
+
647
850
  // Check if PM2 is installed
648
851
  if (await checkPM2()) {
649
852
  await managePM2Process(settings);
853
+ await manageUiPM2Process(settings, mangoCmsRoot, userProjectRoot);
650
854
 
651
855
  // Configure web server
652
856
  const buildPath = path.join(process.cwd(), 'build');
@@ -810,6 +1014,15 @@ program
810
1014
  fs.unlinkSync(tempZipPath);
811
1015
  console.log(`Source files (version ${version}) updated successfully.`);
812
1016
 
1017
+ // Refresh the bundled (hidden) Mango UI as well. The project-local
1018
+ // ejected copy, if any, is never touched here.
1019
+ const bundledUiDir = path.join(cmsPackagePath, 'mango-ui');
1020
+ if (fs.existsSync(bundledUiDir)) {
1021
+ console.log('Removing existing Mango UI...');
1022
+ fs.removeSync(bundledUiDir);
1023
+ }
1024
+ await ensureUiExists(cmsPackagePath);
1025
+
813
1026
  } catch (error) {
814
1027
  console.error('Error updating Mango CMS source files:', error.message);
815
1028
  process.exit(1);
@@ -848,6 +1061,7 @@ const proxyCmd = program
848
1061
  const slug = siteName.toLowerCase().replace(/[^a-z0-9]/g, '');
849
1062
  const frontDomain = `${slug}.mango`;
850
1063
  const apiDomain = `api.${slug}.mango`;
1064
+ const uiDomain = `admin.${slug}.mango`;
851
1065
 
852
1066
  // Kill existing proxy if running
853
1067
  if (fs.existsSync(pidPath)) {
@@ -859,10 +1073,11 @@ const proxyCmd = program
859
1073
  }
860
1074
 
861
1075
  // Add /etc/hosts entries
862
- addHosts([frontDomain, apiDomain]);
1076
+ addHosts([frontDomain, apiDomain, uiDomain]);
863
1077
 
864
1078
  const frontendPort = settings.frontPort || 5173;
865
1079
  const backendPort = settings.port || 3002;
1080
+ const uiPort = settings.uiPort || 3001;
866
1081
 
867
1082
  // Start the proxy server as a detached background process
868
1083
  const proxyScript = path.join(__dirname, 'lib/dev-proxy.js');
@@ -871,7 +1086,7 @@ const proxyCmd = program
871
1086
  // Use shell to redirect output to a log file and background the process
872
1087
  // This keeps sudo in the current TTY so the password prompt works
873
1088
  execSync(
874
- `sudo node "${proxyScript}" ${frontDomain} ${apiDomain} ${frontendPort} ${backendPort} "${pidPath}" > "${logPath}" 2>&1 &`,
1089
+ `sudo node "${proxyScript}" ${frontDomain} ${apiDomain} ${frontendPort} ${backendPort} "${pidPath}" ${uiDomain} ${uiPort} > "${logPath}" 2>&1 &`,
875
1090
  { stdio: 'inherit', shell: true }
876
1091
  );
877
1092
 
@@ -900,12 +1115,13 @@ const proxyCmd = program
900
1115
  console.log(`
901
1116
  Mango Dev Proxy started.
902
1117
 
903
- http://${frontDomain} -> localhost:${frontendPort} (frontend)
904
- http://${apiDomain} -> localhost:${backendPort} (backend)
1118
+ http://${frontDomain} -> localhost:${frontendPort} (frontend)
1119
+ http://${apiDomain} -> localhost:${backendPort} (backend)
1120
+ http://${uiDomain} -> localhost:${uiPort} (admin ui)
905
1121
 
906
1122
  Start your dev servers with proxy support:
907
- mango start (backend on port ${backendPort})
908
- VITE_DEV_PROXY=${apiDomain} mango ui dev (frontend on port ${frontendPort})
1123
+ mango start (backend on port ${backendPort})
1124
+ mango ui dev (admin ui on port ${uiPort})
909
1125
 
910
1126
  Proxy log: ${logPath}
911
1127
  To stop: mango proxy stop
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "port": 6646,
3
3
  "frontPort": 6645,
4
+ "uiPort": 6644,
4
5
  "siteName": "Example",
5
6
  "siteDomain": "example.com",
6
7
  "mangoDomain": "api.example.com",
8
+ "uiDomain": "admin.example.com",
7
9
  "serverIp": null,
8
10
 
9
- "mangoThreads": 2,
11
+ "mangoThreads": 1,
10
12
  "maxPoolSize": 10,
11
13
  "minPoolSize": 0,
12
14
  "maxIdleTimeMS": 60000,
@@ -24,7 +24,7 @@
24
24
  "dayjs": "^1.10.7",
25
25
  "express": "^4.18.1",
26
26
  "google-maps": "^4.3.3",
27
- "mango-cms": "^0.3.21",
27
+ "mango-cms": "^0.3.22",
28
28
  "mapbox-gl": "^2.7.0",
29
29
  "vite": "^6.2.2",
30
30
  "vue": "^3.2.37",
@@ -34,6 +34,7 @@
34
34
  "vue3-clipboard": "^1.0.0",
35
35
  "vue3-touch-events": "^4.1.0",
36
36
  "vuedraggable": "^4.1.0",
37
+ "sweetalert2": "^11.26.24",
37
38
  "socket.io-client": "^4.8.1"
38
39
  },
39
40
  "devDependencies": {
@@ -1,40 +1,59 @@
1
1
  import { ref } from 'vue'
2
2
  import { mango } from 'mango-cms'
3
3
 
4
- const getUser = async (user) => {
5
-
6
- user = user || { value: {} }
7
-
8
- if (window.user?.id) {
9
-
10
- console.log('got it!')
11
- user.value = window.user
4
+ function setCookie(cname, cvalue) {
5
+ var d = new Date()
6
+ d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000)
7
+ var expires = 'expires=' + d.toUTCString()
8
+ document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'
9
+ }
12
10
 
13
- /*
11
+ const getUser = async (user) => {
12
+ user = user || { value: {} }
13
+
14
+ // Persist token from URL query param
15
+ let params = new URLSearchParams(window.location.search)
16
+ let tokenParam = params.get('token')
17
+ if (tokenParam) {
18
+ setCookie('Authorization', tokenParam)
19
+ window.localStorage.setItem('token', tokenParam)
20
+ window.localStorage.setItem('user', tokenParam.split(':')[1])
21
+
22
+ params.delete('token')
23
+ let newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : '') + window.location.hash
24
+ window.history.replaceState({}, '', newUrl)
25
+ }
26
+
27
+ if (window.user?.id) {
28
+ console.log('got it!')
29
+ user.value = window.user
30
+
31
+ window.localStorage.setItem('token', `${window.user.password.hash}:${window.user.id}`)
32
+ window.localStorage.setItem('user', window.user.id)
33
+ window.localStorage.setItem('email', window.user.email)
34
+
35
+ /*
14
36
  Gotta clear this out so next time
15
37
  we try to call getUser we don't get
16
38
  stale data. ;)
17
39
  */
18
- window.user = null
19
- return user.value
20
- }
21
-
22
- let userId = window.localStorage.getItem('user')
40
+ window.user = null
41
+ return user.value
42
+ }
23
43
 
24
- if (!userId) return {}
44
+ let userId = window.localStorage.getItem('user')
25
45
 
26
- user.value = await mango.member(userId, { depthLimit: 0 }).then(r => user.value = r)
46
+ if (!userId) return {}
27
47
 
28
- return user.value
48
+ user.value = await mango.member(userId, { depthLimit: 0 }).then((r) => (user.value = r))
29
49
 
50
+ return user.value
30
51
  }
31
52
 
32
53
  const initUser = () => {
33
-
34
- const user = ref({})
35
- getUser(user)
36
- return user
37
-
54
+ const user = ref({})
55
+ getUser(user)
56
+ return user
38
57
  }
39
58
 
40
59
  export { initUser, getUser }
@@ -21,6 +21,9 @@ if (!fs.existsSync(configPath)) {
21
21
 
22
22
  const collectionsPath = path.resolve(configPath, 'config/.collections.json')
23
23
  const endpointsPath = path.resolve(configPath, 'config/.endpoints.json')
24
+ const settingsPath = path.resolve(configPath, 'config/settings.json')
25
+
26
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'))
24
27
 
25
28
  // https://vitejs.dev/config/
26
29
  export default defineConfig({
@@ -56,6 +59,7 @@ export default defineConfig({
56
59
  },
57
60
  ],
58
61
  server: {
62
+ port: settings.frontPort || 5173,
59
63
  host: '0.0.0.0',
60
64
  allowedHosts: ['.mango'],
61
65
  // When running behind mango proxy, configure HMR to use the proxy domain
package/lib/dev-proxy.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * Runs on port 80, routes requests by hostname to the correct local dev server.
7
7
  * Supports WebSocket upgrade for Vite HMR and Socket.io.
8
8
  *
9
- * Usage: node lib/dev-proxy.js <frontDomain> <apiDomain> <frontPort> <backendPort> [pidPath]
9
+ * Usage: node lib/dev-proxy.js <frontDomain> <apiDomain> <frontPort> <backendPort> [pidPath] [uiDomain] [uiPort]
10
10
  */
11
11
 
12
12
  import http from 'http';
@@ -18,9 +18,11 @@ const apiDomain = process.argv[3];
18
18
  const frontendPort = parseInt(process.argv[4], 10);
19
19
  const backendPort = parseInt(process.argv[5], 10);
20
20
  const pidPath = process.argv[6];
21
+ const uiDomain = process.argv[7];
22
+ const uiPort = process.argv[8] ? parseInt(process.argv[8], 10) : null;
21
23
 
22
24
  if (!frontDomain || !apiDomain || !frontendPort || !backendPort) {
23
- console.error('Usage: node lib/dev-proxy.js <frontDomain> <apiDomain> <frontPort> <backendPort> [pidPath]');
25
+ console.error('Usage: node lib/dev-proxy.js <frontDomain> <apiDomain> <frontPort> <backendPort> [pidPath] [uiDomain] [uiPort]');
24
26
  process.exit(1);
25
27
  }
26
28
 
@@ -45,6 +47,9 @@ function getTarget(host) {
45
47
  if (hostname === frontDomain) {
46
48
  return `http://127.0.0.1:${frontendPort}`;
47
49
  }
50
+ if (uiDomain && uiPort && hostname === uiDomain) {
51
+ return `http://127.0.0.1:${uiPort}`;
52
+ }
48
53
  return null;
49
54
  }
50
55
 
@@ -71,6 +76,9 @@ server.listen(80, '0.0.0.0', () => {
71
76
  console.log('Mango Dev Proxy running on port 80');
72
77
  console.log(` ${frontDomain} -> http://127.0.0.1:${frontendPort} (frontend)`);
73
78
  console.log(` ${apiDomain} -> http://127.0.0.1:${backendPort} (backend)`);
79
+ if (uiDomain && uiPort) {
80
+ console.log(` ${uiDomain} -> http://127.0.0.1:${uiPort} (ui)`);
81
+ }
74
82
  });
75
83
 
76
84
  // Write PID for cleanup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mango-cms",
3
- "version": "0.3.21",
3
+ "version": "0.3.22",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "exports": {
@@ -1,108 +0,0 @@
1
- <template>
2
- <div
3
- ref="modal"
4
- @click.prevent.stop="close"
5
- tabindex="-1"
6
- class="fixed w-full h-screen bg-gray-800/50 dark:bg-black/60 backdrop-blur-sm md:backdrop-blur-md opacity-0 transition-all duration-500 flex items-start sm:items-center justify-center z-[100] inset-0 !m-0 overscroll-none cursor-default"
7
- :class="{ '!opacity-100': fadeIn }"
8
- aria-modal="true"
9
- v-show="active"
10
- role="dialog"
11
- >
12
- <button @click.prevent.stop="close" class="absolute top-2 right-3"><i class="fa fa-times md:text-4xl" /></button>
13
- <div
14
- ref="modalContent"
15
- @click.stop
16
- class="shadow-card rounded w-full p-4 md:p-8 m-2 dark:bg-gray-800 bg-white relative border dark:border-gray-700 space-y-4 md:space-y-8 max-h-[75vh]"
17
- :class="[dialogClasses, maxWidth, allowOverflow ? 'overflow-visible' : 'overflow-y-scroll']"
18
- >
19
- <slot :close="close" />
20
- </div>
21
- </div>
22
- <!-- max-w-md max-w-lg max-w-xl max-w-sm -->
23
- </template>
24
-
25
- <script>
26
- export default {
27
- props: {
28
- dialogClasses: { type: String, default: '' },
29
- maxWidth: { default: 'max-w-md' },
30
- active: { default: true },
31
- allowOverflow: { type: Boolean, default: false },
32
- },
33
- data() {
34
- return {
35
- fadeIn: false,
36
- }
37
- },
38
- watch: {
39
- active: {
40
- handler() {
41
- if (this.active) this.freeze()
42
- else this.thaw()
43
- },
44
- immediate: true,
45
- },
46
- },
47
- beforeDestroy() {
48
- this.thaw()
49
- },
50
- unmounted() {
51
- this.thaw()
52
- },
53
- methods: {
54
- freeze() {
55
- this.$nextTick(() => {
56
- this.$refs.modalContent.focus()
57
- this.fadeIn = true
58
- document.body.style.overflow = 'hidden'
59
- document.getElementById('app').setAttribute('aria-hidden', 'true')
60
- this.$refs?.modal?.setAttribute('aria-modal', 'true')
61
- this.attachListeners()
62
- })
63
- },
64
- thaw() {
65
- document.body.style.overflow = ''
66
- document.getElementById('app').setAttribute('aria-hidden', 'false')
67
- this.$refs?.modal?.removeAttribute('aria-modal')
68
- this.removeListeners()
69
- },
70
- close() {
71
- this.fadeIn = false
72
- this.thaw()
73
- setTimeout(() => {
74
- this.$emit('hide')
75
- }, 500)
76
- },
77
- attachListeners() {
78
- window.addEventListener('keydown', this.handleKeydown)
79
- },
80
- removeListeners() {
81
- window.removeEventListener('keydown', this.handleKeydown)
82
- },
83
- handleKeydown(e) {
84
- if (e.key === 'Tab') {
85
- this.manageFocus(e)
86
- } else if (e.key === 'Escape') {
87
- this.close()
88
- }
89
- },
90
- manageFocus(e) {
91
- let focusable = Array.from(this.$refs.modal.querySelectorAll('button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])'))
92
- focusable = focusable.filter((el) => window.getComputedStyle(el).display !== 'none')
93
- console.log('focusable', focusable)
94
-
95
- if (!focusable.includes(document.activeElement)) {
96
- e.preventDefault()
97
- focusable[0].focus()
98
- } else if (e.shiftKey && document.activeElement === focusable[0]) {
99
- e.preventDefault()
100
- focusable[focusable.length - 1].focus()
101
- } else if (document.activeElement === focusable[focusable.length - 1]) {
102
- e.preventDefault()
103
- focusable[0].focus()
104
- }
105
- },
106
- },
107
- }
108
- </script>
@@ -1,17 +0,0 @@
1
- <template>
2
- <div
3
- class="flex justify-center items-center border-transparent"
4
- :class="{'w-full h-screen': !small}"
5
- >
6
- <div class="rounded-full border-4 animate-spin w-full h-full max-w-32 max-h-32 border-inherit" :class="color"/>
7
- </div>
8
- </template>
9
-
10
- <script>
11
- export default {
12
- props: {
13
- small: {type: Boolean, default: false},
14
- color: {default: 'border-t-blue-500'}
15
- },
16
- }
17
- </script>