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 +240 -24
- 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,93 @@ 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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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}
|
|
904
|
-
http://${apiDomain}
|
|
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
|
|
908
|
-
|
|
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":
|
|
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.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
|
-
|
|
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>
|