@mongoosejs/studio 0.3.1 → 0.3.2
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/DEVGUIDE.md +8 -0
- package/backend/actions/Dashboard/getDashboard.js +15 -11
- package/frontend/public/app.js +10 -4
- package/frontend/src/chat/chat.js +2 -0
- package/frontend/src/dashboard/dashboard.js +3 -1
- package/frontend/src/models/models.js +1 -0
- package/frontend/src/navbar/navbar.js +3 -2
- package/local.js +38 -0
- package/package.json +3 -1
- package/seed/connect.js +23 -0
- package/seed/index.js +101 -0
package/DEVGUIDE.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
## Running Locally
|
|
2
|
+
|
|
3
|
+
To run this repo locally:
|
|
4
|
+
|
|
5
|
+
1. Install dependencies with `npm install`
|
|
6
|
+
2. Seed the database with `npm run seed`. Make sure you have a MongoDB instance running on `localhost:27017`.
|
|
7
|
+
3. Run backend on port 7777. Make sure to seed backend as well.
|
|
8
|
+
4. Start the server with `npm start`
|
|
@@ -23,16 +23,17 @@ const GetDashboardParams = new Archetype({
|
|
|
23
23
|
}
|
|
24
24
|
}).compile('GetDashboardParams');
|
|
25
25
|
|
|
26
|
-
module.exports = ({ db }) => async function getDashboard(params) {
|
|
26
|
+
module.exports = ({ db, options }) => async function getDashboard(params) {
|
|
27
27
|
const { $workspaceId, authorization, dashboardId, evaluate, roles } = new GetDashboardParams(params);
|
|
28
28
|
const Dashboard = db.model('__Studio_Dashboard');
|
|
29
|
+
const mothershipUrl = options?._mothershipUrl ?? 'https://mongoose-js.netlify.app/.netlify/functions';
|
|
29
30
|
|
|
30
31
|
await authorize('Dashboard.getDashboard', roles);
|
|
31
32
|
|
|
32
33
|
const dashboard = await Dashboard.findOne({ _id: dashboardId });
|
|
33
34
|
if (evaluate) {
|
|
34
35
|
let result = null;
|
|
35
|
-
const startExec = startDashboardEvaluate(dashboardId, $workspaceId, authorization);
|
|
36
|
+
const startExec = startDashboardEvaluate(dashboardId, $workspaceId, authorization, mothershipUrl);
|
|
36
37
|
// Avoid unhandled promise rejection since we handle the promise later.
|
|
37
38
|
startExec.catch(() => {});
|
|
38
39
|
try {
|
|
@@ -47,7 +48,8 @@ module.exports = ({ db }) => async function getDashboard(params) {
|
|
|
47
48
|
$workspaceId,
|
|
48
49
|
authorization,
|
|
49
50
|
null,
|
|
50
|
-
{ message: error.message }
|
|
51
|
+
{ message: error.message },
|
|
52
|
+
mothershipUrl
|
|
51
53
|
);
|
|
52
54
|
});
|
|
53
55
|
return { dashboard, dashboardResult, error: { message: error.message } };
|
|
@@ -62,7 +64,9 @@ module.exports = ({ db }) => async function getDashboard(params) {
|
|
|
62
64
|
dashboardResult._id,
|
|
63
65
|
$workspaceId,
|
|
64
66
|
authorization,
|
|
65
|
-
result
|
|
67
|
+
result,
|
|
68
|
+
undefined,
|
|
69
|
+
mothershipUrl
|
|
66
70
|
);
|
|
67
71
|
});
|
|
68
72
|
|
|
@@ -71,12 +75,12 @@ module.exports = ({ db }) => async function getDashboard(params) {
|
|
|
71
75
|
return { dashboard, error: { message: error.message } };
|
|
72
76
|
}
|
|
73
77
|
} else {
|
|
74
|
-
const { dashboardResults } = await getDashboardResults(dashboardId, $workspaceId, authorization);
|
|
78
|
+
const { dashboardResults } = await getDashboardResults(dashboardId, $workspaceId, authorization, mothershipUrl);
|
|
75
79
|
return { dashboard, dashboardResults };
|
|
76
80
|
}
|
|
77
81
|
};
|
|
78
82
|
|
|
79
|
-
async function completeDashboardEvaluate(dashboardResultId, workspaceId, authorization, result, error) {
|
|
83
|
+
async function completeDashboardEvaluate(dashboardResultId, workspaceId, authorization, result, error, mothershipUrl) {
|
|
80
84
|
if (!workspaceId) {
|
|
81
85
|
return {};
|
|
82
86
|
}
|
|
@@ -84,7 +88,7 @@ async function completeDashboardEvaluate(dashboardResultId, workspaceId, authori
|
|
|
84
88
|
if (authorization) {
|
|
85
89
|
headers.Authorization = authorization;
|
|
86
90
|
}
|
|
87
|
-
const response = await fetch(
|
|
91
|
+
const response = await fetch(`${mothershipUrl}/completeDashboardEvaluate`, {
|
|
88
92
|
method: 'POST',
|
|
89
93
|
headers,
|
|
90
94
|
body: JSON.stringify({
|
|
@@ -106,7 +110,7 @@ async function completeDashboardEvaluate(dashboardResultId, workspaceId, authori
|
|
|
106
110
|
return await response.json();
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
async function startDashboardEvaluate(dashboardId, workspaceId, authorization) {
|
|
113
|
+
async function startDashboardEvaluate(dashboardId, workspaceId, authorization, _mothershipUrl) {
|
|
110
114
|
if (!workspaceId) {
|
|
111
115
|
return {};
|
|
112
116
|
}
|
|
@@ -114,7 +118,7 @@ async function startDashboardEvaluate(dashboardId, workspaceId, authorization) {
|
|
|
114
118
|
if (authorization) {
|
|
115
119
|
headers.Authorization = authorization;
|
|
116
120
|
}
|
|
117
|
-
const response = await fetch(
|
|
121
|
+
const response = await fetch(`${_mothershipUrl}/startDashboardEvaluate`, {
|
|
118
122
|
method: 'POST',
|
|
119
123
|
headers,
|
|
120
124
|
body: JSON.stringify({
|
|
@@ -134,7 +138,7 @@ async function startDashboardEvaluate(dashboardId, workspaceId, authorization) {
|
|
|
134
138
|
return await response.json();
|
|
135
139
|
}
|
|
136
140
|
|
|
137
|
-
async function getDashboardResults(dashboardId, workspaceId, authorization) {
|
|
141
|
+
async function getDashboardResults(dashboardId, workspaceId, authorization, mothershipUrl) {
|
|
138
142
|
if (!workspaceId) {
|
|
139
143
|
return {};
|
|
140
144
|
}
|
|
@@ -142,7 +146,7 @@ async function getDashboardResults(dashboardId, workspaceId, authorization) {
|
|
|
142
146
|
if (authorization) {
|
|
143
147
|
headers.Authorization = authorization;
|
|
144
148
|
}
|
|
145
|
-
const response = await fetch(
|
|
149
|
+
const response = await fetch(`${mothershipUrl}/getDashboardResults`, {
|
|
146
150
|
method: 'POST',
|
|
147
151
|
headers,
|
|
148
152
|
body: JSON.stringify({
|
package/frontend/public/app.js
CHANGED
|
@@ -1822,6 +1822,8 @@ module.exports = {
|
|
|
1822
1822
|
}
|
|
1823
1823
|
},
|
|
1824
1824
|
async mounted() {
|
|
1825
|
+
window.pageState = this;
|
|
1826
|
+
|
|
1825
1827
|
this.chatThreadId = this.threadId;
|
|
1826
1828
|
const { chatThreads } = await api.ChatThread.listChatThreads();
|
|
1827
1829
|
this.chatThreads = chatThreads;
|
|
@@ -2730,7 +2732,9 @@ module.exports = {
|
|
|
2730
2732
|
return this.dashboardResults.length > 0 ? this.dashboardResults[0] : null;
|
|
2731
2733
|
}
|
|
2732
2734
|
},
|
|
2733
|
-
mounted: async function() {
|
|
2735
|
+
mounted: async function () {
|
|
2736
|
+
window.pageState = this;
|
|
2737
|
+
|
|
2734
2738
|
document.addEventListener('click', this.handleDocumentClick);
|
|
2735
2739
|
this.showEditor = this.$route.query.edit;
|
|
2736
2740
|
await this.loadInitial();
|
|
@@ -7120,6 +7124,7 @@ module.exports = app => app.component('models', {
|
|
|
7120
7124
|
this.destroyMap();
|
|
7121
7125
|
},
|
|
7122
7126
|
async mounted() {
|
|
7127
|
+
window.pageState = this;
|
|
7123
7128
|
this.onScroll = () => this.checkIfScrolledToBottom();
|
|
7124
7129
|
document.addEventListener('scroll', this.onScroll, true);
|
|
7125
7130
|
this.onPopState = () => this.initSearchFromUrl();
|
|
@@ -8294,7 +8299,8 @@ module.exports = app => app.component('navbar', {
|
|
|
8294
8299
|
showFlyout: false,
|
|
8295
8300
|
darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark'
|
|
8296
8301
|
}),
|
|
8297
|
-
mounted: function() {
|
|
8302
|
+
mounted: function () {
|
|
8303
|
+
window.navbar = this;
|
|
8298
8304
|
const mobileMenuMask = document.querySelector('#mobile-menu-mask');
|
|
8299
8305
|
const mobileMenu = document.querySelector('#mobile-menu');
|
|
8300
8306
|
const openBtn = document.querySelector('#open-mobile-menu');
|
|
@@ -8348,7 +8354,7 @@ module.exports = app => app.component('navbar', {
|
|
|
8348
8354
|
} else {
|
|
8349
8355
|
return 'https://www.npmjs.com/package/@mongoosejs/task';
|
|
8350
8356
|
}
|
|
8351
|
-
|
|
8357
|
+
|
|
8352
8358
|
}
|
|
8353
8359
|
},
|
|
8354
8360
|
methods: {
|
|
@@ -61661,7 +61667,7 @@ var src_default = VueToastificationPlugin;
|
|
|
61661
61667
|
(module) {
|
|
61662
61668
|
|
|
61663
61669
|
"use strict";
|
|
61664
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.
|
|
61670
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.2","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
|
|
61665
61671
|
|
|
61666
61672
|
/***/ }
|
|
61667
61673
|
|
|
@@ -157,7 +157,9 @@ module.exports = {
|
|
|
157
157
|
return this.dashboardResults.length > 0 ? this.dashboardResults[0] : null;
|
|
158
158
|
}
|
|
159
159
|
},
|
|
160
|
-
mounted: async function() {
|
|
160
|
+
mounted: async function () {
|
|
161
|
+
window.pageState = this;
|
|
162
|
+
|
|
161
163
|
document.addEventListener('click', this.handleDocumentClick);
|
|
162
164
|
this.showEditor = this.$route.query.edit;
|
|
163
165
|
await this.loadInitial();
|
|
@@ -79,6 +79,7 @@ module.exports = app => app.component('models', {
|
|
|
79
79
|
this.destroyMap();
|
|
80
80
|
},
|
|
81
81
|
async mounted() {
|
|
82
|
+
window.pageState = this;
|
|
82
83
|
this.onScroll = () => this.checkIfScrolledToBottom();
|
|
83
84
|
document.addEventListener('scroll', this.onScroll, true);
|
|
84
85
|
this.onPopState = () => this.initSearchFromUrl();
|
|
@@ -17,7 +17,8 @@ module.exports = app => app.component('navbar', {
|
|
|
17
17
|
showFlyout: false,
|
|
18
18
|
darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark'
|
|
19
19
|
}),
|
|
20
|
-
mounted: function() {
|
|
20
|
+
mounted: function () {
|
|
21
|
+
window.navbar = this;
|
|
21
22
|
const mobileMenuMask = document.querySelector('#mobile-menu-mask');
|
|
22
23
|
const mobileMenu = document.querySelector('#mobile-menu');
|
|
23
24
|
const openBtn = document.querySelector('#open-mobile-menu');
|
|
@@ -71,7 +72,7 @@ module.exports = app => app.component('navbar', {
|
|
|
71
72
|
} else {
|
|
72
73
|
return 'https://www.npmjs.com/package/@mongoosejs/task';
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
|
|
75
76
|
}
|
|
76
77
|
},
|
|
77
78
|
methods: {
|
package/local.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const mongoose = require('mongoose');
|
|
4
|
+
const connect = require('./seed/connect');
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const studio = require('./express');
|
|
8
|
+
|
|
9
|
+
const getModelDescriptions = require('./backend/helpers/getModelDescriptions');
|
|
10
|
+
|
|
11
|
+
Error.stackTraceLimit = 25;
|
|
12
|
+
|
|
13
|
+
run().catch(err => {
|
|
14
|
+
console.error(err);
|
|
15
|
+
process.exit(-1);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function run() {
|
|
19
|
+
const app = express();
|
|
20
|
+
await connect();
|
|
21
|
+
|
|
22
|
+
console.log(getModelDescriptions(mongoose.connection));
|
|
23
|
+
|
|
24
|
+
app.use('/studio', await studio(
|
|
25
|
+
null,
|
|
26
|
+
null,
|
|
27
|
+
{
|
|
28
|
+
__build: true,
|
|
29
|
+
__watch: process.env.WATCH,
|
|
30
|
+
_mothershipUrl: 'http://localhost:7777/.netlify/functions',
|
|
31
|
+
apiKey: 'TACO',
|
|
32
|
+
openAIAPIKey: process.env.OPENAI_API_KEY
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
await app.listen(3333);
|
|
37
|
+
console.log('Listening on port 3333');
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.",
|
|
5
5
|
"homepage": "https://mongoosestudio.app/",
|
|
6
6
|
"repository": {
|
|
@@ -46,6 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"lint": "eslint .",
|
|
49
|
+
"seed": "node seed/index.js",
|
|
50
|
+
"start": "node ./local.js",
|
|
49
51
|
"tailwind": "tailwindcss -o ./frontend/public/tw.css",
|
|
50
52
|
"tailwind:watch": "tailwindcss -o ./frontend/public/tw.css --watch",
|
|
51
53
|
"test": "mocha test/*.test.js",
|
package/seed/connect.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const mongoose = require('mongoose');
|
|
4
|
+
|
|
5
|
+
const uri = 'mongodb://127.0.0.1:27017/mongoose_studio_test';
|
|
6
|
+
|
|
7
|
+
module.exports = async function connect() {
|
|
8
|
+
await mongoose.connect(uri);
|
|
9
|
+
|
|
10
|
+
mongoose.model('User', new mongoose.Schema({
|
|
11
|
+
name: String,
|
|
12
|
+
email: String,
|
|
13
|
+
role: String,
|
|
14
|
+
plan: String,
|
|
15
|
+
status: String,
|
|
16
|
+
isDeleted: Boolean,
|
|
17
|
+
lastLoginAt: Date,
|
|
18
|
+
createdAt: Date,
|
|
19
|
+
updatedAt: Date
|
|
20
|
+
}, { collection: 'User' }));
|
|
21
|
+
|
|
22
|
+
return mongoose.connection;
|
|
23
|
+
};
|
package/seed/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const connect = require('./connect');
|
|
4
|
+
const mongoose = require('mongoose');
|
|
5
|
+
|
|
6
|
+
if (require.main === module) {
|
|
7
|
+
run().catch(err => {
|
|
8
|
+
console.error(err);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function run() {
|
|
14
|
+
await connect();
|
|
15
|
+
const { User } = mongoose.models;
|
|
16
|
+
const dashboardCollection = mongoose.connection.collection('studio__dashboards');
|
|
17
|
+
const now = new Date();
|
|
18
|
+
|
|
19
|
+
const users = [
|
|
20
|
+
{
|
|
21
|
+
name: 'Ada Lovelace',
|
|
22
|
+
email: 'ada@example.com',
|
|
23
|
+
role: 'admin',
|
|
24
|
+
plan: 'enterprise',
|
|
25
|
+
status: 'active',
|
|
26
|
+
isDeleted: false,
|
|
27
|
+
lastLoginAt: new Date('2026-03-11T14:21:00.000Z'),
|
|
28
|
+
createdAt: new Date('2025-10-03T09:15:00.000Z'),
|
|
29
|
+
updatedAt: now
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'Grace Hopper',
|
|
33
|
+
email: 'grace@example.com',
|
|
34
|
+
role: 'analyst',
|
|
35
|
+
plan: 'pro',
|
|
36
|
+
status: 'active',
|
|
37
|
+
isDeleted: false,
|
|
38
|
+
lastLoginAt: new Date('2026-03-10T17:42:00.000Z'),
|
|
39
|
+
createdAt: new Date('2025-11-16T13:05:00.000Z'),
|
|
40
|
+
updatedAt: now
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Linus Torvalds',
|
|
44
|
+
email: 'linus@example.com',
|
|
45
|
+
role: 'editor',
|
|
46
|
+
plan: 'starter',
|
|
47
|
+
status: 'invited',
|
|
48
|
+
isDeleted: false,
|
|
49
|
+
lastLoginAt: null,
|
|
50
|
+
createdAt: new Date('2026-01-09T11:30:00.000Z'),
|
|
51
|
+
updatedAt: now
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Margaret Hamilton',
|
|
55
|
+
email: 'margaret@example.com',
|
|
56
|
+
role: 'viewer',
|
|
57
|
+
plan: 'pro',
|
|
58
|
+
status: 'inactive',
|
|
59
|
+
isDeleted: false,
|
|
60
|
+
lastLoginAt: new Date('2026-02-22T08:00:00.000Z'),
|
|
61
|
+
createdAt: new Date('2025-12-01T16:20:00.000Z'),
|
|
62
|
+
updatedAt: now
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const dashboard = {
|
|
67
|
+
title: 'User directory table',
|
|
68
|
+
description: 'Sample dashboard for testing the dashboard-table component.',
|
|
69
|
+
code: `const users = await db.model('User')
|
|
70
|
+
.find({ isDeleted: false })
|
|
71
|
+
.sort({ createdAt: -1 })
|
|
72
|
+
.lean();
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
$table: {
|
|
76
|
+
columns: ['Name', 'Email', 'Role', 'Plan', 'Status', 'Last Login'],
|
|
77
|
+
rows: users.map(user => [
|
|
78
|
+
user.name,
|
|
79
|
+
user.email,
|
|
80
|
+
user.role,
|
|
81
|
+
user.plan,
|
|
82
|
+
user.status,
|
|
83
|
+
user.lastLoginAt ? new Date(user.lastLoginAt).toISOString().slice(0, 10) : 'Never'
|
|
84
|
+
])
|
|
85
|
+
}
|
|
86
|
+
};`
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
await User.deleteMany({ email: { $in: users.map(user => user.email) } });
|
|
90
|
+
await dashboardCollection.deleteMany({ title: dashboard.title });
|
|
91
|
+
|
|
92
|
+
const insertedUsers = await User.insertMany(users);
|
|
93
|
+
const insertedDashboard = await dashboardCollection.insertOne(dashboard);
|
|
94
|
+
|
|
95
|
+
console.log(`Inserted ${insertedUsers.length} users`);
|
|
96
|
+
console.log(`Inserted dashboard ${insertedDashboard.insertedId.toString()}`);
|
|
97
|
+
|
|
98
|
+
await mongoose.disconnect();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = run;
|