@mongoosejs/studio 0.5.5 → 0.5.6
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/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 +1 -0
- package/frontend/public/app.js +221 -4
- 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/package.json +1 -1
|
@@ -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
|
@@ -87,6 +87,7 @@ module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options)
|
|
|
87
87
|
req._internals.initiatedById = user._id;
|
|
88
88
|
req._internals.roles = roles;
|
|
89
89
|
req._internals.$workspaceId = workspace._id;
|
|
90
|
+
req._internals.initiatedBy = user;
|
|
90
91
|
|
|
91
92
|
next();
|
|
92
93
|
})
|
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
|
|
|
@@ -50528,6 +50623,128 @@ module.exports = {
|
|
|
50528
50623
|
|
|
50529
50624
|
"use strict";
|
|
50530
50625
|
|
|
50626
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
50627
|
+
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;
|
|
50628
|
+
exports.parseToElementsToArray = parseToElementsToArray;
|
|
50629
|
+
exports.pluckBSONSerializeOptions = pluckBSONSerializeOptions;
|
|
50630
|
+
exports.resolveBSONOptions = resolveBSONOptions;
|
|
50631
|
+
exports.parseUtf8ValidationOption = parseUtf8ValidationOption;
|
|
50632
|
+
/* eslint-disable no-restricted-imports */
|
|
50633
|
+
const bson_1 = __webpack_require__(/*! bson */ "./node_modules/bson/lib/bson.mjs");
|
|
50634
|
+
var bson_2 = __webpack_require__(/*! bson */ "./node_modules/bson/lib/bson.mjs");
|
|
50635
|
+
Object.defineProperty(exports, "Binary", ({ enumerable: true, get: function () { return bson_2.Binary; } }));
|
|
50636
|
+
Object.defineProperty(exports, "BSON", ({ enumerable: true, get: function () { return bson_2.BSON; } }));
|
|
50637
|
+
Object.defineProperty(exports, "BSONError", ({ enumerable: true, get: function () { return bson_2.BSONError; } }));
|
|
50638
|
+
Object.defineProperty(exports, "BSONRegExp", ({ enumerable: true, get: function () { return bson_2.BSONRegExp; } }));
|
|
50639
|
+
Object.defineProperty(exports, "BSONSymbol", ({ enumerable: true, get: function () { return bson_2.BSONSymbol; } }));
|
|
50640
|
+
Object.defineProperty(exports, "BSONType", ({ enumerable: true, get: function () { return bson_2.BSONType; } }));
|
|
50641
|
+
Object.defineProperty(exports, "ByteUtils", ({ enumerable: true, get: function () { return bson_2.ByteUtils; } }));
|
|
50642
|
+
Object.defineProperty(exports, "calculateObjectSize", ({ enumerable: true, get: function () { return bson_2.calculateObjectSize; } }));
|
|
50643
|
+
Object.defineProperty(exports, "Code", ({ enumerable: true, get: function () { return bson_2.Code; } }));
|
|
50644
|
+
Object.defineProperty(exports, "DBRef", ({ enumerable: true, get: function () { return bson_2.DBRef; } }));
|
|
50645
|
+
Object.defineProperty(exports, "Decimal128", ({ enumerable: true, get: function () { return bson_2.Decimal128; } }));
|
|
50646
|
+
Object.defineProperty(exports, "deserialize", ({ enumerable: true, get: function () { return bson_2.deserialize; } }));
|
|
50647
|
+
Object.defineProperty(exports, "Double", ({ enumerable: true, get: function () { return bson_2.Double; } }));
|
|
50648
|
+
Object.defineProperty(exports, "EJSON", ({ enumerable: true, get: function () { return bson_2.EJSON; } }));
|
|
50649
|
+
Object.defineProperty(exports, "Int32", ({ enumerable: true, get: function () { return bson_2.Int32; } }));
|
|
50650
|
+
Object.defineProperty(exports, "Long", ({ enumerable: true, get: function () { return bson_2.Long; } }));
|
|
50651
|
+
Object.defineProperty(exports, "MaxKey", ({ enumerable: true, get: function () { return bson_2.MaxKey; } }));
|
|
50652
|
+
Object.defineProperty(exports, "MinKey", ({ enumerable: true, get: function () { return bson_2.MinKey; } }));
|
|
50653
|
+
Object.defineProperty(exports, "NumberUtils", ({ enumerable: true, get: function () { return bson_2.NumberUtils; } }));
|
|
50654
|
+
Object.defineProperty(exports, "ObjectId", ({ enumerable: true, get: function () { return bson_2.ObjectId; } }));
|
|
50655
|
+
Object.defineProperty(exports, "serialize", ({ enumerable: true, get: function () { return bson_2.serialize; } }));
|
|
50656
|
+
Object.defineProperty(exports, "Timestamp", ({ enumerable: true, get: function () { return bson_2.Timestamp; } }));
|
|
50657
|
+
Object.defineProperty(exports, "UUID", ({ enumerable: true, get: function () { return bson_2.UUID; } }));
|
|
50658
|
+
function parseToElementsToArray(bytes, offset) {
|
|
50659
|
+
const res = bson_1.BSON.onDemand.parseToElements(bytes, offset);
|
|
50660
|
+
return Array.isArray(res) ? res : [...res];
|
|
50661
|
+
}
|
|
50662
|
+
// validates buffer inputs, used for read operations
|
|
50663
|
+
const validateBufferInputs = (buffer, offset, length) => {
|
|
50664
|
+
if (offset < 0 || offset + length > buffer.length) {
|
|
50665
|
+
throw new RangeError(`Attempt to access memory outside buffer bounds: buffer length: ${buffer.length}, offset: ${offset}, length: ${length}`);
|
|
50666
|
+
}
|
|
50667
|
+
};
|
|
50668
|
+
// readInt32LE, reads a 32-bit integer from buffer at given offset
|
|
50669
|
+
// throws if offset is out of bounds
|
|
50670
|
+
const readInt32LE = (buffer, offset) => {
|
|
50671
|
+
validateBufferInputs(buffer, offset, 4);
|
|
50672
|
+
return bson_1.NumberUtils.getInt32LE(buffer, offset);
|
|
50673
|
+
};
|
|
50674
|
+
exports.readInt32LE = readInt32LE;
|
|
50675
|
+
// readUint8, reads a single unsigned byte from buffer at given offset
|
|
50676
|
+
const readUint8 = (buffer, offset) => {
|
|
50677
|
+
validateBufferInputs(buffer, offset, 1);
|
|
50678
|
+
return buffer[offset];
|
|
50679
|
+
};
|
|
50680
|
+
exports.readUint8 = readUint8;
|
|
50681
|
+
const setUint32LE = (destination, offset, value) => {
|
|
50682
|
+
destination[offset] = value;
|
|
50683
|
+
value >>>= 8;
|
|
50684
|
+
destination[offset + 1] = value;
|
|
50685
|
+
value >>>= 8;
|
|
50686
|
+
destination[offset + 2] = value;
|
|
50687
|
+
value >>>= 8;
|
|
50688
|
+
destination[offset + 3] = value;
|
|
50689
|
+
return 4;
|
|
50690
|
+
};
|
|
50691
|
+
exports.setUint32LE = setUint32LE;
|
|
50692
|
+
function pluckBSONSerializeOptions(options) {
|
|
50693
|
+
const { fieldsAsRaw, useBigInt64, promoteValues, promoteBuffers, promoteLongs, serializeFunctions, ignoreUndefined, bsonRegExp, raw, enableUtf8Validation } = options;
|
|
50694
|
+
return {
|
|
50695
|
+
fieldsAsRaw,
|
|
50696
|
+
useBigInt64,
|
|
50697
|
+
promoteValues,
|
|
50698
|
+
promoteBuffers,
|
|
50699
|
+
promoteLongs,
|
|
50700
|
+
serializeFunctions,
|
|
50701
|
+
ignoreUndefined,
|
|
50702
|
+
bsonRegExp,
|
|
50703
|
+
raw,
|
|
50704
|
+
enableUtf8Validation
|
|
50705
|
+
};
|
|
50706
|
+
}
|
|
50707
|
+
/**
|
|
50708
|
+
* Merge the given BSONSerializeOptions, preferring options over the parent's options, and
|
|
50709
|
+
* substituting defaults for values not set.
|
|
50710
|
+
*
|
|
50711
|
+
* @internal
|
|
50712
|
+
*/
|
|
50713
|
+
function resolveBSONOptions(options, parent) {
|
|
50714
|
+
const parentOptions = parent?.bsonOptions;
|
|
50715
|
+
return {
|
|
50716
|
+
raw: options?.raw ?? parentOptions?.raw ?? false,
|
|
50717
|
+
useBigInt64: options?.useBigInt64 ?? parentOptions?.useBigInt64 ?? false,
|
|
50718
|
+
promoteLongs: options?.promoteLongs ?? parentOptions?.promoteLongs ?? true,
|
|
50719
|
+
promoteValues: options?.promoteValues ?? parentOptions?.promoteValues ?? true,
|
|
50720
|
+
promoteBuffers: options?.promoteBuffers ?? parentOptions?.promoteBuffers ?? false,
|
|
50721
|
+
ignoreUndefined: options?.ignoreUndefined ?? parentOptions?.ignoreUndefined ?? false,
|
|
50722
|
+
bsonRegExp: options?.bsonRegExp ?? parentOptions?.bsonRegExp ?? false,
|
|
50723
|
+
serializeFunctions: options?.serializeFunctions ?? parentOptions?.serializeFunctions ?? false,
|
|
50724
|
+
fieldsAsRaw: options?.fieldsAsRaw ?? parentOptions?.fieldsAsRaw ?? {},
|
|
50725
|
+
enableUtf8Validation: options?.enableUtf8Validation ?? parentOptions?.enableUtf8Validation ?? true
|
|
50726
|
+
};
|
|
50727
|
+
}
|
|
50728
|
+
/** @internal */
|
|
50729
|
+
function parseUtf8ValidationOption(options) {
|
|
50730
|
+
const enableUtf8Validation = options?.enableUtf8Validation;
|
|
50731
|
+
if (enableUtf8Validation === false) {
|
|
50732
|
+
return { utf8: false };
|
|
50733
|
+
}
|
|
50734
|
+
return { utf8: { writeErrors: false } };
|
|
50735
|
+
}
|
|
50736
|
+
//# sourceMappingURL=bson.js.map
|
|
50737
|
+
|
|
50738
|
+
/***/ },
|
|
50739
|
+
|
|
50740
|
+
/***/ "./node_modules/mongoose/node_modules/mongodb/lib/bson.js"
|
|
50741
|
+
/*!****************************************************************!*\
|
|
50742
|
+
!*** ./node_modules/mongoose/node_modules/mongodb/lib/bson.js ***!
|
|
50743
|
+
\****************************************************************/
|
|
50744
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
50745
|
+
|
|
50746
|
+
"use strict";
|
|
50747
|
+
|
|
50531
50748
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
50532
50749
|
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
50750
|
exports.parseToElementsToArray = parseToElementsToArray;
|
|
@@ -52745,7 +52962,7 @@ module.exports = "<div class=\"dashboard-editor p-4 bg-muted rounded-lg shadow-l
|
|
|
52745
52962
|
(module) {
|
|
52746
52963
|
|
|
52747
52964
|
"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
|
|
52965
|
+
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
52966
|
|
|
52750
52967
|
/***/ },
|
|
52751
52968
|
|
|
@@ -58836,7 +59053,7 @@ exports.validatorErrorSymbol = Symbol('mongoose#validatorError');
|
|
|
58836
59053
|
|
|
58837
59054
|
|
|
58838
59055
|
|
|
58839
|
-
const ObjectId = (__webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongodb/lib/bson.js").ObjectId);
|
|
59056
|
+
const ObjectId = (__webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongoose/node_modules/mongodb/lib/bson.js").ObjectId);
|
|
58840
59057
|
const objectIdSymbol = (__webpack_require__(/*! ../helpers/symbols */ "./node_modules/mongoose/lib/helpers/symbols.js").objectIdSymbol);
|
|
58841
59058
|
|
|
58842
59059
|
/**
|
|
@@ -64736,7 +64953,7 @@ var src_default = VueToastificationPlugin;
|
|
|
64736
64953
|
(module) {
|
|
64737
64954
|
|
|
64738
64955
|
"use strict";
|
|
64739
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.
|
|
64956
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.6","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":{"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","eval:agent":"node eval/agent/run.js"}}');
|
|
64740
64957
|
|
|
64741
64958
|
/***/ }
|
|
64742
64959
|
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
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": {
|