mango-cms 0.3.21 → 0.3.23

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,99 @@ 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
- {
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);
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
+ // Pass the project root so the postinstall (nuxt prepare) can resolve
401
+ // this project's mango/config instead of failing inside node_modules.
402
+ if (!fs.existsSync(path.join(uiDir, 'node_modules'))) {
403
+ console.log('Installing Mango UI dependencies...');
404
+ execSync('npm install', {
304
405
  cwd: uiDir,
305
406
  stdio: 'inherit',
306
- env: {
307
- ...process.env,
308
- // Optionally add more env vars here if needed
309
- }
407
+ env: { ...process.env, MANGO_PROJECT_ROOT: userProjectRoot, MANGO_UI_PORT: String(uiPort) }
408
+ });
409
+ }
410
+
411
+ // Map mango actions to Nuxt commands. `start` serves a production build.
412
+ const nuxtCmd = {
413
+ dev: `npx nuxt dev --port ${uiPort}`,
414
+ build: 'npx nuxt build',
415
+ preview: `npx nuxt preview --port ${uiPort}`,
416
+ start: `node .output/server/index.mjs`
417
+ }[action];
418
+
419
+ execSync(nuxtCmd, {
420
+ cwd: uiDir,
421
+ stdio: 'inherit',
422
+ env: {
423
+ ...process.env,
424
+ // Tell nuxt.config where the project (and its mango/config) lives,
425
+ // since the bundled UI sits inside node_modules, not the project.
426
+ MANGO_PROJECT_ROOT: userProjectRoot,
427
+ MANGO_UI_PORT: String(uiPort),
428
+ NUXT_PORT: String(uiPort),
429
+ PORT: String(uiPort)
310
430
  }
311
- );
431
+ });
312
432
  } catch (error) {
313
433
  console.error(`Error running Mango UI (${action}):`, error.message);
314
434
  process.exit(1);
@@ -406,6 +526,29 @@ function generateNginxConfig(settings, buildPath) {
406
526
  const domain = settings.mangoDomain;
407
527
  const port = settings.port || 3002;
408
528
  const frontPort = settings.frontPort || 3001;
529
+ const uiDomain = settings.uiDomain;
530
+ const uiPort = settings.uiPort || 3001;
531
+
532
+ // Reverse-proxy the Mango UI (Nuxt SSR server) on its own subdomain, when configured.
533
+ const uiServerBlock = uiDomain ? `
534
+ server {
535
+ server_name ${uiDomain};
536
+
537
+ client_max_body_size 50M;
538
+
539
+ location / {
540
+ proxy_http_version 1.1;
541
+ proxy_cache_bypass $http_upgrade;
542
+ proxy_set_header Upgrade $http_upgrade;
543
+ proxy_set_header Connection 'upgrade';
544
+ proxy_set_header Host $host;
545
+ proxy_set_header X-Real-IP $remote_addr;
546
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
547
+ proxy_set_header X-Forwarded-Proto $scheme;
548
+ proxy_pass http://localhost:${uiPort};
549
+ }
550
+ }
551
+ ` : '';
409
552
 
410
553
  return `server {
411
554
  root ${buildPath};
@@ -470,7 +613,7 @@ server {
470
613
  proxy_pass http://localhost:${frontPort};
471
614
  }
472
615
  }
473
- `;
616
+ ${uiServerBlock}`;
474
617
  }
475
618
 
476
619
  // Helper function to generate apache config
@@ -478,6 +621,27 @@ function generateApacheConfig(settings, buildPath) {
478
621
  const domain = settings.mangoDomain;
479
622
  const port = settings.port || 3002;
480
623
  const frontPort = settings.frontPort || 3001;
624
+ const uiDomain = settings.uiDomain;
625
+ const uiPort = settings.uiPort || 3001;
626
+
627
+ // Reverse-proxy the Mango UI (Nuxt SSR server) on its own subdomain, when configured.
628
+ const uiVirtualHost = uiDomain ? `
629
+ <VirtualHost *:80>
630
+ ServerName ${uiDomain}
631
+
632
+ ProxyRequests Off
633
+ ProxyPreserveHost On
634
+
635
+ ProxyPass / http://localhost:${uiPort}/
636
+ ProxyPassReverse / http://localhost:${uiPort}/
637
+
638
+ RequestHeader set X-Forwarded-Proto "https"
639
+ RequestHeader set X-Forwarded-Port "443"
640
+
641
+ ErrorLog \${APACHE_LOG_DIR}/${uiDomain}-error.log
642
+ CustomLog \${APACHE_LOG_DIR}/${uiDomain}-access.log combined
643
+ </VirtualHost>
644
+ ` : '';
481
645
 
482
646
  return `<VirtualHost *:80>
483
647
  ServerName ${domain}
@@ -529,7 +693,7 @@ function generateApacheConfig(settings, buildPath) {
529
693
  ErrorLog \${APACHE_LOG_DIR}/${settings.siteDomain}-error.log
530
694
  CustomLog \${APACHE_LOG_DIR}/${settings.siteDomain}-access.log combined
531
695
  </VirtualHost>
532
- `;
696
+ ${uiVirtualHost}`;
533
697
  }
534
698
 
535
699
  // Helper function to configure web server
@@ -632,6 +796,50 @@ async function managePM2Process(settings) {
632
796
  }
633
797
  }
634
798
 
799
+ // Helper function to build and (re)start the Mango UI (Nuxt) under PM2
800
+ async function manageUiPM2Process(settings, mangoCmsRoot, userProjectRoot) {
801
+ if (!settings.uiDomain) {
802
+ console.log('No uiDomain configured, skipping Mango UI deploy.');
803
+ return;
804
+ }
805
+
806
+ const { dir: uiDir, ejected } = resolveUiDir(mangoCmsRoot, userProjectRoot);
807
+ const uiPort = settings.uiPort || 3001;
808
+ const processName = `${settings.database.toLowerCase()}-ui`;
809
+
810
+ // Install deps + build the Nuxt app (reads this project's mango/config).
811
+ if (!fs.existsSync(path.join(uiDir, 'node_modules'))) {
812
+ console.log('Installing Mango UI dependencies...');
813
+ execSync('npm install', {
814
+ cwd: uiDir,
815
+ stdio: 'inherit',
816
+ env: { ...process.env, MANGO_PROJECT_ROOT: userProjectRoot, MANGO_UI_PORT: String(uiPort) }
817
+ });
818
+ }
819
+ console.log(`Building Mango UI (${ejected ? 'ejected' : 'bundled'})...`);
820
+ execSync('npx nuxt build', {
821
+ cwd: uiDir,
822
+ stdio: 'inherit',
823
+ env: { ...process.env, MANGO_PROJECT_ROOT: userProjectRoot, MANGO_UI_PORT: String(uiPort) }
824
+ });
825
+
826
+ const serverEntry = path.join(uiDir, '.output/server/index.mjs');
827
+ const processListOutput = execSync('pm2 list', { encoding: 'utf8' });
828
+
829
+ if (processListOutput.includes(processName)) {
830
+ console.log(`Reloading PM2 process: ${processName}`);
831
+ execSync(`pm2 reload ${processName} --update-env`, {
832
+ env: { ...process.env, PORT: String(uiPort), NODE_ENV: 'production' }
833
+ });
834
+ } else {
835
+ console.log(`Starting new PM2 process: ${processName}`);
836
+ execSync(`pm2 start "${serverEntry}" --name ${processName}`, {
837
+ cwd: uiDir,
838
+ env: { ...process.env, PORT: String(uiPort), NODE_ENV: 'production' }
839
+ });
840
+ }
841
+ }
842
+
635
843
  program
636
844
  .command('deploy')
637
845
  .description('Build and deploy Mango CMS using PM2')
@@ -639,14 +847,20 @@ program
639
847
  try {
640
848
  const configPath = path.join(process.cwd(), 'mango/config/settings.json');
641
849
  const settings = JSON.parse(fs.readFileSync(configPath, 'utf8'));
850
+ const mangoCmsRoot = path.resolve(__dirname);
851
+ const userProjectRoot = process.cwd();
642
852
 
643
853
  // Run build command
644
854
  console.log('Building Mango CMS...');
645
855
  execSync('npm run mango build', { stdio: 'inherit' });
646
856
 
857
+ // Make sure a UI bundle is available for deploy.
858
+ await ensureUiExists(mangoCmsRoot);
859
+
647
860
  // Check if PM2 is installed
648
861
  if (await checkPM2()) {
649
862
  await managePM2Process(settings);
863
+ await manageUiPM2Process(settings, mangoCmsRoot, userProjectRoot);
650
864
 
651
865
  // Configure web server
652
866
  const buildPath = path.join(process.cwd(), 'build');
@@ -810,6 +1024,15 @@ program
810
1024
  fs.unlinkSync(tempZipPath);
811
1025
  console.log(`Source files (version ${version}) updated successfully.`);
812
1026
 
1027
+ // Refresh the bundled (hidden) Mango UI as well. The project-local
1028
+ // ejected copy, if any, is never touched here.
1029
+ const bundledUiDir = path.join(cmsPackagePath, 'mango-ui');
1030
+ if (fs.existsSync(bundledUiDir)) {
1031
+ console.log('Removing existing Mango UI...');
1032
+ fs.removeSync(bundledUiDir);
1033
+ }
1034
+ await ensureUiExists(cmsPackagePath);
1035
+
813
1036
  } catch (error) {
814
1037
  console.error('Error updating Mango CMS source files:', error.message);
815
1038
  process.exit(1);
@@ -848,6 +1071,7 @@ const proxyCmd = program
848
1071
  const slug = siteName.toLowerCase().replace(/[^a-z0-9]/g, '');
849
1072
  const frontDomain = `${slug}.mango`;
850
1073
  const apiDomain = `api.${slug}.mango`;
1074
+ const uiDomain = `admin.${slug}.mango`;
851
1075
 
852
1076
  // Kill existing proxy if running
853
1077
  if (fs.existsSync(pidPath)) {
@@ -859,10 +1083,11 @@ const proxyCmd = program
859
1083
  }
860
1084
 
861
1085
  // Add /etc/hosts entries
862
- addHosts([frontDomain, apiDomain]);
1086
+ addHosts([frontDomain, apiDomain, uiDomain]);
863
1087
 
864
1088
  const frontendPort = settings.frontPort || 5173;
865
1089
  const backendPort = settings.port || 3002;
1090
+ const uiPort = settings.uiPort || 3001;
866
1091
 
867
1092
  // Start the proxy server as a detached background process
868
1093
  const proxyScript = path.join(__dirname, 'lib/dev-proxy.js');
@@ -871,7 +1096,7 @@ const proxyCmd = program
871
1096
  // Use shell to redirect output to a log file and background the process
872
1097
  // This keeps sudo in the current TTY so the password prompt works
873
1098
  execSync(
874
- `sudo node "${proxyScript}" ${frontDomain} ${apiDomain} ${frontendPort} ${backendPort} "${pidPath}" > "${logPath}" 2>&1 &`,
1099
+ `sudo node "${proxyScript}" ${frontDomain} ${apiDomain} ${frontendPort} ${backendPort} "${pidPath}" ${uiDomain} ${uiPort} > "${logPath}" 2>&1 &`,
875
1100
  { stdio: 'inherit', shell: true }
876
1101
  );
877
1102
 
@@ -900,12 +1125,13 @@ const proxyCmd = program
900
1125
  console.log(`
901
1126
  Mango Dev Proxy started.
902
1127
 
903
- http://${frontDomain} -> localhost:${frontendPort} (frontend)
904
- http://${apiDomain} -> localhost:${backendPort} (backend)
1128
+ http://${frontDomain} -> localhost:${frontendPort} (frontend)
1129
+ http://${apiDomain} -> localhost:${backendPort} (backend)
1130
+ http://${uiDomain} -> localhost:${uiPort} (admin ui)
905
1131
 
906
1132
  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})
1133
+ mango start (backend on port ${backendPort})
1134
+ mango ui dev (admin ui on port ${uiPort})
909
1135
 
910
1136
  Proxy log: ${logPath}
911
1137
  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.23",
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.23",
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>