@mongoosejs/studio 0.5.5 → 0.5.7
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/README.md +35 -3
- package/backend/actions/Dashboard/createDashboard.js +12 -2
- package/backend/actions/Dashboard/getDashboard.js +6 -2
- package/backend/actions/Dashboard/getDashboards.js +3 -1
- package/backend/actions/Dashboard/updateDashboard.js +19 -6
- package/backend/db/dashboardSchema.js +31 -0
- package/backend/netlify.js +1 -0
- package/backend/next.js +1 -0
- package/express.js +9 -1
- package/frontend/public/app.js +257 -18
- package/frontend/public/images/pin-off.svg +1 -0
- package/frontend/public/images/pin.svg +1 -0
- package/frontend/public/tw.css +28 -0
- package/frontend/src/dashboards/dashboards.html +52 -4
- package/frontend/src/dashboards/dashboards.js +96 -1
- package/nest.d.ts +27 -0
- package/nest.js +134 -0
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Mongoose Studio
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
## Getting Started
|
|
8
8
|
|
|
9
9
|
Mongoose Studio is meant to run as a [sidecar](https://learn.microsoft.com/en-us/azure/architecture/patterns/sidecar) to your Node.js application, using the same Mongoose connection config.
|
|
10
|
-
If your app runs on `acme.app`, Studio will be on `acme.app/studio` or whichever path you prefer.
|
|
11
|
-
For local dev, if your app runs on `localhost:3000`, Studio will be on `localhost:3000/studio`.
|
|
10
|
+
If your app runs on `acme.app`, Mongoose Studio will be on `acme.app/studio` or whichever path you prefer.
|
|
11
|
+
For local dev, if your app runs on `localhost:3000`, Mongoose Studio will be on `localhost:3000/studio`.
|
|
12
12
|
|
|
13
13
|
By default, Mongoose Studio does **not** provide any authentication or authorization.
|
|
14
14
|
You can use Mongoose Studio for free for local development, but we recommend [Mongoose Studio Pro](https://studio.mongoosejs.io/#pricing) for when you want to go into production.
|
|
@@ -85,6 +85,38 @@ const handler = studio(
|
|
|
85
85
|
export default handler;
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
### Nest.js
|
|
89
|
+
|
|
90
|
+
Add `MongooseStudioModule` to your app module as follows.
|
|
91
|
+
Use `getConnectionToken()` so Mongoose Studio uses the same connection that `MongooseModule.forRoot()` creates.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { Module } from '@nestjs/common';
|
|
95
|
+
import { MongooseModule, getConnectionToken } from '@nestjs/mongoose';
|
|
96
|
+
import { MongooseStudioModule } from '@mongoosejs/studio/nest';
|
|
97
|
+
|
|
98
|
+
@Module({
|
|
99
|
+
imports: [
|
|
100
|
+
MongooseModule.forRoot(process.env.MONGODB_URI),
|
|
101
|
+
MongooseStudioModule.forRoot({
|
|
102
|
+
connectionToken: getConnectionToken(),
|
|
103
|
+
apiKey: process.env.MONGOOSE_STUDIO_API_KEY // optional
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
export class AppModule {}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
With this setup, Mongoose Studio is available at `/studio`.
|
|
111
|
+
If you mount Mongoose Studio on a different path, update the `path` option as follows.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
MongooseStudioModule.forRoot({
|
|
115
|
+
path: '/__studio', // Serve on `/__studio` rather than `/studio`
|
|
116
|
+
connectionToken: getConnectionToken()
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
88
120
|
### Netlify
|
|
89
121
|
|
|
90
122
|
[Here is a full example of how to add Mongoose Studio to a Netlify repo](https://github.com/mongoosejs/studio.mongoosejs.io/commit/8b02ea367c8a1b7b4bcab290708f57d58f08210b).
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const mongoose = require('mongoose');
|
|
5
6
|
|
|
6
7
|
const CreateDashboardParams = new Archetype({
|
|
7
8
|
title: {
|
|
@@ -12,18 +13,27 @@ const CreateDashboardParams = new Archetype({
|
|
|
12
13
|
$type: 'string',
|
|
13
14
|
$required: true
|
|
14
15
|
},
|
|
16
|
+
initiatedById: {
|
|
17
|
+
$type: mongoose.Types.ObjectId,
|
|
18
|
+
$required: false
|
|
19
|
+
},
|
|
15
20
|
roles: {
|
|
16
21
|
$type: ['string']
|
|
17
22
|
}
|
|
18
23
|
}).compile('CreateDashboardParams');
|
|
19
24
|
|
|
20
25
|
module.exports = ({ studioConnection }) => async function createDashboard(params) {
|
|
21
|
-
const { title, code, roles } = new CreateDashboardParams(params);
|
|
26
|
+
const { title, code, initiatedById, roles } = new CreateDashboardParams(params);
|
|
22
27
|
const Dashboard = studioConnection.model('__Studio_Dashboard');
|
|
23
28
|
|
|
24
29
|
await authorize('Dashboard.createDashboard', roles);
|
|
25
30
|
|
|
26
|
-
const dashboard = await Dashboard.create({
|
|
31
|
+
const dashboard = await Dashboard.create({
|
|
32
|
+
title,
|
|
33
|
+
code,
|
|
34
|
+
createdById: initiatedById,
|
|
35
|
+
createdBy: params.initiatedBy
|
|
36
|
+
});
|
|
27
37
|
|
|
28
38
|
return { dashboard };
|
|
29
39
|
};
|
|
@@ -51,6 +51,7 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
51
51
|
return {};
|
|
52
52
|
}
|
|
53
53
|
return completeDashboardEvaluate(
|
|
54
|
+
Dashboard,
|
|
54
55
|
DashboardResult,
|
|
55
56
|
dashboardResult._id,
|
|
56
57
|
null,
|
|
@@ -73,6 +74,7 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
73
74
|
return {};
|
|
74
75
|
}
|
|
75
76
|
return completeDashboardEvaluate(
|
|
77
|
+
Dashboard,
|
|
76
78
|
DashboardResult,
|
|
77
79
|
dashboardResult._id,
|
|
78
80
|
result,
|
|
@@ -97,13 +99,15 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
97
99
|
}
|
|
98
100
|
};
|
|
99
101
|
|
|
100
|
-
async function completeDashboardEvaluate(DashboardResult, dashboardResultId, result, error, status) {
|
|
102
|
+
async function completeDashboardEvaluate(Dashboard, DashboardResult, dashboardResultId, result, error, status) {
|
|
101
103
|
const dashboardResult = await DashboardResult.findById(dashboardResultId).orFail();
|
|
102
|
-
|
|
104
|
+
const finishedEvaluatingAt = new Date();
|
|
105
|
+
dashboardResult.finishedEvaluatingAt = finishedEvaluatingAt;
|
|
103
106
|
dashboardResult.result = result;
|
|
104
107
|
dashboardResult.error = error;
|
|
105
108
|
dashboardResult.status = status;
|
|
106
109
|
await dashboardResult.save();
|
|
110
|
+
await Dashboard.updateOne({ _id: dashboardResult.dashboardId }, { $set: { lastEvaluatedAt: finishedEvaluatingAt } });
|
|
107
111
|
return { dashboardResult };
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -15,7 +15,9 @@ module.exports = ({ studioConnection }) => async function getDashboards(params)
|
|
|
15
15
|
|
|
16
16
|
await authorize('Dashboard.getDashboards', roles);
|
|
17
17
|
|
|
18
|
-
const dashboards = await Dashboard
|
|
18
|
+
const dashboards = await Dashboard
|
|
19
|
+
.find()
|
|
20
|
+
.sort({ isPinned: -1, createdAt: -1, _id: -1 });
|
|
19
21
|
|
|
20
22
|
return { dashboards };
|
|
21
23
|
};
|
|
@@ -9,8 +9,7 @@ const UpdateDashboardParams = new Archetype({
|
|
|
9
9
|
$required: true
|
|
10
10
|
},
|
|
11
11
|
code: {
|
|
12
|
-
$type: 'string'
|
|
13
|
-
$required: true
|
|
12
|
+
$type: 'string'
|
|
14
13
|
},
|
|
15
14
|
title: {
|
|
16
15
|
$type: 'string'
|
|
@@ -18,19 +17,26 @@ const UpdateDashboardParams = new Archetype({
|
|
|
18
17
|
description: {
|
|
19
18
|
$type: 'string'
|
|
20
19
|
},
|
|
20
|
+
isPinned: {
|
|
21
|
+
$type: 'boolean'
|
|
22
|
+
},
|
|
21
23
|
roles: {
|
|
22
24
|
$type: ['string']
|
|
23
25
|
}
|
|
24
26
|
}).compile('UpdateDashboardParams');
|
|
25
27
|
|
|
26
28
|
module.exports = ({ studioConnection }) => async function updateDashboard(params) {
|
|
27
|
-
const { dashboardId, code, title, description, roles, evaluate } = new UpdateDashboardParams(params);
|
|
29
|
+
const { dashboardId, code, title, description, isPinned, roles, evaluate } = new UpdateDashboardParams(params);
|
|
28
30
|
|
|
29
31
|
const Dashboard = studioConnection.models['__Studio_Dashboard'];
|
|
30
32
|
|
|
31
33
|
await authorize('Dashboard.updateDashboard', roles);
|
|
32
34
|
|
|
33
|
-
const updateObj = {
|
|
35
|
+
const updateObj = {};
|
|
36
|
+
|
|
37
|
+
if (code != null) {
|
|
38
|
+
updateObj.code = code;
|
|
39
|
+
}
|
|
34
40
|
|
|
35
41
|
if (title != null) {
|
|
36
42
|
updateObj.title = title;
|
|
@@ -40,8 +46,15 @@ module.exports = ({ studioConnection }) => async function updateDashboard(params
|
|
|
40
46
|
updateObj.description = description;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
if (isPinned != null) {
|
|
50
|
+
updateObj.isPinned = isPinned;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const doc = await Dashboard.findByIdAndUpdate(
|
|
54
|
+
dashboardId,
|
|
55
|
+
updateObj,
|
|
56
|
+
{ sanitizeFilter: true, returnDocument: 'after' }
|
|
57
|
+
);
|
|
45
58
|
|
|
46
59
|
return { doc };
|
|
47
60
|
};
|
|
@@ -13,6 +13,37 @@ const dashboardSchema = new mongoose.Schema({
|
|
|
13
13
|
},
|
|
14
14
|
description: {
|
|
15
15
|
type: String
|
|
16
|
+
},
|
|
17
|
+
isPinned: {
|
|
18
|
+
type: Boolean
|
|
19
|
+
},
|
|
20
|
+
createdById: {
|
|
21
|
+
type: mongoose.Schema.Types.ObjectId
|
|
22
|
+
},
|
|
23
|
+
createdBy: {
|
|
24
|
+
name: String,
|
|
25
|
+
email: String
|
|
26
|
+
},
|
|
27
|
+
lastEvaluatedAt: {
|
|
28
|
+
type: Date
|
|
29
|
+
}
|
|
30
|
+
}, { timestamps: true });
|
|
31
|
+
|
|
32
|
+
dashboardSchema.post(['find', 'findOne'], async function(docs) {
|
|
33
|
+
const dashboards = Array.isArray(docs) ? docs : [docs];
|
|
34
|
+
for (const dashboard of dashboards) {
|
|
35
|
+
if (dashboard != null && dashboard.createdAt == null && dashboard._id?.getTimestamp) {
|
|
36
|
+
const createdAt = dashboard._id.getTimestamp();
|
|
37
|
+
if (!createdAt.valueOf()) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
dashboard.set('createdAt', createdAt, { overwriteImmutable: true });
|
|
41
|
+
await this.model.db.model('__Studio_Dashboard').updateOne(
|
|
42
|
+
{ _id: dashboard._id },
|
|
43
|
+
{ createdAt },
|
|
44
|
+
{ overwriteImmutable: true }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
16
47
|
}
|
|
17
48
|
});
|
|
18
49
|
|
package/backend/netlify.js
CHANGED
package/backend/next.js
CHANGED
package/express.js
CHANGED
|
@@ -9,6 +9,8 @@ const normalizeBindIPOption = require('./backend/helpers/normalizeBindIPOption')
|
|
|
9
9
|
const { toRoute, objectRouter } = require('extrovert');
|
|
10
10
|
const { defaultMothershipURL } = require('./constants');
|
|
11
11
|
|
|
12
|
+
const jsonParser = express.json();
|
|
13
|
+
|
|
12
14
|
module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options) {
|
|
13
15
|
const router = express.Router();
|
|
14
16
|
options = options ? { changeStream: true, ...options } : { changeStream: true };
|
|
@@ -87,6 +89,7 @@ module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options)
|
|
|
87
89
|
req._internals.initiatedById = user._id;
|
|
88
90
|
req._internals.roles = roles;
|
|
89
91
|
req._internals.$workspaceId = workspace._id;
|
|
92
|
+
req._internals.initiatedBy = user;
|
|
90
93
|
|
|
91
94
|
next();
|
|
92
95
|
})
|
|
@@ -94,7 +97,12 @@ module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options)
|
|
|
94
97
|
return res.status(500).json({ message: err.message });
|
|
95
98
|
});
|
|
96
99
|
},
|
|
97
|
-
|
|
100
|
+
function parseJson(req, res, next) {
|
|
101
|
+
if (req.body !== undefined || req.readable === false) {
|
|
102
|
+
return next();
|
|
103
|
+
}
|
|
104
|
+
return jsonParser(req, res, next);
|
|
105
|
+
},
|
|
98
106
|
objectRouter(backend, toRoute)
|
|
99
107
|
);
|
|
100
108
|
|
package/frontend/public/app.js
CHANGED
|
@@ -3266,11 +3266,73 @@ module.exports = app => app.component('dashboards', {
|
|
|
3266
3266
|
data: () => ({
|
|
3267
3267
|
status: 'loading',
|
|
3268
3268
|
dashboards: [],
|
|
3269
|
+
searchText: '',
|
|
3269
3270
|
showCreateDashboardModal: false,
|
|
3270
3271
|
showDeleteDashboardModal: null,
|
|
3271
3272
|
openMenuId: null
|
|
3272
3273
|
}),
|
|
3274
|
+
computed: {
|
|
3275
|
+
filteredDashboards() {
|
|
3276
|
+
const searchWords = getSearchWords(this.searchText);
|
|
3277
|
+
let dashboards = this.dashboards;
|
|
3278
|
+
if (searchWords.length === 0) {
|
|
3279
|
+
return sortPinnedFirst(dashboards);
|
|
3280
|
+
}
|
|
3281
|
+
const searchPatterns = searchWords.map(word => new RegExp(escapeRegExp(word), 'i'));
|
|
3282
|
+
|
|
3283
|
+
dashboards = dashboards.filter(dashboard => {
|
|
3284
|
+
const searchableText = `${dashboard.title || ''} ${dashboard.description || ''}`;
|
|
3285
|
+
return searchPatterns.every(pattern => pattern.test(searchableText));
|
|
3286
|
+
});
|
|
3287
|
+
|
|
3288
|
+
return sortPinnedFirst(dashboards);
|
|
3289
|
+
}
|
|
3290
|
+
},
|
|
3273
3291
|
methods: {
|
|
3292
|
+
highlightTitle(value) {
|
|
3293
|
+
return this.highlightSearchText(value, 'underline decoration-primary underline-offset-2');
|
|
3294
|
+
},
|
|
3295
|
+
highlightDescription(value) {
|
|
3296
|
+
return this.highlightSearchText(value, 'font-semibold text-content-secondary');
|
|
3297
|
+
},
|
|
3298
|
+
highlightSearchText(value, className) {
|
|
3299
|
+
const text = value || '';
|
|
3300
|
+
const searchWords = getSearchWords(this.searchText);
|
|
3301
|
+
if (searchWords.length === 0) {
|
|
3302
|
+
return escapeHtml(text);
|
|
3303
|
+
}
|
|
3304
|
+
const searchPattern = new RegExp(searchWords.map(escapeRegExp).join('|'), 'ig');
|
|
3305
|
+
|
|
3306
|
+
let html = '';
|
|
3307
|
+
let lastIndex = 0;
|
|
3308
|
+
let match = searchPattern.exec(text);
|
|
3309
|
+
while (match != null) {
|
|
3310
|
+
html += escapeHtml(text.slice(lastIndex, match.index));
|
|
3311
|
+
html += `<span class="${className}">${escapeHtml(match[0])}</span>`;
|
|
3312
|
+
lastIndex = match.index + match[0].length;
|
|
3313
|
+
match = searchPattern.exec(text);
|
|
3314
|
+
}
|
|
3315
|
+
html += escapeHtml(text.slice(lastIndex));
|
|
3316
|
+
return html;
|
|
3317
|
+
},
|
|
3318
|
+
formatDate(value) {
|
|
3319
|
+
if (!value) {
|
|
3320
|
+
return '-';
|
|
3321
|
+
}
|
|
3322
|
+
const date = new Date(value);
|
|
3323
|
+
if (Number.isNaN(date.getTime())) {
|
|
3324
|
+
return '-';
|
|
3325
|
+
}
|
|
3326
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
3327
|
+
dateStyle: 'medium',
|
|
3328
|
+
timeStyle: 'short'
|
|
3329
|
+
}).format(date);
|
|
3330
|
+
},
|
|
3331
|
+
formatUser(dashboard) {
|
|
3332
|
+
return dashboard.createdBy?.name ||
|
|
3333
|
+
dashboard.createdBy?.email ||
|
|
3334
|
+
(dashboard.createdById ? dashboard.createdById.toString() : '-');
|
|
3335
|
+
},
|
|
3274
3336
|
toggleMenu(id) {
|
|
3275
3337
|
this.openMenuId = this.openMenuId === id ? null : id;
|
|
3276
3338
|
},
|
|
@@ -3290,8 +3352,19 @@ module.exports = app => app.component('dashboards', {
|
|
|
3290
3352
|
this.$toast.success('Dashboard deleted!');
|
|
3291
3353
|
},
|
|
3292
3354
|
insertNewDashboard(dashboard) {
|
|
3293
|
-
this.dashboards.
|
|
3355
|
+
this.dashboards.unshift(dashboard);
|
|
3294
3356
|
this.showCreateDashboardModal = false;
|
|
3357
|
+
},
|
|
3358
|
+
async togglePin(dashboard) {
|
|
3359
|
+
if (!dashboard) {
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
const { doc } = await api.Dashboard.updateDashboard({
|
|
3364
|
+
dashboardId: dashboard._id,
|
|
3365
|
+
isPinned: !dashboard.isPinned
|
|
3366
|
+
});
|
|
3367
|
+
dashboard.isPinned = doc.isPinned;
|
|
3295
3368
|
}
|
|
3296
3369
|
},
|
|
3297
3370
|
directives: {
|
|
@@ -3316,6 +3389,28 @@ module.exports = app => app.component('dashboards', {
|
|
|
3316
3389
|
}
|
|
3317
3390
|
});
|
|
3318
3391
|
|
|
3392
|
+
function escapeHtml(value) {
|
|
3393
|
+
return value.toString().
|
|
3394
|
+
replace(/&/g, '&').
|
|
3395
|
+
replace(/</g, '<').
|
|
3396
|
+
replace(/>/g, '>').
|
|
3397
|
+
replace(/"/g, '"').
|
|
3398
|
+
replace(/'/g, ''');
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
function escapeRegExp(value) {
|
|
3402
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
function getSearchWords(searchText) {
|
|
3406
|
+
return Array.from(new Set(searchText.trim().split(/\s+/).filter(Boolean))).
|
|
3407
|
+
sort((a, b) => b.length - a.length);
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
function sortPinnedFirst(dashboards) {
|
|
3411
|
+
return dashboards.slice().sort((a, b) => Number(Boolean(b.isPinned)) - Number(Boolean(a.isPinned)));
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3319
3414
|
|
|
3320
3415
|
/***/ },
|
|
3321
3416
|
|
|
@@ -12604,7 +12699,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
12604
12699
|
/* harmony export */ });
|
|
12605
12700
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
12606
12701
|
/**
|
|
12607
|
-
* @vue/reactivity v3.5.
|
|
12702
|
+
* @vue/reactivity v3.5.38
|
|
12608
12703
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
12609
12704
|
* @license MIT
|
|
12610
12705
|
**/
|
|
@@ -14501,8 +14596,9 @@ function watch(source, cb, options = _vue_shared__WEBPACK_IMPORTED_MODULE_0__.EM
|
|
|
14501
14596
|
if (once && cb) {
|
|
14502
14597
|
const _cb = cb;
|
|
14503
14598
|
cb = (...args) => {
|
|
14504
|
-
_cb(...args);
|
|
14599
|
+
const res = _cb(...args);
|
|
14505
14600
|
watchHandle();
|
|
14601
|
+
return res;
|
|
14506
14602
|
};
|
|
14507
14603
|
}
|
|
14508
14604
|
let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE;
|
|
@@ -14512,7 +14608,7 @@ function watch(source, cb, options = _vue_shared__WEBPACK_IMPORTED_MODULE_0__.EM
|
|
|
14512
14608
|
}
|
|
14513
14609
|
if (cb) {
|
|
14514
14610
|
const newValue = effect.run();
|
|
14515
|
-
if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.hasChanged)(v, oldValue[i])) : (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.hasChanged)(newValue, oldValue))) {
|
|
14611
|
+
if (immediateFirstRun || deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.hasChanged)(v, oldValue[i])) : (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.hasChanged)(newValue, oldValue))) {
|
|
14516
14612
|
if (cleanup) {
|
|
14517
14613
|
cleanup();
|
|
14518
14614
|
}
|
|
@@ -14772,7 +14868,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
14772
14868
|
/* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/reactivity */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
14773
14869
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
14774
14870
|
/**
|
|
14775
|
-
* @vue/runtime-core v3.5.
|
|
14871
|
+
* @vue/runtime-core v3.5.38
|
|
14776
14872
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
14777
14873
|
* @license MIT
|
|
14778
14874
|
**/
|
|
@@ -17504,13 +17600,21 @@ function defineAsyncComponent(source) {
|
|
|
17504
17600
|
const loaded = (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_0__.ref)(false);
|
|
17505
17601
|
const error = (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_0__.ref)();
|
|
17506
17602
|
const delayed = (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_0__.ref)(!!delay);
|
|
17603
|
+
let timeoutTimer;
|
|
17604
|
+
let delayTimer;
|
|
17605
|
+
onUnmounted(() => {
|
|
17606
|
+
if (timeoutTimer != null) clearTimeout(timeoutTimer);
|
|
17607
|
+
if (delayTimer != null) clearTimeout(delayTimer);
|
|
17608
|
+
});
|
|
17507
17609
|
if (delay) {
|
|
17508
|
-
setTimeout(() => {
|
|
17610
|
+
delayTimer = setTimeout(() => {
|
|
17611
|
+
if (instance.isUnmounted) return;
|
|
17509
17612
|
delayed.value = false;
|
|
17510
17613
|
}, delay);
|
|
17511
17614
|
}
|
|
17512
17615
|
if (timeout != null) {
|
|
17513
|
-
setTimeout(() => {
|
|
17616
|
+
timeoutTimer = setTimeout(() => {
|
|
17617
|
+
if (instance.isUnmounted) return;
|
|
17514
17618
|
if (!loaded.value && !error.value) {
|
|
17515
17619
|
const err = new Error(
|
|
17516
17620
|
`Async component timed out after ${timeout}ms.`
|
|
@@ -17521,11 +17625,16 @@ function defineAsyncComponent(source) {
|
|
|
17521
17625
|
}, timeout);
|
|
17522
17626
|
}
|
|
17523
17627
|
load().then(() => {
|
|
17628
|
+
if (instance.isUnmounted) return;
|
|
17524
17629
|
loaded.value = true;
|
|
17525
17630
|
if (instance.parent && isKeepAlive(instance.parent.vnode)) {
|
|
17526
17631
|
instance.parent.update();
|
|
17527
17632
|
}
|
|
17528
17633
|
}).catch((err) => {
|
|
17634
|
+
if (instance.isUnmounted) {
|
|
17635
|
+
pendingRequest = null;
|
|
17636
|
+
return;
|
|
17637
|
+
}
|
|
17529
17638
|
onError(err);
|
|
17530
17639
|
error.value = err;
|
|
17531
17640
|
});
|
|
@@ -19107,13 +19216,20 @@ function useModel(props, name, options = _vue_shared__WEBPACK_IMPORTED_MODULE_1_
|
|
|
19107
19216
|
return;
|
|
19108
19217
|
}
|
|
19109
19218
|
const rawProps = i.vnode.props;
|
|
19110
|
-
|
|
19111
|
-
(name in rawProps || camelizedName in rawProps || hyphenatedName in rawProps) && (`onUpdate:${name}` in rawProps || `onUpdate:${camelizedName}` in rawProps || `onUpdate:${hyphenatedName}` in rawProps))
|
|
19219
|
+
const hasVModel = !!(rawProps && // check if parent has passed v-model
|
|
19220
|
+
(name in rawProps || camelizedName in rawProps || hyphenatedName in rawProps) && (`onUpdate:${name}` in rawProps || `onUpdate:${camelizedName}` in rawProps || `onUpdate:${hyphenatedName}` in rawProps));
|
|
19221
|
+
if (!hasVModel) {
|
|
19112
19222
|
localValue = value;
|
|
19113
19223
|
trigger();
|
|
19114
19224
|
}
|
|
19115
19225
|
i.emit(`update:${name}`, emittedValue);
|
|
19116
|
-
if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.hasChanged)(value,
|
|
19226
|
+
if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.hasChanged)(value, prevSetValue) && ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.hasChanged)(value, emittedValue) && !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.hasChanged)(emittedValue, prevEmittedValue) || // #13524: browsers differ in when they flush microtasks between
|
|
19227
|
+
// event listeners. If a v-model listener emits an intermediate value
|
|
19228
|
+
// and a following listener restores the model to its previous prop
|
|
19229
|
+
// value before parent updates are flushed, the parent render can be
|
|
19230
|
+
// deduped as having no prop change. Force a local update so DOM state
|
|
19231
|
+
// such as an input's value is synchronized back to the current model.
|
|
19232
|
+
hasVModel && prevSetValue !== _vue_shared__WEBPACK_IMPORTED_MODULE_1__.EMPTY_OBJ && !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.hasChanged)(emittedValue, localValue))) {
|
|
19117
19233
|
trigger();
|
|
19118
19234
|
}
|
|
19119
19235
|
prevSetValue = value;
|
|
@@ -23375,7 +23491,7 @@ function isMemoSame(cached, memo) {
|
|
|
23375
23491
|
return true;
|
|
23376
23492
|
}
|
|
23377
23493
|
|
|
23378
|
-
const version = "3.5.
|
|
23494
|
+
const version = "3.5.38";
|
|
23379
23495
|
const warn = true ? warn$1 : 0;
|
|
23380
23496
|
const ErrorTypeStrings = ErrorTypeStrings$1 ;
|
|
23381
23497
|
const devtools = true ? devtools$1 : 0;
|
|
@@ -23586,7 +23702,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
23586
23702
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-core */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
23587
23703
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
23588
23704
|
/**
|
|
23589
|
-
* @vue/runtime-dom v3.5.
|
|
23705
|
+
* @vue/runtime-dom v3.5.38
|
|
23590
23706
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
23591
23707
|
* @license MIT
|
|
23592
23708
|
**/
|
|
@@ -25048,7 +25164,8 @@ const TransitionGroupImpl = /* @__PURE__ */ decorate({
|
|
|
25048
25164
|
if (children) {
|
|
25049
25165
|
for (let i = 0; i < children.length; i++) {
|
|
25050
25166
|
const child = children[i];
|
|
25051
|
-
if (child.el && child.el instanceof Element
|
|
25167
|
+
if (child.el && child.el instanceof Element && // Hidden v-show nodes have no previous layout box to animate from.
|
|
25168
|
+
!child.el[vShowHidden]) {
|
|
25052
25169
|
prevChildren.push(child);
|
|
25053
25170
|
(0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_0__.setTransitionHooks)(
|
|
25054
25171
|
child,
|
|
@@ -25684,7 +25801,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
25684
25801
|
/* harmony export */ toTypeString: () => (/* binding */ toTypeString)
|
|
25685
25802
|
/* harmony export */ });
|
|
25686
25803
|
/**
|
|
25687
|
-
* @vue/shared v3.5.
|
|
25804
|
+
* @vue/shared v3.5.38
|
|
25688
25805
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
25689
25806
|
* @license MIT
|
|
25690
25807
|
**/
|
|
@@ -50528,6 +50645,128 @@ module.exports = {
|
|
|
50528
50645
|
|
|
50529
50646
|
"use strict";
|
|
50530
50647
|
|
|
50648
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
50649
|
+
exports.setUint32LE = exports.readUint8 = exports.readInt32LE = exports.UUID = exports.Timestamp = exports.serialize = exports.ObjectId = exports.NumberUtils = exports.MinKey = exports.MaxKey = exports.Long = exports.Int32 = exports.EJSON = exports.Double = exports.deserialize = exports.Decimal128 = exports.DBRef = exports.Code = exports.calculateObjectSize = exports.ByteUtils = exports.BSONType = exports.BSONSymbol = exports.BSONRegExp = exports.BSONError = exports.BSON = exports.Binary = void 0;
|
|
50650
|
+
exports.parseToElementsToArray = parseToElementsToArray;
|
|
50651
|
+
exports.pluckBSONSerializeOptions = pluckBSONSerializeOptions;
|
|
50652
|
+
exports.resolveBSONOptions = resolveBSONOptions;
|
|
50653
|
+
exports.parseUtf8ValidationOption = parseUtf8ValidationOption;
|
|
50654
|
+
/* eslint-disable no-restricted-imports */
|
|
50655
|
+
const bson_1 = __webpack_require__(/*! bson */ "./node_modules/bson/lib/bson.mjs");
|
|
50656
|
+
var bson_2 = __webpack_require__(/*! bson */ "./node_modules/bson/lib/bson.mjs");
|
|
50657
|
+
Object.defineProperty(exports, "Binary", ({ enumerable: true, get: function () { return bson_2.Binary; } }));
|
|
50658
|
+
Object.defineProperty(exports, "BSON", ({ enumerable: true, get: function () { return bson_2.BSON; } }));
|
|
50659
|
+
Object.defineProperty(exports, "BSONError", ({ enumerable: true, get: function () { return bson_2.BSONError; } }));
|
|
50660
|
+
Object.defineProperty(exports, "BSONRegExp", ({ enumerable: true, get: function () { return bson_2.BSONRegExp; } }));
|
|
50661
|
+
Object.defineProperty(exports, "BSONSymbol", ({ enumerable: true, get: function () { return bson_2.BSONSymbol; } }));
|
|
50662
|
+
Object.defineProperty(exports, "BSONType", ({ enumerable: true, get: function () { return bson_2.BSONType; } }));
|
|
50663
|
+
Object.defineProperty(exports, "ByteUtils", ({ enumerable: true, get: function () { return bson_2.ByteUtils; } }));
|
|
50664
|
+
Object.defineProperty(exports, "calculateObjectSize", ({ enumerable: true, get: function () { return bson_2.calculateObjectSize; } }));
|
|
50665
|
+
Object.defineProperty(exports, "Code", ({ enumerable: true, get: function () { return bson_2.Code; } }));
|
|
50666
|
+
Object.defineProperty(exports, "DBRef", ({ enumerable: true, get: function () { return bson_2.DBRef; } }));
|
|
50667
|
+
Object.defineProperty(exports, "Decimal128", ({ enumerable: true, get: function () { return bson_2.Decimal128; } }));
|
|
50668
|
+
Object.defineProperty(exports, "deserialize", ({ enumerable: true, get: function () { return bson_2.deserialize; } }));
|
|
50669
|
+
Object.defineProperty(exports, "Double", ({ enumerable: true, get: function () { return bson_2.Double; } }));
|
|
50670
|
+
Object.defineProperty(exports, "EJSON", ({ enumerable: true, get: function () { return bson_2.EJSON; } }));
|
|
50671
|
+
Object.defineProperty(exports, "Int32", ({ enumerable: true, get: function () { return bson_2.Int32; } }));
|
|
50672
|
+
Object.defineProperty(exports, "Long", ({ enumerable: true, get: function () { return bson_2.Long; } }));
|
|
50673
|
+
Object.defineProperty(exports, "MaxKey", ({ enumerable: true, get: function () { return bson_2.MaxKey; } }));
|
|
50674
|
+
Object.defineProperty(exports, "MinKey", ({ enumerable: true, get: function () { return bson_2.MinKey; } }));
|
|
50675
|
+
Object.defineProperty(exports, "NumberUtils", ({ enumerable: true, get: function () { return bson_2.NumberUtils; } }));
|
|
50676
|
+
Object.defineProperty(exports, "ObjectId", ({ enumerable: true, get: function () { return bson_2.ObjectId; } }));
|
|
50677
|
+
Object.defineProperty(exports, "serialize", ({ enumerable: true, get: function () { return bson_2.serialize; } }));
|
|
50678
|
+
Object.defineProperty(exports, "Timestamp", ({ enumerable: true, get: function () { return bson_2.Timestamp; } }));
|
|
50679
|
+
Object.defineProperty(exports, "UUID", ({ enumerable: true, get: function () { return bson_2.UUID; } }));
|
|
50680
|
+
function parseToElementsToArray(bytes, offset) {
|
|
50681
|
+
const res = bson_1.BSON.onDemand.parseToElements(bytes, offset);
|
|
50682
|
+
return Array.isArray(res) ? res : [...res];
|
|
50683
|
+
}
|
|
50684
|
+
// validates buffer inputs, used for read operations
|
|
50685
|
+
const validateBufferInputs = (buffer, offset, length) => {
|
|
50686
|
+
if (offset < 0 || offset + length > buffer.length) {
|
|
50687
|
+
throw new RangeError(`Attempt to access memory outside buffer bounds: buffer length: ${buffer.length}, offset: ${offset}, length: ${length}`);
|
|
50688
|
+
}
|
|
50689
|
+
};
|
|
50690
|
+
// readInt32LE, reads a 32-bit integer from buffer at given offset
|
|
50691
|
+
// throws if offset is out of bounds
|
|
50692
|
+
const readInt32LE = (buffer, offset) => {
|
|
50693
|
+
validateBufferInputs(buffer, offset, 4);
|
|
50694
|
+
return bson_1.NumberUtils.getInt32LE(buffer, offset);
|
|
50695
|
+
};
|
|
50696
|
+
exports.readInt32LE = readInt32LE;
|
|
50697
|
+
// readUint8, reads a single unsigned byte from buffer at given offset
|
|
50698
|
+
const readUint8 = (buffer, offset) => {
|
|
50699
|
+
validateBufferInputs(buffer, offset, 1);
|
|
50700
|
+
return buffer[offset];
|
|
50701
|
+
};
|
|
50702
|
+
exports.readUint8 = readUint8;
|
|
50703
|
+
const setUint32LE = (destination, offset, value) => {
|
|
50704
|
+
destination[offset] = value;
|
|
50705
|
+
value >>>= 8;
|
|
50706
|
+
destination[offset + 1] = value;
|
|
50707
|
+
value >>>= 8;
|
|
50708
|
+
destination[offset + 2] = value;
|
|
50709
|
+
value >>>= 8;
|
|
50710
|
+
destination[offset + 3] = value;
|
|
50711
|
+
return 4;
|
|
50712
|
+
};
|
|
50713
|
+
exports.setUint32LE = setUint32LE;
|
|
50714
|
+
function pluckBSONSerializeOptions(options) {
|
|
50715
|
+
const { fieldsAsRaw, useBigInt64, promoteValues, promoteBuffers, promoteLongs, serializeFunctions, ignoreUndefined, bsonRegExp, raw, enableUtf8Validation } = options;
|
|
50716
|
+
return {
|
|
50717
|
+
fieldsAsRaw,
|
|
50718
|
+
useBigInt64,
|
|
50719
|
+
promoteValues,
|
|
50720
|
+
promoteBuffers,
|
|
50721
|
+
promoteLongs,
|
|
50722
|
+
serializeFunctions,
|
|
50723
|
+
ignoreUndefined,
|
|
50724
|
+
bsonRegExp,
|
|
50725
|
+
raw,
|
|
50726
|
+
enableUtf8Validation
|
|
50727
|
+
};
|
|
50728
|
+
}
|
|
50729
|
+
/**
|
|
50730
|
+
* Merge the given BSONSerializeOptions, preferring options over the parent's options, and
|
|
50731
|
+
* substituting defaults for values not set.
|
|
50732
|
+
*
|
|
50733
|
+
* @internal
|
|
50734
|
+
*/
|
|
50735
|
+
function resolveBSONOptions(options, parent) {
|
|
50736
|
+
const parentOptions = parent?.bsonOptions;
|
|
50737
|
+
return {
|
|
50738
|
+
raw: options?.raw ?? parentOptions?.raw ?? false,
|
|
50739
|
+
useBigInt64: options?.useBigInt64 ?? parentOptions?.useBigInt64 ?? false,
|
|
50740
|
+
promoteLongs: options?.promoteLongs ?? parentOptions?.promoteLongs ?? true,
|
|
50741
|
+
promoteValues: options?.promoteValues ?? parentOptions?.promoteValues ?? true,
|
|
50742
|
+
promoteBuffers: options?.promoteBuffers ?? parentOptions?.promoteBuffers ?? false,
|
|
50743
|
+
ignoreUndefined: options?.ignoreUndefined ?? parentOptions?.ignoreUndefined ?? false,
|
|
50744
|
+
bsonRegExp: options?.bsonRegExp ?? parentOptions?.bsonRegExp ?? false,
|
|
50745
|
+
serializeFunctions: options?.serializeFunctions ?? parentOptions?.serializeFunctions ?? false,
|
|
50746
|
+
fieldsAsRaw: options?.fieldsAsRaw ?? parentOptions?.fieldsAsRaw ?? {},
|
|
50747
|
+
enableUtf8Validation: options?.enableUtf8Validation ?? parentOptions?.enableUtf8Validation ?? true
|
|
50748
|
+
};
|
|
50749
|
+
}
|
|
50750
|
+
/** @internal */
|
|
50751
|
+
function parseUtf8ValidationOption(options) {
|
|
50752
|
+
const enableUtf8Validation = options?.enableUtf8Validation;
|
|
50753
|
+
if (enableUtf8Validation === false) {
|
|
50754
|
+
return { utf8: false };
|
|
50755
|
+
}
|
|
50756
|
+
return { utf8: { writeErrors: false } };
|
|
50757
|
+
}
|
|
50758
|
+
//# sourceMappingURL=bson.js.map
|
|
50759
|
+
|
|
50760
|
+
/***/ },
|
|
50761
|
+
|
|
50762
|
+
/***/ "./node_modules/mongoose/node_modules/mongodb/lib/bson.js"
|
|
50763
|
+
/*!****************************************************************!*\
|
|
50764
|
+
!*** ./node_modules/mongoose/node_modules/mongodb/lib/bson.js ***!
|
|
50765
|
+
\****************************************************************/
|
|
50766
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
50767
|
+
|
|
50768
|
+
"use strict";
|
|
50769
|
+
|
|
50531
50770
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
50532
50771
|
exports.setUint32LE = exports.readInt32LE = exports.UUID = exports.Timestamp = exports.serialize = exports.ObjectId = exports.NumberUtils = exports.MinKey = exports.MaxKey = exports.Long = exports.Int32 = exports.EJSON = exports.Double = exports.deserialize = exports.Decimal128 = exports.DBRef = exports.Code = exports.calculateObjectSize = exports.ByteUtils = exports.BSONType = exports.BSONSymbol = exports.BSONRegExp = exports.BSONError = exports.BSON = exports.Binary = void 0;
|
|
50533
50772
|
exports.parseToElementsToArray = parseToElementsToArray;
|
|
@@ -51393,7 +51632,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
51393
51632
|
/* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js");
|
|
51394
51633
|
/* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js");
|
|
51395
51634
|
/**
|
|
51396
|
-
* vue v3.5.
|
|
51635
|
+
* vue v3.5.38
|
|
51397
51636
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
51398
51637
|
* @license MIT
|
|
51399
51638
|
**/
|
|
@@ -52745,7 +52984,7 @@ module.exports = "<div class=\"dashboard-editor p-4 bg-muted rounded-lg shadow-l
|
|
|
52745
52984
|
(module) {
|
|
52746
52985
|
|
|
52747
52986
|
"use strict";
|
|
52748
|
-
module.exports = "<div class=\"dashboards bg-page min-h-[calc(100vh-55px)]\">\n <div class=\"max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-lg font-semibold text-content\">Dashboards</h1>\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"rounded-md bg-primary px-3 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n New Dashboard\n </button>\n </div>\n\n <div v-if=\"status === 'loading'\" class=\"text-center py-12\">\n <svg\n class=\"inline w-8 h-8 animate-spin text-gray-400\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\"></path>\n </svg>\n </div>\n\n <div v-else-if=\"dashboards.length === 0\" class=\"text-center py-12\">\n <h3 class=\"text-sm font-semibold text-content\">No dashboards yet</h3>\n <p class=\"mt-1 text-sm text-content-tertiary\">Get started by creating a new dashboard.</p>\n <div class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover\">\n <svg class=\"-ml-0.5 mr-1.5 h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n New Dashboard\n </button>\n </div>\n </div>\n\n <div v-else class=\"space-y-3\">\n <div\n v-for=\"dashboard in
|
|
52987
|
+
module.exports = "<div class=\"dashboards bg-page min-h-[calc(100vh-55px)]\">\n <div class=\"max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8\">\n <div class=\"flex items-center justify-between mb-6\">\n <h1 class=\"text-lg font-semibold text-content\">Dashboards</h1>\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"rounded-md bg-primary px-3 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n New Dashboard\n </button>\n </div>\n\n <div v-if=\"status !== 'loading'\" class=\"mb-5\">\n <label for=\"dashboard-search\" class=\"sr-only\">Search dashboards</label>\n <div class=\"relative max-w-lg\">\n <div class=\"pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-content-tertiary\">\n <svg class=\"h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n </div>\n <input\n id=\"dashboard-search\"\n v-model=\"searchText\"\n type=\"search\"\n class=\"block w-full rounded-md border-0 bg-surface py-2 pl-9 pr-3 text-sm text-content shadow-sm ring-1 ring-inset ring-edge placeholder:text-content-tertiary focus:ring-2 focus:ring-inset focus:ring-primary\"\n placeholder=\"Search by title or description\">\n </div>\n </div>\n\n <div v-if=\"status === 'loading'\" class=\"text-center py-12\">\n <svg\n class=\"inline w-8 h-8 animate-spin text-gray-400\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\"></path>\n </svg>\n </div>\n\n <div v-else-if=\"dashboards.length === 0\" class=\"text-center py-12\">\n <h3 class=\"text-sm font-semibold text-content\">No dashboards yet</h3>\n <p class=\"mt-1 text-sm text-content-tertiary\">Get started by creating a new dashboard.</p>\n <div class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"showCreateDashboardModal = true\"\n class=\"inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover\">\n <svg class=\"-ml-0.5 mr-1.5 h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z\" />\n </svg>\n New Dashboard\n </button>\n </div>\n </div>\n\n <div v-else-if=\"filteredDashboards.length === 0\" class=\"text-center py-12\">\n <h3 class=\"text-sm font-semibold text-content\">No matching dashboards</h3>\n <p class=\"mt-1 text-sm text-content-tertiary\">Try a different title or description.</p>\n </div>\n\n <div v-else class=\"space-y-3\">\n <div\n v-for=\"dashboard in filteredDashboards\"\n :key=\"dashboard._id\"\n class=\"bg-surface rounded-lg border border-edge px-5 py-4 flex items-start justify-between hover:border-edge-strong transition-colors\"\n >\n <div class=\"min-w-0 flex-1 mr-4\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id\"\n class=\"text-sm font-semibold text-content hover:text-primary block truncate\">\n <span v-html=\"highlightTitle(dashboard.title)\"></span>\n </router-link>\n <p\n v-if=\"dashboard.description\"\n class=\"mt-0.5 text-sm text-content-tertiary truncate\"\n v-html=\"highlightDescription(dashboard.description)\"></p>\n <dl class=\"mt-3 grid grid-cols-1 gap-x-4 gap-y-1 text-xs text-content-tertiary sm:grid-cols-3\">\n <div>\n <dt class=\"font-medium text-content-secondary\">Created At</dt>\n <dd>{{ formatDate(dashboard.createdAt) }}</dd>\n </div>\n <div>\n <dt class=\"font-medium text-content-secondary\">Last Evaluated At</dt>\n <dd>{{ formatDate(dashboard.lastEvaluatedAt) }}</dd>\n </div>\n <div v-show=\"dashboard.createdBy\">\n <dt class=\"font-medium text-content-secondary\">Created By</dt>\n <dd class=\"truncate\">{{ dashboard.createdBy?.name }}</dd>\n </div>\n </dl>\n </div>\n <div class=\"flex items-center gap-2 shrink-0\">\n <button\n type=\"button\"\n @click=\"togglePin(dashboard)\"\n :title=\"dashboard.isPinned ? 'Unpin dashboard' : 'Pin dashboard'\"\n :aria-label=\"dashboard.isPinned ? 'Unpin dashboard' : 'Pin dashboard'\"\n class=\"rounded-md bg-surface px-2 py-1.5 text-content-tertiary ring-1 ring-inset ring-gray-300 hover:bg-page\"\n :class=\"dashboard.isPinned ? 'text-primary ring-primary/40' : ''\">\n <img :src=\"dashboard.isPinned ? 'images/pin-off.svg' : 'images/pin.svg'\" class=\"w-4 h-4\" alt=\"\" />\n </button>\n <router-link\n :to=\"'/dashboard/' + dashboard._id\"\n class=\"rounded-md bg-primary px-3 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover\">\n View\n </router-link>\n <div class=\"relative\" v-click-outside=\"() => closeMenu(dashboard._id)\">\n <button\n type=\"button\"\n @click=\"toggleMenu(dashboard._id)\"\n class=\"rounded-md bg-surface px-2 py-1.5 text-content-tertiary ring-1 ring-inset ring-gray-300 hover:bg-page\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm6 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm6 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z\" />\n </svg>\n </button>\n <div\n v-if=\"openMenuId === dashboard._id\"\n class=\"absolute right-0 mt-1 w-36 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black/5 z-10\"\n >\n <div class=\"py-1\">\n <router-link\n :to=\"'/dashboard/' + dashboard._id + '?edit=true'\"\n class=\"block px-4 py-2 text-sm text-content-secondary hover:bg-muted\">\n Edit\n </router-link>\n <button\n @click=\"showDeleteDashboardModal = dashboard; openMenuId = null\"\n type=\"button\"\n class=\"block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-muted\">\n Delete\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <modal v-if=\"showCreateDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showCreateDashboardModal = false;\">×</div>\n <create-dashboard @close=\"insertNewDashboard\"></create-dashboard>\n </template>\n </modal>\n\n <modal v-if=\"showDeleteDashboardModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showDeleteDashboardModal = null;\">×</div>\n <h2>Are you sure you want to delete \"{{showDeleteDashboardModal.title}}\"?</h2>\n <div class=\"flex space-x-2 mt-4\">\n <button class=\"px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-500 text-sm font-semibold\" @click=\"deleteDashboard(showDeleteDashboardModal)\">Yes, delete</button>\n <button class=\"px-4 py-2 bg-muted text-content-secondary rounded-md hover:bg-muted text-sm font-semibold\" @click=\"showDeleteDashboardModal=null;\">Cancel</button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
52749
52988
|
|
|
52750
52989
|
/***/ },
|
|
52751
52990
|
|
|
@@ -58836,7 +59075,7 @@ exports.validatorErrorSymbol = Symbol('mongoose#validatorError');
|
|
|
58836
59075
|
|
|
58837
59076
|
|
|
58838
59077
|
|
|
58839
|
-
const ObjectId = (__webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongodb/lib/bson.js").ObjectId);
|
|
59078
|
+
const ObjectId = (__webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongoose/node_modules/mongodb/lib/bson.js").ObjectId);
|
|
58840
59079
|
const objectIdSymbol = (__webpack_require__(/*! ../helpers/symbols */ "./node_modules/mongoose/lib/helpers/symbols.js").objectIdSymbol);
|
|
58841
59080
|
|
|
58842
59081
|
/**
|
|
@@ -64736,7 +64975,7 @@ var src_default = VueToastificationPlugin;
|
|
|
64736
64975
|
(module) {
|
|
64737
64976
|
|
|
64738
64977
|
"use strict";
|
|
64739
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.
|
|
64978
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.7","description":"AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.","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","mpath":"^0.9.0","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","time-commando":"1.0.1","typescript":"^6.0.2","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"@nestjs/common":"10.x || 11.x","mongoose":"7.x || 8.x || ^9.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"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","@nestjs/common":"^11.1.9","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","eval:agent":"node eval/agent/run.js"}}');
|
|
64740
64979
|
|
|
64741
64980
|
/***/ }
|
|
64742
64981
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor"><path d="M680-840v80h-40v327l-80-80v-247H400v87l-87-87-33-33v-47h400ZM480-40l-40-40v-240H240v-80l80-80v-46L56-792l56-56 736 736-58 56-264-264h-6v240l-40 40ZM354-400h92l-44-44-2-2-46 46Zm126-193Zm-78 149Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor"><path d="m640-480 80 80v80H520v240l-40 40-40-40v-240H240v-80l80-80v-280h-40v-80h400v80h-40v280Zm-286 80h252l-46-46v-314H400v314l-46 46Zm126 0Z"/></svg>
|
package/frontend/public/tw.css
CHANGED
|
@@ -905,6 +905,10 @@ video {
|
|
|
905
905
|
margin-bottom: 1rem;
|
|
906
906
|
}
|
|
907
907
|
|
|
908
|
+
.mb-5 {
|
|
909
|
+
margin-bottom: 1.25rem;
|
|
910
|
+
}
|
|
911
|
+
|
|
908
912
|
.mb-6 {
|
|
909
913
|
margin-bottom: 1.5rem;
|
|
910
914
|
}
|
|
@@ -1681,6 +1685,10 @@ video {
|
|
|
1681
1685
|
column-gap: 1.5rem;
|
|
1682
1686
|
}
|
|
1683
1687
|
|
|
1688
|
+
.gap-y-1 {
|
|
1689
|
+
row-gap: 0.25rem;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1684
1692
|
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
|
1685
1693
|
--tw-space-x-reverse: 0;
|
|
1686
1694
|
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
|
@@ -2524,6 +2532,10 @@ video {
|
|
|
2524
2532
|
padding-left: 2rem;
|
|
2525
2533
|
}
|
|
2526
2534
|
|
|
2535
|
+
.pl-9 {
|
|
2536
|
+
padding-left: 2.25rem;
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2527
2539
|
.pr-12 {
|
|
2528
2540
|
padding-right: 3rem;
|
|
2529
2541
|
}
|
|
@@ -2927,6 +2939,10 @@ video {
|
|
|
2927
2939
|
text-decoration-line: underline;
|
|
2928
2940
|
}
|
|
2929
2941
|
|
|
2942
|
+
.decoration-primary {
|
|
2943
|
+
text-decoration-color: var(--color-primary);
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2930
2946
|
.decoration-dotted {
|
|
2931
2947
|
text-decoration-style: dotted;
|
|
2932
2948
|
}
|
|
@@ -3063,6 +3079,10 @@ video {
|
|
|
3063
3079
|
--tw-ring-color: rgb(0 0 0 / 0.05);
|
|
3064
3080
|
}
|
|
3065
3081
|
|
|
3082
|
+
.ring-edge {
|
|
3083
|
+
--tw-ring-color: var(--color-edge);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3066
3086
|
.ring-edge-strong {
|
|
3067
3087
|
--tw-ring-color: var(--color-edge-strong);
|
|
3068
3088
|
}
|
|
@@ -3650,6 +3670,10 @@ video {
|
|
|
3650
3670
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
3651
3671
|
}
|
|
3652
3672
|
|
|
3673
|
+
.focus\:ring-inset:focus {
|
|
3674
|
+
--tw-ring-inset: inset;
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3653
3677
|
.focus\:ring-blue-500:focus {
|
|
3654
3678
|
--tw-ring-opacity: 1;
|
|
3655
3679
|
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
|
|
@@ -3896,6 +3920,10 @@ video {
|
|
|
3896
3920
|
flex: none;
|
|
3897
3921
|
}
|
|
3898
3922
|
|
|
3923
|
+
.sm\:grid-cols-3 {
|
|
3924
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3899
3927
|
.sm\:grid-cols-4 {
|
|
3900
3928
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
3901
3929
|
}
|
|
@@ -10,6 +10,23 @@
|
|
|
10
10
|
</button>
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
|
+
<div v-if="status !== 'loading'" class="mb-5">
|
|
14
|
+
<label for="dashboard-search" class="sr-only">Search dashboards</label>
|
|
15
|
+
<div class="relative max-w-lg">
|
|
16
|
+
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-content-tertiary">
|
|
17
|
+
<svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
18
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
|
19
|
+
</svg>
|
|
20
|
+
</div>
|
|
21
|
+
<input
|
|
22
|
+
id="dashboard-search"
|
|
23
|
+
v-model="searchText"
|
|
24
|
+
type="search"
|
|
25
|
+
class="block w-full rounded-md border-0 bg-surface py-2 pl-9 pr-3 text-sm text-content shadow-sm ring-1 ring-inset ring-edge placeholder:text-content-tertiary focus:ring-2 focus:ring-inset focus:ring-primary"
|
|
26
|
+
placeholder="Search by title or description">
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
13
30
|
<div v-if="status === 'loading'" class="text-center py-12">
|
|
14
31
|
<svg
|
|
15
32
|
class="inline w-8 h-8 animate-spin text-gray-400"
|
|
@@ -38,21 +55,52 @@
|
|
|
38
55
|
</div>
|
|
39
56
|
</div>
|
|
40
57
|
|
|
58
|
+
<div v-else-if="filteredDashboards.length === 0" class="text-center py-12">
|
|
59
|
+
<h3 class="text-sm font-semibold text-content">No matching dashboards</h3>
|
|
60
|
+
<p class="mt-1 text-sm text-content-tertiary">Try a different title or description.</p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
41
63
|
<div v-else class="space-y-3">
|
|
42
64
|
<div
|
|
43
|
-
v-for="dashboard in
|
|
65
|
+
v-for="dashboard in filteredDashboards"
|
|
44
66
|
:key="dashboard._id"
|
|
45
|
-
class="bg-surface rounded-lg border border-edge px-5 py-4 flex items-
|
|
67
|
+
class="bg-surface rounded-lg border border-edge px-5 py-4 flex items-start justify-between hover:border-edge-strong transition-colors"
|
|
46
68
|
>
|
|
47
69
|
<div class="min-w-0 flex-1 mr-4">
|
|
48
70
|
<router-link
|
|
49
71
|
:to="'/dashboard/' + dashboard._id"
|
|
50
72
|
class="text-sm font-semibold text-content hover:text-primary block truncate">
|
|
51
|
-
|
|
73
|
+
<span v-html="highlightTitle(dashboard.title)"></span>
|
|
52
74
|
</router-link>
|
|
53
|
-
<p
|
|
75
|
+
<p
|
|
76
|
+
v-if="dashboard.description"
|
|
77
|
+
class="mt-0.5 text-sm text-content-tertiary truncate"
|
|
78
|
+
v-html="highlightDescription(dashboard.description)"></p>
|
|
79
|
+
<dl class="mt-3 grid grid-cols-1 gap-x-4 gap-y-1 text-xs text-content-tertiary sm:grid-cols-3">
|
|
80
|
+
<div>
|
|
81
|
+
<dt class="font-medium text-content-secondary">Created At</dt>
|
|
82
|
+
<dd>{{ formatDate(dashboard.createdAt) }}</dd>
|
|
83
|
+
</div>
|
|
84
|
+
<div>
|
|
85
|
+
<dt class="font-medium text-content-secondary">Last Evaluated At</dt>
|
|
86
|
+
<dd>{{ formatDate(dashboard.lastEvaluatedAt) }}</dd>
|
|
87
|
+
</div>
|
|
88
|
+
<div v-show="dashboard.createdBy">
|
|
89
|
+
<dt class="font-medium text-content-secondary">Created By</dt>
|
|
90
|
+
<dd class="truncate">{{ dashboard.createdBy?.name }}</dd>
|
|
91
|
+
</div>
|
|
92
|
+
</dl>
|
|
54
93
|
</div>
|
|
55
94
|
<div class="flex items-center gap-2 shrink-0">
|
|
95
|
+
<button
|
|
96
|
+
type="button"
|
|
97
|
+
@click="togglePin(dashboard)"
|
|
98
|
+
:title="dashboard.isPinned ? 'Unpin dashboard' : 'Pin dashboard'"
|
|
99
|
+
:aria-label="dashboard.isPinned ? 'Unpin dashboard' : 'Pin dashboard'"
|
|
100
|
+
class="rounded-md bg-surface px-2 py-1.5 text-content-tertiary ring-1 ring-inset ring-gray-300 hover:bg-page"
|
|
101
|
+
:class="dashboard.isPinned ? 'text-primary ring-primary/40' : ''">
|
|
102
|
+
<img :src="dashboard.isPinned ? 'images/pin-off.svg' : 'images/pin.svg'" class="w-4 h-4" alt="" />
|
|
103
|
+
</button>
|
|
56
104
|
<router-link
|
|
57
105
|
:to="'/dashboard/' + dashboard._id"
|
|
58
106
|
class="rounded-md bg-primary px-3 py-1.5 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover">
|
|
@@ -9,11 +9,73 @@ module.exports = app => app.component('dashboards', {
|
|
|
9
9
|
data: () => ({
|
|
10
10
|
status: 'loading',
|
|
11
11
|
dashboards: [],
|
|
12
|
+
searchText: '',
|
|
12
13
|
showCreateDashboardModal: false,
|
|
13
14
|
showDeleteDashboardModal: null,
|
|
14
15
|
openMenuId: null
|
|
15
16
|
}),
|
|
17
|
+
computed: {
|
|
18
|
+
filteredDashboards() {
|
|
19
|
+
const searchWords = getSearchWords(this.searchText);
|
|
20
|
+
let dashboards = this.dashboards;
|
|
21
|
+
if (searchWords.length === 0) {
|
|
22
|
+
return sortPinnedFirst(dashboards);
|
|
23
|
+
}
|
|
24
|
+
const searchPatterns = searchWords.map(word => new RegExp(escapeRegExp(word), 'i'));
|
|
25
|
+
|
|
26
|
+
dashboards = dashboards.filter(dashboard => {
|
|
27
|
+
const searchableText = `${dashboard.title || ''} ${dashboard.description || ''}`;
|
|
28
|
+
return searchPatterns.every(pattern => pattern.test(searchableText));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return sortPinnedFirst(dashboards);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
16
34
|
methods: {
|
|
35
|
+
highlightTitle(value) {
|
|
36
|
+
return this.highlightSearchText(value, 'underline decoration-primary underline-offset-2');
|
|
37
|
+
},
|
|
38
|
+
highlightDescription(value) {
|
|
39
|
+
return this.highlightSearchText(value, 'font-semibold text-content-secondary');
|
|
40
|
+
},
|
|
41
|
+
highlightSearchText(value, className) {
|
|
42
|
+
const text = value || '';
|
|
43
|
+
const searchWords = getSearchWords(this.searchText);
|
|
44
|
+
if (searchWords.length === 0) {
|
|
45
|
+
return escapeHtml(text);
|
|
46
|
+
}
|
|
47
|
+
const searchPattern = new RegExp(searchWords.map(escapeRegExp).join('|'), 'ig');
|
|
48
|
+
|
|
49
|
+
let html = '';
|
|
50
|
+
let lastIndex = 0;
|
|
51
|
+
let match = searchPattern.exec(text);
|
|
52
|
+
while (match != null) {
|
|
53
|
+
html += escapeHtml(text.slice(lastIndex, match.index));
|
|
54
|
+
html += `<span class="${className}">${escapeHtml(match[0])}</span>`;
|
|
55
|
+
lastIndex = match.index + match[0].length;
|
|
56
|
+
match = searchPattern.exec(text);
|
|
57
|
+
}
|
|
58
|
+
html += escapeHtml(text.slice(lastIndex));
|
|
59
|
+
return html;
|
|
60
|
+
},
|
|
61
|
+
formatDate(value) {
|
|
62
|
+
if (!value) {
|
|
63
|
+
return '-';
|
|
64
|
+
}
|
|
65
|
+
const date = new Date(value);
|
|
66
|
+
if (Number.isNaN(date.getTime())) {
|
|
67
|
+
return '-';
|
|
68
|
+
}
|
|
69
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
70
|
+
dateStyle: 'medium',
|
|
71
|
+
timeStyle: 'short'
|
|
72
|
+
}).format(date);
|
|
73
|
+
},
|
|
74
|
+
formatUser(dashboard) {
|
|
75
|
+
return dashboard.createdBy?.name ||
|
|
76
|
+
dashboard.createdBy?.email ||
|
|
77
|
+
(dashboard.createdById ? dashboard.createdById.toString() : '-');
|
|
78
|
+
},
|
|
17
79
|
toggleMenu(id) {
|
|
18
80
|
this.openMenuId = this.openMenuId === id ? null : id;
|
|
19
81
|
},
|
|
@@ -33,8 +95,19 @@ module.exports = app => app.component('dashboards', {
|
|
|
33
95
|
this.$toast.success('Dashboard deleted!');
|
|
34
96
|
},
|
|
35
97
|
insertNewDashboard(dashboard) {
|
|
36
|
-
this.dashboards.
|
|
98
|
+
this.dashboards.unshift(dashboard);
|
|
37
99
|
this.showCreateDashboardModal = false;
|
|
100
|
+
},
|
|
101
|
+
async togglePin(dashboard) {
|
|
102
|
+
if (!dashboard) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { doc } = await api.Dashboard.updateDashboard({
|
|
107
|
+
dashboardId: dashboard._id,
|
|
108
|
+
isPinned: !dashboard.isPinned
|
|
109
|
+
});
|
|
110
|
+
dashboard.isPinned = doc.isPinned;
|
|
38
111
|
}
|
|
39
112
|
},
|
|
40
113
|
directives: {
|
|
@@ -58,3 +131,25 @@ module.exports = app => app.component('dashboards', {
|
|
|
58
131
|
this.status = 'loaded';
|
|
59
132
|
}
|
|
60
133
|
});
|
|
134
|
+
|
|
135
|
+
function escapeHtml(value) {
|
|
136
|
+
return value.toString().
|
|
137
|
+
replace(/&/g, '&').
|
|
138
|
+
replace(/</g, '<').
|
|
139
|
+
replace(/>/g, '>').
|
|
140
|
+
replace(/"/g, '"').
|
|
141
|
+
replace(/'/g, ''');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function escapeRegExp(value) {
|
|
145
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getSearchWords(searchText) {
|
|
149
|
+
return Array.from(new Set(searchText.trim().split(/\s+/).filter(Boolean))).
|
|
150
|
+
sort((a, b) => b.length - a.length);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function sortPinnedFirst(dashboards) {
|
|
154
|
+
return dashboards.slice().sort((a, b) => Number(Boolean(b.isPinned)) - Number(Boolean(a.isPinned)));
|
|
155
|
+
}
|
package/nest.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
declare module '@mongoosejs/studio/nest' {
|
|
2
|
+
import { DynamicModule, MiddlewareConsumer, NestModule } from '@nestjs/common';
|
|
3
|
+
import { Connection, Mongoose } from 'mongoose';
|
|
4
|
+
|
|
5
|
+
export interface MongooseStudioModuleOptions {
|
|
6
|
+
path?: string;
|
|
7
|
+
apiPath?: string;
|
|
8
|
+
connection?: Connection | Mongoose;
|
|
9
|
+
connectionToken?: string | symbol | Function;
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
bindIp?: string | string[] | null;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MongooseStudioModuleAsyncOptions {
|
|
16
|
+
imports?: any[];
|
|
17
|
+
inject?: any[];
|
|
18
|
+
connectionToken?: string | symbol | Function;
|
|
19
|
+
useFactory: (...args: any[]) => Promise<MongooseStudioModuleOptions> | MongooseStudioModuleOptions;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class MongooseStudioModule implements NestModule {
|
|
23
|
+
configure(consumer: MiddlewareConsumer): void;
|
|
24
|
+
static forRoot(options?: MongooseStudioModuleOptions): DynamicModule;
|
|
25
|
+
static forRootAsync(options: MongooseStudioModuleAsyncOptions): DynamicModule;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/nest.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Inject, Module, RequestMethod } = require('@nestjs/common');
|
|
4
|
+
const mongoose = require('mongoose');
|
|
5
|
+
const createExpressRouter = require('./express');
|
|
6
|
+
|
|
7
|
+
const MONGOOSE_STUDIO_OPTIONS = Symbol('MONGOOSE_STUDIO_OPTIONS');
|
|
8
|
+
const MONGOOSE_STUDIO_CONNECTION = Symbol('MONGOOSE_STUDIO_CONNECTION');
|
|
9
|
+
const MONGOOSE_STUDIO_EXPRESS_ROUTER = Symbol('MONGOOSE_STUDIO_EXPRESS_ROUTER');
|
|
10
|
+
|
|
11
|
+
class MongooseStudioModule {
|
|
12
|
+
constructor(options, expressRouter) {
|
|
13
|
+
this.options = options || {};
|
|
14
|
+
this.expressRouter = expressRouter;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
configure(consumer) {
|
|
18
|
+
const mountPath = this.options.path || '/studio';
|
|
19
|
+
|
|
20
|
+
consumer
|
|
21
|
+
.apply((req, res, next) => {
|
|
22
|
+
if (!isMountedStudioRequest(req, mountPath)) {
|
|
23
|
+
return next();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const originalUrl = req.url;
|
|
27
|
+
req.url = req.url.slice(normalizeExpressMountPath(mountPath).length) || '/';
|
|
28
|
+
try {
|
|
29
|
+
return this.expressRouter(req, res, err => {
|
|
30
|
+
req.url = originalUrl;
|
|
31
|
+
next(err);
|
|
32
|
+
});
|
|
33
|
+
} finally {
|
|
34
|
+
req.url = originalUrl;
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
.forRoutes({
|
|
38
|
+
path: '*path',
|
|
39
|
+
method: RequestMethod.ALL
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static forRoot(options) {
|
|
44
|
+
options = options || {};
|
|
45
|
+
const providers = [
|
|
46
|
+
{ provide: MONGOOSE_STUDIO_OPTIONS, useValue: options },
|
|
47
|
+
createExpressRouterProvider()
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
if (options.connectionToken) {
|
|
51
|
+
providers.push({ provide: MONGOOSE_STUDIO_CONNECTION, useExisting: options.connectionToken });
|
|
52
|
+
} else if (options.connection) {
|
|
53
|
+
providers.push({ provide: MONGOOSE_STUDIO_CONNECTION, useValue: options.connection });
|
|
54
|
+
} else {
|
|
55
|
+
providers.push({ provide: MONGOOSE_STUDIO_CONNECTION, useValue: mongoose });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
module: MongooseStudioModule,
|
|
60
|
+
providers,
|
|
61
|
+
exports: providers
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static forRootAsync(options) {
|
|
66
|
+
options = options || {};
|
|
67
|
+
const providers = [
|
|
68
|
+
{
|
|
69
|
+
provide: MONGOOSE_STUDIO_OPTIONS,
|
|
70
|
+
useFactory: options.useFactory,
|
|
71
|
+
inject: options.inject || []
|
|
72
|
+
},
|
|
73
|
+
createExpressRouterProvider()
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
if (options.connectionToken) {
|
|
77
|
+
providers.push({ provide: MONGOOSE_STUDIO_CONNECTION, useExisting: options.connectionToken });
|
|
78
|
+
} else if (options.connection) {
|
|
79
|
+
providers.push({ provide: MONGOOSE_STUDIO_CONNECTION, useValue: options.connection });
|
|
80
|
+
} else {
|
|
81
|
+
providers.push({ provide: MONGOOSE_STUDIO_CONNECTION, useValue: mongoose });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
module: MongooseStudioModule,
|
|
86
|
+
imports: options.imports || [],
|
|
87
|
+
providers,
|
|
88
|
+
exports: providers
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createExpressRouterProvider() {
|
|
94
|
+
return {
|
|
95
|
+
provide: MONGOOSE_STUDIO_EXPRESS_ROUTER,
|
|
96
|
+
useFactory: async(options, connection) => {
|
|
97
|
+
options = options || {};
|
|
98
|
+
const mountPath = options.path || '/studio';
|
|
99
|
+
const apiPath = options.apiPath || `${mountPath}/api`;
|
|
100
|
+
const studioOptions = { ...options };
|
|
101
|
+
delete studioOptions.path;
|
|
102
|
+
delete studioOptions.apiPath;
|
|
103
|
+
delete studioOptions.connection;
|
|
104
|
+
delete studioOptions.connectionToken;
|
|
105
|
+
|
|
106
|
+
return createExpressRouter(apiPath, options.connection || connection || mongoose, studioOptions);
|
|
107
|
+
},
|
|
108
|
+
inject: [MONGOOSE_STUDIO_OPTIONS, MONGOOSE_STUDIO_CONNECTION]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isMountedStudioRequest(req, mountPath) {
|
|
113
|
+
const normalizedMountPath = normalizeExpressMountPath(mountPath);
|
|
114
|
+
return req.url === normalizedMountPath ||
|
|
115
|
+
req.url.startsWith(`${normalizedMountPath}/`) ||
|
|
116
|
+
req.url.startsWith(`${normalizedMountPath}?`) ||
|
|
117
|
+
req.url.startsWith(`${normalizedMountPath}#`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeExpressMountPath(path) {
|
|
121
|
+
path = `/${path.replace(/^\/+/, '').replace(/\/+$/, '')}`;
|
|
122
|
+
return path === '/' ? '' : path;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
Inject(MONGOOSE_STUDIO_OPTIONS)(MongooseStudioModule, undefined, 0);
|
|
126
|
+
Inject(MONGOOSE_STUDIO_EXPRESS_ROUTER)(MongooseStudioModule, undefined, 1);
|
|
127
|
+
Module({})(MongooseStudioModule);
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
MONGOOSE_STUDIO_CONNECTION,
|
|
131
|
+
MONGOOSE_STUDIO_EXPRESS_ROUTER,
|
|
132
|
+
MONGOOSE_STUDIO_OPTIONS,
|
|
133
|
+
MongooseStudioModule
|
|
134
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.",
|
|
5
5
|
"homepage": "https://mongoosestudio.app/",
|
|
6
6
|
"repository": {
|
|
@@ -31,8 +31,14 @@
|
|
|
31
31
|
"xss": "^1.0.15"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
+
"@nestjs/common": "10.x || 11.x",
|
|
34
35
|
"mongoose": "7.x || 8.x || ^9.0.0"
|
|
35
36
|
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@nestjs/common": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
36
42
|
"optionalPeerDependencies": {
|
|
37
43
|
"@mongoosejs/task": "0.5.x || 0.6.x"
|
|
38
44
|
},
|
|
@@ -41,6 +47,7 @@
|
|
|
41
47
|
"axios": "1.2.2",
|
|
42
48
|
"dedent": "^1.6.0",
|
|
43
49
|
"eslint": "9.30.0",
|
|
50
|
+
"@nestjs/common": "^11.1.9",
|
|
44
51
|
"express": "4.x",
|
|
45
52
|
"mocha": "10.2.0",
|
|
46
53
|
"mongodb-memory-server": "^11.0.1",
|