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 +249 -23
- package/default/mango/config/settings.json +3 -1
- package/default/package.json +2 -1
- package/default/src/helpers/user.js +41 -22
- package/default/vite.config.js +4 -0
- package/lib/dev-proxy.js +10 -2
- package/package.json +1 -1
- package/default/src/components/layout/modal.vue +0 -108
- package/default/src/components/layout/spinner.vue +0 -17
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
|
|
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
|
|
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
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
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
|
-
|
|
302
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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}
|
|
904
|
-
http://${apiDomain}
|
|
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
|
|
908
|
-
|
|
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":
|
|
11
|
+
"mangoThreads": 1,
|
|
10
12
|
"maxPoolSize": 10,
|
|
11
13
|
"minPoolSize": 0,
|
|
12
14
|
"maxIdleTimeMS": 60000,
|
package/default/package.json
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
let userId = window.localStorage.getItem('user')
|
|
40
|
+
window.user = null
|
|
41
|
+
return user.value
|
|
42
|
+
}
|
|
23
43
|
|
|
24
|
-
|
|
44
|
+
let userId = window.localStorage.getItem('user')
|
|
25
45
|
|
|
26
|
-
|
|
46
|
+
if (!userId) return {}
|
|
27
47
|
|
|
28
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
return user
|
|
37
|
-
|
|
54
|
+
const user = ref({})
|
|
55
|
+
getUser(user)
|
|
56
|
+
return user
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
export { initUser, getUser }
|
package/default/vite.config.js
CHANGED
|
@@ -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,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>
|