@mongoosejs/studio 0.0.89 → 0.0.90
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/astra.js +2 -2
- package/backend/actions/ChatMessage/executeScript.js +7 -1
- package/backend/actions/ChatThread/createChatMessage.js +7 -1
- package/backend/actions/ChatThread/createChatThread.js +6 -0
- package/backend/actions/ChatThread/getChatThread.js +7 -1
- package/backend/actions/ChatThread/listChatThreads.js +8 -2
- package/backend/actions/Dashboard/createDashboard.js +9 -2
- package/backend/actions/Dashboard/deleteDashboard.js +8 -3
- package/backend/actions/Dashboard/getDashboard.js +9 -3
- package/backend/actions/Dashboard/getDashboards.js +5 -2
- package/backend/actions/Dashboard/updateDashboard.js +10 -4
- package/backend/actions/Model/createDocument.js +5 -6
- package/backend/actions/Model/deleteDocument.js +5 -5
- package/backend/actions/Model/deleteDocuments.js +6 -6
- package/backend/actions/Model/dropIndex.js +36 -0
- package/backend/actions/Model/exportQueryResults.js +7 -1
- package/backend/actions/Model/getDocument.js +9 -3
- package/backend/actions/Model/getDocuments.js +7 -0
- package/backend/actions/Model/getIndexes.js +6 -2
- package/backend/actions/Model/listModels.js +14 -2
- package/backend/actions/Model/updateDocument.js +5 -5
- package/backend/actions/Model/updateDocuments.js +5 -6
- package/backend/authorize.js +36 -0
- package/frontend/public/app.js +36 -9
- package/frontend/src/index.js +6 -4
- package/frontend/src/models/models.html +8 -7
- package/frontend/src/models/models.js +8 -1
- package/frontend/src/mothership.js +8 -0
- package/frontend/src/splash/splash.html +19 -7
- package/frontend/src/splash/splash.js +4 -0
- package/frontend/src/team/new-invitation/new-invitation.html +5 -1
- package/frontend/src/team/new-invitation/new-invitation.js +6 -0
- package/frontend/src/team/team.html +2 -1
- package/package.json +1 -1
package/astra.js
CHANGED
|
@@ -154,6 +154,6 @@ void async function main() {
|
|
|
154
154
|
# Astra Notes
|
|
155
155
|
|
|
156
156
|
1. Must use collections. Tables don't support `countDocuments()` or `estimatedDocumentCount()`.
|
|
157
|
-
2.
|
|
158
|
-
3. `countDocuments()` with filter erroring out with more than 1000 documents
|
|
157
|
+
2. Collections don't let you store keys that start with '$', which is problematic for `$chart`. Ended up creating separate connection to store ChatMessages in MongoDB.
|
|
158
|
+
3. `countDocuments()` with filter erroring out with more than 1000 documents caused trouble. Worked around it by converting `countDocuments()` to `find()` using Mongoose middleware.
|
|
159
159
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
const mongoose = require('mongoose');
|
|
5
6
|
const vm = require('vm');
|
|
6
7
|
|
|
@@ -12,7 +13,10 @@ const ExecuteScriptParams = new Archetype({
|
|
|
12
13
|
$type: mongoose.Types.ObjectId
|
|
13
14
|
},
|
|
14
15
|
script: {
|
|
15
|
-
$type:
|
|
16
|
+
$type: 'string'
|
|
17
|
+
},
|
|
18
|
+
roles: {
|
|
19
|
+
$type: ['string']
|
|
16
20
|
}
|
|
17
21
|
}).compile('ExecuteScriptParams');
|
|
18
22
|
|
|
@@ -20,6 +24,8 @@ module.exports = ({ db, studioConnection }) => async function executeScript(para
|
|
|
20
24
|
const { userId, chatMessageId, script } = new ExecuteScriptParams(params);
|
|
21
25
|
const ChatMessage = studioConnection.model('__Studio_ChatMessage');
|
|
22
26
|
|
|
27
|
+
await authorize('ChatMessage.executeScript', roles);
|
|
28
|
+
|
|
23
29
|
const chatMessage = await ChatMessage.findById(chatMessageId);
|
|
24
30
|
if (!chatMessage) {
|
|
25
31
|
throw new Error('Chat message not found');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
const getModelDescriptions = require('../../helpers/getModelDescriptions');
|
|
5
6
|
const mongoose = require('mongoose');
|
|
6
7
|
|
|
@@ -17,6 +18,9 @@ const CreateChatMessageParams = new Archetype({
|
|
|
17
18
|
authorization: {
|
|
18
19
|
$type: 'string',
|
|
19
20
|
$required: true
|
|
21
|
+
},
|
|
22
|
+
roles: {
|
|
23
|
+
$type: ['string'],
|
|
20
24
|
}
|
|
21
25
|
}).compile('CreateChatMessageParams');
|
|
22
26
|
|
|
@@ -56,10 +60,12 @@ Here is a description of the user's models. Assume these are the only models ava
|
|
|
56
60
|
`.trim();
|
|
57
61
|
|
|
58
62
|
module.exports = ({ db, studioConnection, options }) => async function createChatMessage(params) {
|
|
59
|
-
const { chatThreadId, userId, content, script, authorization } = new CreateChatMessageParams(params);
|
|
63
|
+
const { chatThreadId, userId, content, script, authorization, roles } = new CreateChatMessageParams(params);
|
|
60
64
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
61
65
|
const ChatMessage = studioConnection.model('__Studio_ChatMessage');
|
|
62
66
|
|
|
67
|
+
await authorize('ChatThread.createChatMessage', roles);
|
|
68
|
+
|
|
63
69
|
// Check that the user owns the thread
|
|
64
70
|
const chatThread = await ChatThread.findOne({ _id: chatThreadId });
|
|
65
71
|
if (!chatThread) {
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
const mongoose = require('mongoose');
|
|
5
6
|
|
|
6
7
|
const CreateChatThreadParams = new Archetype({
|
|
7
8
|
userId: {
|
|
8
9
|
$type: mongoose.Types.ObjectId
|
|
10
|
+
},
|
|
11
|
+
roles: {
|
|
12
|
+
$type: ['string'],
|
|
9
13
|
}
|
|
10
14
|
}).compile('CreateChatThreadParams');
|
|
11
15
|
|
|
@@ -13,6 +17,8 @@ module.exports = ({ studioConnection }) => async function createChatThread(param
|
|
|
13
17
|
const { userId } = new CreateChatThreadParams(params);
|
|
14
18
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
15
19
|
|
|
20
|
+
await authorize('ChatThread.createChatThread', roles);
|
|
21
|
+
|
|
16
22
|
const chatThread = await ChatThread.create({ userId });
|
|
17
23
|
|
|
18
24
|
return { chatThread };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
const mongoose = require('mongoose');
|
|
5
6
|
|
|
6
7
|
const GetChatThreadParams = new Archetype({
|
|
@@ -9,14 +10,19 @@ const GetChatThreadParams = new Archetype({
|
|
|
9
10
|
},
|
|
10
11
|
userId: {
|
|
11
12
|
$type: mongoose.Types.ObjectId
|
|
13
|
+
},
|
|
14
|
+
roles: {
|
|
15
|
+
$type: ['string']
|
|
12
16
|
}
|
|
13
17
|
}).compile('GetChatThreadParams');
|
|
14
18
|
|
|
15
19
|
module.exports = ({ db, studioConnection }) => async function getChatThread(params) {
|
|
16
|
-
const { chatThreadId, userId } = new GetChatThreadParams(params);
|
|
20
|
+
const { chatThreadId, userId, roles } = new GetChatThreadParams(params);
|
|
17
21
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
18
22
|
const ChatMessage = studioConnection.model('__Studio_ChatMessage');
|
|
19
23
|
|
|
24
|
+
await authorize('ChatThread.getChatThread', roles);
|
|
25
|
+
|
|
20
26
|
const chatThread = await ChatThread.findById(chatThreadId);
|
|
21
27
|
|
|
22
28
|
if (!chatThread) {
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
const mongoose = require('mongoose');
|
|
5
6
|
|
|
6
7
|
const ListChatThreadsParams = new Archetype({
|
|
7
8
|
userId: {
|
|
8
9
|
$type: mongoose.Types.ObjectId
|
|
10
|
+
},
|
|
11
|
+
roles: {
|
|
12
|
+
$type: ['string']
|
|
9
13
|
}
|
|
10
14
|
}).compile('ListChatThreadsParams');
|
|
11
15
|
|
|
12
16
|
module.exports = ({ db, studioConnection }) => async function listChatThreads(params) {
|
|
13
|
-
//
|
|
14
|
-
const { userId } = new ListChatThreadsParams(params);
|
|
17
|
+
// Validate the params object
|
|
18
|
+
const { userId, roles } = new ListChatThreadsParams(params);
|
|
15
19
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
16
20
|
|
|
21
|
+
await authorize('ChatThread.listChatThreads', roles);
|
|
22
|
+
|
|
17
23
|
// Get all chat threads
|
|
18
24
|
const chatThreads = await ChatThread.find(userId ? { userId } : {})
|
|
19
25
|
.sort({ updatedAt: -1 }); // Sort by most recently updated
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
|
|
2
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
3
5
|
|
|
4
6
|
const CreateDashboardParams = new Archetype({
|
|
5
7
|
title: {
|
|
@@ -9,14 +11,19 @@ const CreateDashboardParams = new Archetype({
|
|
|
9
11
|
code: {
|
|
10
12
|
$type: 'string',
|
|
11
13
|
$required: true
|
|
14
|
+
},
|
|
15
|
+
roles: {
|
|
16
|
+
$type: ['string']
|
|
12
17
|
}
|
|
13
18
|
}).compile('CreateDashboardParams');
|
|
14
19
|
|
|
15
20
|
module.exports = ({ db }) => async function createDashboard(params) {
|
|
16
|
-
const { title, code } = new CreateDashboardParams(params);
|
|
21
|
+
const { title, code, roles } = new CreateDashboardParams(params);
|
|
17
22
|
const Dashboard = db.model('__Studio_Dashboard');
|
|
18
23
|
|
|
24
|
+
await authorize('Dashboard.createDashboard', roles);
|
|
25
|
+
|
|
19
26
|
const dashboard = await Dashboard.create({ title, code });
|
|
20
27
|
|
|
21
28
|
return { dashboard };
|
|
22
|
-
};
|
|
29
|
+
};
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
-
const
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
5
|
|
|
6
6
|
const DeleteDashboardParams = new Archetype({
|
|
7
7
|
dashboardId: {
|
|
8
8
|
$type: 'string',
|
|
9
9
|
$required: true
|
|
10
10
|
},
|
|
11
|
+
roles: {
|
|
12
|
+
$type: ['string']
|
|
13
|
+
}
|
|
11
14
|
}).compile('DeleteDashboardParams');
|
|
12
15
|
|
|
13
16
|
module.exports = ({ db }) => async function deleteDashboard(params) {
|
|
14
|
-
const { dashboardId } = new DeleteDashboardParams(params);
|
|
17
|
+
const { dashboardId, roles } = new DeleteDashboardParams(params);
|
|
15
18
|
const Dashboard = db.model('__Studio_Dashboard');
|
|
16
19
|
|
|
20
|
+
await authorize('Dashboard.deleteDashboard', roles);
|
|
21
|
+
|
|
17
22
|
const result = await Dashboard.deleteOne({ _id: dashboardId }).orFail();
|
|
18
23
|
return { result };
|
|
19
|
-
};
|
|
24
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const vm = require('vm');
|
|
5
|
+
const authorize = require('../../authorize');
|
|
5
6
|
|
|
6
7
|
const GetDashboardParams = new Archetype({
|
|
7
8
|
dashboardId: {
|
|
@@ -10,13 +11,18 @@ const GetDashboardParams = new Archetype({
|
|
|
10
11
|
},
|
|
11
12
|
evaluate: {
|
|
12
13
|
$type: 'boolean'
|
|
14
|
+
},
|
|
15
|
+
roles: {
|
|
16
|
+
$type: ['string']
|
|
13
17
|
}
|
|
14
18
|
}).compile('GetDashboardParams');
|
|
15
19
|
|
|
16
20
|
module.exports = ({ db }) => async function getDashboard(params) {
|
|
17
|
-
const { dashboardId, evaluate } = new GetDashboardParams(params);
|
|
21
|
+
const { dashboardId, evaluate, roles } = new GetDashboardParams(params);
|
|
18
22
|
const Dashboard = db.model('__Studio_Dashboard');
|
|
19
23
|
|
|
24
|
+
await authorize('Dashboard.getDashboard', roles);
|
|
25
|
+
|
|
20
26
|
const dashboard = await Dashboard.findOne({ _id: dashboardId });
|
|
21
27
|
if (evaluate) {
|
|
22
28
|
let result = null;
|
|
@@ -25,9 +31,9 @@ module.exports = ({ db }) => async function getDashboard(params) {
|
|
|
25
31
|
} catch (error) {
|
|
26
32
|
return { dashboard, error: { message: error.message } };
|
|
27
33
|
}
|
|
28
|
-
|
|
34
|
+
|
|
29
35
|
return { dashboard, result };
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
return { dashboard };
|
|
33
|
-
};
|
|
39
|
+
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const authorize = require('../../authorize');
|
|
3
4
|
|
|
4
|
-
module.exports = ({ db }) => async function getDashboards() {
|
|
5
|
+
module.exports = ({ db }) => async function getDashboards(roles) {
|
|
5
6
|
const Dashboard = db.model('__Studio_Dashboard');
|
|
6
7
|
|
|
8
|
+
await authorize('Dashboard.getDashboards', roles);
|
|
9
|
+
|
|
7
10
|
const dashboards = await Dashboard.find();
|
|
8
11
|
|
|
9
12
|
return { dashboards }
|
|
10
|
-
};
|
|
13
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
|
|
5
6
|
const UpdateDashboardParams = new Archetype({
|
|
6
7
|
dashboardId: {
|
|
@@ -16,16 +17,21 @@ const UpdateDashboardParams = new Archetype({
|
|
|
16
17
|
},
|
|
17
18
|
description: {
|
|
18
19
|
$type: 'string'
|
|
20
|
+
},
|
|
21
|
+
roles: {
|
|
22
|
+
$type: ['string']
|
|
19
23
|
}
|
|
20
24
|
}).compile('UpdateDashboardParams');
|
|
21
25
|
|
|
22
26
|
module.exports = ({ db }) => async function updateDashboard(params) {
|
|
23
|
-
const { dashboardId, code, title, description } = new UpdateDashboardParams(params);
|
|
27
|
+
const { dashboardId, code, title, description, roles } = new UpdateDashboardParams(params);
|
|
24
28
|
|
|
25
29
|
const Dashboard = db.models[`__Studio_Dashboard`];
|
|
26
30
|
|
|
31
|
+
await authorize('Dashboard.updateDashboard', roles);
|
|
32
|
+
|
|
27
33
|
const updateObj = { code };
|
|
28
|
-
|
|
34
|
+
|
|
29
35
|
if (title) {
|
|
30
36
|
updateObj.title = title;
|
|
31
37
|
}
|
|
@@ -36,7 +42,7 @@ module.exports = ({ db }) => async function updateDashboard(params) {
|
|
|
36
42
|
|
|
37
43
|
const doc = await Dashboard.
|
|
38
44
|
findByIdAndUpdate(dashboardId, updateObj, { sanitizeFilter: true, returnDocument: 'after', overwriteImmutable: true });
|
|
39
|
-
|
|
45
|
+
|
|
40
46
|
let result = null;
|
|
41
47
|
try {
|
|
42
48
|
result = await doc.evaluate();
|
|
@@ -45,4 +51,4 @@ module.exports = ({ db }) => async function updateDashboard(params) {
|
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
return { doc, result };
|
|
48
|
-
};
|
|
54
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const { EJSON } = require('bson');
|
|
5
|
+
const authorize = require('../../authorize');
|
|
5
6
|
|
|
6
7
|
const CreateDocumentParams = new Archetype({
|
|
7
8
|
model: {
|
|
@@ -20,16 +21,14 @@ const CreateDocumentParams = new Archetype({
|
|
|
20
21
|
module.exports = ({ db }) => async function CreateDocument(params) {
|
|
21
22
|
const { model, data, roles } = new CreateDocumentParams(params);
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
throw new Error('Not authorized');
|
|
25
|
-
}
|
|
24
|
+
await authorize('Model.createDocument', roles);
|
|
26
25
|
|
|
27
26
|
const Model = db.models[model];
|
|
28
27
|
if (Model == null) {
|
|
29
28
|
throw new Error(`Model ${model} not found`);
|
|
30
29
|
}
|
|
31
|
-
|
|
30
|
+
|
|
32
31
|
const doc = await Model.create(EJSON.deserialize(data));
|
|
33
|
-
|
|
32
|
+
|
|
34
33
|
return { doc };
|
|
35
|
-
};
|
|
34
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
|
|
5
6
|
const DeleteDocumentParams = new Archetype({
|
|
6
7
|
model: {
|
|
@@ -21,9 +22,8 @@ module.exports = ({ db }) => async function DeleteDocument(params) {
|
|
|
21
22
|
|
|
22
23
|
const Model = db.models[model];
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
25
|
+
await authorize('Model.deleteDocument', roles);
|
|
26
|
+
|
|
27
27
|
if (Model == null) {
|
|
28
28
|
throw new Error(`Model ${model} not found`);
|
|
29
29
|
}
|
|
@@ -33,6 +33,6 @@ module.exports = ({ db }) => async function DeleteDocument(params) {
|
|
|
33
33
|
setOptions({ sanitizeFilter: true }).
|
|
34
34
|
orFail();
|
|
35
35
|
console.log('what is doc', doc);
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
return { doc };
|
|
38
|
-
};
|
|
38
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
|
|
5
6
|
const DeleteDocumentsParams = new Archetype({
|
|
6
7
|
model: {
|
|
@@ -21,9 +22,8 @@ module.exports = ({ db }) => async function DeleteDocuments(params) {
|
|
|
21
22
|
|
|
22
23
|
const Model = db.models[model];
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
25
|
+
await authorize('Model.deleteDocuments', roles);
|
|
26
|
+
|
|
27
27
|
if (Model == null) {
|
|
28
28
|
throw new Error(`Model ${model} not found`);
|
|
29
29
|
}
|
|
@@ -32,7 +32,7 @@ module.exports = ({ db }) => async function DeleteDocuments(params) {
|
|
|
32
32
|
deleteMany({_id: { $in: documentIds }}).
|
|
33
33
|
setOptions({ sanitizeFilter: true }).
|
|
34
34
|
orFail();
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
|
|
36
|
+
|
|
37
37
|
return { };
|
|
38
|
-
};
|
|
38
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
|
+
|
|
6
|
+
const DropIndexParams = new Archetype({
|
|
7
|
+
model: {
|
|
8
|
+
$type: 'string',
|
|
9
|
+
$required: true
|
|
10
|
+
},
|
|
11
|
+
name: {
|
|
12
|
+
$type: 'string',
|
|
13
|
+
$required: true
|
|
14
|
+
},
|
|
15
|
+
roles: {
|
|
16
|
+
$type: ['string']
|
|
17
|
+
}
|
|
18
|
+
}).compile('DropIndexParams');
|
|
19
|
+
|
|
20
|
+
module.exports = ({ db }) => async function getIndexes(params) {
|
|
21
|
+
const { model, name, roles } = new DropIndexParams(params);
|
|
22
|
+
|
|
23
|
+
await authorize('Model.dropIndex', roles);
|
|
24
|
+
|
|
25
|
+
const Model = db.models[model];
|
|
26
|
+
if (Model == null) {
|
|
27
|
+
throw new Error(`Model ${model} not found`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await Model.dropIndex(name);
|
|
31
|
+
|
|
32
|
+
const mongoDBIndexes = await Model.listIndexes();
|
|
33
|
+
return {
|
|
34
|
+
mongoDBIndexes
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const mongoose = require('mongoose');
|
|
5
5
|
const { stringify } = require('csv-stringify/sync');
|
|
6
|
+
const authorize = require('../../authorize');
|
|
6
7
|
|
|
7
8
|
const GetDocumentsParams = new Archetype({
|
|
8
9
|
model: {
|
|
@@ -21,13 +22,18 @@ const GetDocumentsParams = new Archetype({
|
|
|
21
22
|
}
|
|
22
23
|
return v;
|
|
23
24
|
}
|
|
25
|
+
},
|
|
26
|
+
roles: {
|
|
27
|
+
$type: ['string']
|
|
24
28
|
}
|
|
25
29
|
}).compile('GetDocumentsParams');
|
|
26
30
|
|
|
27
31
|
module.exports = ({ db }) => async function exportQueryResults(params, req, res) {
|
|
28
32
|
params = new GetDocumentsParams(params);
|
|
29
33
|
let { filter } = params;
|
|
30
|
-
const { model, propertiesToInclude } = params;
|
|
34
|
+
const { model, propertiesToInclude, roles } = params;
|
|
35
|
+
|
|
36
|
+
await authorize('Model.exportQueryResults', roles);
|
|
31
37
|
|
|
32
38
|
const Model = db.models[model];
|
|
33
39
|
if (Model == null) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const removeSpecifiedPaths = require('../../helpers/removeSpecifiedPaths');
|
|
5
|
+
const authorize = require('../../authorize');
|
|
5
6
|
|
|
6
7
|
const GetDocumentParams = new Archetype({
|
|
7
8
|
model: {
|
|
@@ -11,11 +12,16 @@ const GetDocumentParams = new Archetype({
|
|
|
11
12
|
documentId: {
|
|
12
13
|
$type: 'string',
|
|
13
14
|
$required: true
|
|
15
|
+
},
|
|
16
|
+
roles: {
|
|
17
|
+
$type: ['string']
|
|
14
18
|
}
|
|
15
19
|
}).compile('GetDocumentParams');
|
|
16
20
|
|
|
17
21
|
module.exports = ({ db }) => async function getDocument(params) {
|
|
18
|
-
const { model, documentId } = new GetDocumentParams(params);
|
|
22
|
+
const { model, documentId, roles } = new GetDocumentParams(params);
|
|
23
|
+
|
|
24
|
+
await authorize('Model.getDocument', roles);
|
|
19
25
|
|
|
20
26
|
const Model = db.models[model];
|
|
21
27
|
if (Model == null) {
|
|
@@ -35,6 +41,6 @@ module.exports = ({ db }) => async function getDocument(params) {
|
|
|
35
41
|
};
|
|
36
42
|
}
|
|
37
43
|
removeSpecifiedPaths(schemaPaths, '.$*');
|
|
38
|
-
|
|
44
|
+
|
|
39
45
|
return { doc: doc.toJSON({ virtuals: true, getters: false, transform: false }), schemaPaths };
|
|
40
|
-
};
|
|
46
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const removeSpecifiedPaths = require('../../helpers/removeSpecifiedPaths');
|
|
5
5
|
const { EJSON } = require('bson')
|
|
6
|
+
const authorize = require('../../authorize');
|
|
6
7
|
|
|
7
8
|
const GetDocumentsParams = new Archetype({
|
|
8
9
|
model: {
|
|
@@ -24,11 +25,17 @@ const GetDocumentsParams = new Archetype({
|
|
|
24
25
|
},
|
|
25
26
|
sort: {
|
|
26
27
|
$type: Archetype.Any
|
|
28
|
+
},
|
|
29
|
+
roles: {
|
|
30
|
+
$type: ['string']
|
|
27
31
|
}
|
|
28
32
|
}).compile('GetDocumentsParams');
|
|
29
33
|
|
|
30
34
|
module.exports = ({ db }) => async function getDocuments(params) {
|
|
31
35
|
params = new GetDocumentsParams(params);
|
|
36
|
+
const { roles } = params;
|
|
37
|
+
await authorize('Model.getDocuments', roles);
|
|
38
|
+
|
|
32
39
|
let { filter } = params;
|
|
33
40
|
if (filter != null && Object.keys(filter).length > 0) {
|
|
34
41
|
filter = EJSON.parse(filter);
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
|
|
5
6
|
const GetDocumentsParams = new Archetype({
|
|
6
7
|
model: {
|
|
7
8
|
$type: 'string',
|
|
8
9
|
$required: true
|
|
9
10
|
},
|
|
11
|
+
roles: {
|
|
12
|
+
$type: ['string']
|
|
13
|
+
}
|
|
10
14
|
}).compile('GetDocumentsParams');
|
|
11
15
|
|
|
12
16
|
module.exports = ({ db }) => async function getIndexes(params) {
|
|
13
|
-
|
|
17
|
+
const { model, roles } = new GetDocumentsParams(params);
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
await authorize('Model.getIndexes', roles);
|
|
16
20
|
|
|
17
21
|
const Model = db.models[model];
|
|
18
22
|
if (Model == null) {
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
|
+
|
|
6
|
+
const ListModelsParams = new Archetype({
|
|
7
|
+
roles: {
|
|
8
|
+
$type: ['string']
|
|
9
|
+
}
|
|
10
|
+
}).compile('ListModelsParams');
|
|
11
|
+
|
|
12
|
+
module.exports = ({ db }) => async function listModels(params) {
|
|
13
|
+
const { roles } = new ListModelsParams(params);
|
|
14
|
+
await authorize('Model.listModels', roles);
|
|
15
|
+
|
|
4
16
|
return {
|
|
5
17
|
models: Object.keys(db.models).filter(key => !key.startsWith('__Studio_')).sort()
|
|
6
18
|
};
|
|
7
|
-
};
|
|
19
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
4
5
|
|
|
5
6
|
const UpdateDocumentsParams = new Archetype({
|
|
6
7
|
model: {
|
|
@@ -23,9 +24,8 @@ const UpdateDocumentsParams = new Archetype({
|
|
|
23
24
|
module.exports = ({ db }) => async function updateDocument(params) {
|
|
24
25
|
const { model, _id, update, roles } = new UpdateDocumentsParams(params);
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
27
|
+
await authorize('Document.updateDocument', roles);
|
|
28
|
+
|
|
29
29
|
const Model = db.models[model];
|
|
30
30
|
if (Model == null) {
|
|
31
31
|
throw new Error(`Model ${model} not found`);
|
|
@@ -40,6 +40,6 @@ module.exports = ({ db }) => async function updateDocument(params) {
|
|
|
40
40
|
|
|
41
41
|
const doc = await Model.
|
|
42
42
|
findByIdAndUpdate(_id, processedUpdate, { sanitizeFilter: true, returnDocument: 'after', overwriteImmutable: true, runValidators: false });
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
return { doc };
|
|
45
|
-
};
|
|
45
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
-
const
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
5
|
|
|
6
6
|
const UpdateDocumentsParams = new Archetype({
|
|
7
7
|
model: {
|
|
@@ -24,9 +24,8 @@ const UpdateDocumentsParams = new Archetype({
|
|
|
24
24
|
module.exports = ({ db }) => async function updateDocuments(params) {
|
|
25
25
|
const { model, _id, update, roles } = new UpdateDocumentsParams(params);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
27
|
+
await authorize('Document.updateDocuments', roles);
|
|
28
|
+
|
|
30
29
|
const Model = db.models[model];
|
|
31
30
|
if (Model == null) {
|
|
32
31
|
throw new Error(`Model ${model} not found`);
|
|
@@ -41,6 +40,6 @@ module.exports = ({ db }) => async function updateDocuments(params) {
|
|
|
41
40
|
|
|
42
41
|
const result = await Model.
|
|
43
42
|
updateMany({ _id: { $in: _id } }, processedUpdate, { overwriteImmutable: true, runValidators: false });
|
|
44
|
-
|
|
43
|
+
|
|
45
44
|
return { result };
|
|
46
|
-
};
|
|
45
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const actionsToRequiredRoles = {
|
|
4
|
+
'ChatMessage.executeScript': ['owner', 'admin', 'member'],
|
|
5
|
+
'ChatThread.createChatMessage': ['owner', 'admin', 'member'],
|
|
6
|
+
'ChatThread.createChatThread': ['owner', 'admin', 'member'],
|
|
7
|
+
'ChatThread.getChatThread': ['owner', 'admin', 'member'],
|
|
8
|
+
'ChatThread.listChatThreads': ['owner', 'admin', 'member'],
|
|
9
|
+
'Dashboard.createDashboard': ['owner', 'admin', 'member'],
|
|
10
|
+
'Dashboard.deleteDashboard': ['owner', 'admin', 'member'],
|
|
11
|
+
'Dashboard.getDashboard': ['owner', 'admin', 'member', 'readonly', 'dashboards'],
|
|
12
|
+
'Dashboard.getDashboards': ['owner', 'admin', 'member', 'readonly', 'dashboards'],
|
|
13
|
+
'Dashboard.getDashboard': ['owner', 'admin', 'member', 'readonly', 'dashboards'],
|
|
14
|
+
'Dashboard.updateDashboard': ['owner', 'admin', 'member'],
|
|
15
|
+
'Model.createDocument': ['owner', 'admin', 'member'],
|
|
16
|
+
'Model.updateDocument': ['owner', 'admin', 'member'],
|
|
17
|
+
'Model.deleteDocument': ['owner', 'admin', 'member'],
|
|
18
|
+
'Model.dropIndex': ['owner', 'admin'],
|
|
19
|
+
'Model.exportQueryResults': ['owner', 'admin', 'member', 'readonly'],
|
|
20
|
+
'Model.getDocument': ['owner', 'admin', 'member', 'readonly'],
|
|
21
|
+
'Model.getDocuments': ['owner', 'admin', 'member', 'readonly'],
|
|
22
|
+
'Model.getIndexes': ['owner', 'admin', 'member', 'readonly'],
|
|
23
|
+
'Model.listModels': ['owner', 'admin', 'member', 'readonly'],
|
|
24
|
+
'Model.updateDocument': ['owner', 'admin', 'member'],
|
|
25
|
+
'Model.updateDocuments': ['owner', 'admin', 'member']
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = function authorize(action, roles) {
|
|
29
|
+
if (roles == null) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const authorized = actionsToRequiredRoles[action] && roles.find(role => actionsToRequiredRoles[action].includes(role));
|
|
33
|
+
if (!authorized) {
|
|
34
|
+
throw new Error(`Unauthorized to take action ${action}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/frontend/public/app.js
CHANGED
|
@@ -1800,13 +1800,15 @@ app.component('app-component', {
|
|
|
1800
1800
|
window.state = this;
|
|
1801
1801
|
|
|
1802
1802
|
if (mothership.hasAPIKey) {
|
|
1803
|
-
const
|
|
1804
|
-
if (
|
|
1805
|
-
const code =
|
|
1803
|
+
const hashParams = new URLSearchParams(window.location.hash.replace(/^#?\/?\??/, '') || '');
|
|
1804
|
+
if (hashParams.has('code')) {
|
|
1805
|
+
const code = hashParams.get('code');
|
|
1806
|
+
const provider = hashParams.get('provider');
|
|
1806
1807
|
try {
|
|
1807
|
-
const { accessToken, user, roles } = await mothership.github(code);
|
|
1808
|
+
const { accessToken, user, roles } = provider === 'github' ? await mothership.github(code) : await mothership.google(code);
|
|
1808
1809
|
if (roles == null) {
|
|
1809
1810
|
this.authError = 'You are not authorized to access this workspace';
|
|
1811
|
+
this.status = 'loaded';
|
|
1810
1812
|
return;
|
|
1811
1813
|
}
|
|
1812
1814
|
this.user = user;
|
|
@@ -2291,6 +2293,10 @@ module.exports = app => app.component('models', {
|
|
|
2291
2293
|
this.status = 'loaded';
|
|
2292
2294
|
},
|
|
2293
2295
|
methods: {
|
|
2296
|
+
async dropIndex(name) {
|
|
2297
|
+
const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, index: name });
|
|
2298
|
+
this.mongoDBIndexes = mongoDBIndexes;
|
|
2299
|
+
},
|
|
2294
2300
|
initFilter(ev) {
|
|
2295
2301
|
if (!this.searchText) {
|
|
2296
2302
|
this.searchText = '{}';
|
|
@@ -2389,7 +2395,7 @@ module.exports = app => app.component('models', {
|
|
|
2389
2395
|
},
|
|
2390
2396
|
async openIndexModal() {
|
|
2391
2397
|
this.shouldShowIndexModal = true;
|
|
2392
|
-
const { mongoDBIndexes, schemaIndexes } = await api.Model.getIndexes({ model: this.currentModel })
|
|
2398
|
+
const { mongoDBIndexes, schemaIndexes } = await api.Model.getIndexes({ model: this.currentModel });
|
|
2393
2399
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
2394
2400
|
this.schemaIndexes = schemaIndexes;
|
|
2395
2401
|
},
|
|
@@ -2493,6 +2499,9 @@ module.exports = app => app.component('models', {
|
|
|
2493
2499
|
deselectAll() {
|
|
2494
2500
|
this.selectedPaths = [];
|
|
2495
2501
|
},
|
|
2502
|
+
selectAll() {
|
|
2503
|
+
this.selectedPaths = [...this.schemaPaths];
|
|
2504
|
+
},
|
|
2496
2505
|
isSelected(path) {
|
|
2497
2506
|
return this.selectedPaths.find(x => x.path == path);
|
|
2498
2507
|
},
|
|
@@ -2607,6 +2616,10 @@ exports.githubLogin = function githubLogin() {
|
|
|
2607
2616
|
return client.post('/githubLogin', { state: window.location.href }).then(res => res.data);
|
|
2608
2617
|
};
|
|
2609
2618
|
|
|
2619
|
+
exports.googleLogin = function googleLogin() {
|
|
2620
|
+
return client.post('/googleLogin', { state: window.location.href }).then(res => res.data);
|
|
2621
|
+
};
|
|
2622
|
+
|
|
2610
2623
|
exports.getWorkspaceTeam = function getWorkspaceTeam() {
|
|
2611
2624
|
return client.post('/getWorkspaceTeam', { workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id }).then(res => res.data);
|
|
2612
2625
|
};
|
|
@@ -2619,6 +2632,10 @@ exports.github = function github(code) {
|
|
|
2619
2632
|
return client.post('/github', { code, workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id }).then(res => res.data);
|
|
2620
2633
|
};
|
|
2621
2634
|
|
|
2635
|
+
exports.google = function google(code) {
|
|
2636
|
+
return client.post('/google', { code, workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id }).then(res => res.data);
|
|
2637
|
+
};
|
|
2638
|
+
|
|
2622
2639
|
exports.inviteToWorkspace = function inviteToWorkspace(params) {
|
|
2623
2640
|
return client.post('/inviteToWorkspace', { workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id, ...params }).then(res => res.data);
|
|
2624
2641
|
};
|
|
@@ -2862,6 +2879,10 @@ module.exports = app => app.component('splash', {
|
|
|
2862
2879
|
async loginWithGithub() {
|
|
2863
2880
|
const { url } = await mothership.githubLogin();
|
|
2864
2881
|
window.location.href = url;
|
|
2882
|
+
},
|
|
2883
|
+
async loginWithGoogle() {
|
|
2884
|
+
const { url } = await mothership.googleLogin();
|
|
2885
|
+
window.location.href = url;
|
|
2865
2886
|
}
|
|
2866
2887
|
}
|
|
2867
2888
|
});
|
|
@@ -2883,12 +2904,18 @@ const template = __webpack_require__(/*! ./new-invitation.html */ "./frontend/sr
|
|
|
2883
2904
|
|
|
2884
2905
|
module.exports = app => app.component('new-invitation', {
|
|
2885
2906
|
template,
|
|
2907
|
+
props: ['tier'],
|
|
2886
2908
|
emits: ['close', 'invitationCreated'],
|
|
2887
2909
|
data: () => ({
|
|
2888
2910
|
githubUsername: '',
|
|
2889
2911
|
email: '',
|
|
2890
2912
|
role: null
|
|
2891
2913
|
}),
|
|
2914
|
+
mounted() {
|
|
2915
|
+
if (this.tier == null) {
|
|
2916
|
+
this.role = 'dashboards';
|
|
2917
|
+
}
|
|
2918
|
+
},
|
|
2892
2919
|
methods: {
|
|
2893
2920
|
async inviteToWorkspace() {
|
|
2894
2921
|
const { invitation } = await mothership.inviteToWorkspace({ githubUsername: this.githubUsername, email: this.email, roles: [this.role] });
|
|
@@ -4377,7 +4404,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
|
|
|
4377
4404
|
/***/ ((module) => {
|
|
4378
4405
|
|
|
4379
4406
|
"use strict";
|
|
4380
|
-
module.exports = "<div class=\"models\">\n <div>\n <div class=\"flex grow flex-col gap-y-5 overflow-auto border-r border-gray-200 bg-white px-2 h-[calc(100vh-55px)] w-48\">\n <div class=\"flex font-bold font-xl mt-4 pl-2\">\n Models\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n </nav>\n </div>\n\n </div>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px]\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"flex-grow m-0\">\n <input ref=\"searchInput\" class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\" type=\"text\" placeholder=\"Filter\" v-model=\"searchText\" @click=\"initFilter\" />\n </form>\n <div>\n <span v-if=\"status === 'loading'\">Loading ...</span>\n <span v-if=\"status === 'loaded'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-ultramarine-500 ring-inset ring-2 ring-gray-300 hover:bg-ultramarine-600': selectMultiple }\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n Select\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"outputType = 'table'\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"outputType = 'json'\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <table v-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"clickFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-if=\"outputType === 'json'\">\n <div v-for=\"document in documents\" @click=\"handleDocumentClick(document)\" :key=\"document._id\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <list-json :value=\"filterDocument(document)\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :filter=\"filter\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <button\n type=\"submit\"\n @click=\"dropIndex()\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"submit\" @click=\"filterDocuments()\" class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Filter Selection</button>\n <button type=\"submit\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"submit\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4407
|
+
module.exports = "<div class=\"models\">\n <div>\n <div class=\"flex grow flex-col gap-y-5 overflow-auto border-r border-gray-200 bg-white px-2 h-[calc(100vh-55px)] w-48\">\n <div class=\"flex font-bold font-xl mt-4 pl-2\">\n Models\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n </nav>\n </div>\n\n </div>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px]\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"flex-grow m-0\">\n <input ref=\"searchInput\" class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\" type=\"text\" placeholder=\"Filter\" v-model=\"searchText\" @click=\"initFilter\" />\n </form>\n <div>\n <span v-if=\"status === 'loading'\">Loading ...</span>\n <span v-if=\"status === 'loaded'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-ultramarine-500 ring-inset ring-2 ring-gray-300 hover:bg-ultramarine-600': selectMultiple }\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n Select\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\"\n >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"outputType = 'table'\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"outputType = 'json'\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <table v-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"clickFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-if=\"outputType === 'json'\">\n <div v-for=\"document in documents\" @click=\"handleDocumentClick(document)\" :key=\"document._id\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <list-json :value=\"filterDocument(document)\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :filter=\"filter\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4381
4408
|
|
|
4382
4409
|
/***/ }),
|
|
4383
4410
|
|
|
@@ -4410,7 +4437,7 @@ module.exports = "<div class=\"navbar\">\n <div class=\"nav-left flex items-cen
|
|
|
4410
4437
|
/***/ ((module) => {
|
|
4411
4438
|
|
|
4412
4439
|
"use strict";
|
|
4413
|
-
module.exports = "<div class=\"w-full h-full flex items-center justify-center\">\n <div class=\"text-center\">\n <div class=\"rounded-full bg-gray-100 p-6 inline-block\">\n <img src=\"images/logo.svg\" class=\"w-48 h-48\">\n </div>\n <div class=\"text-lg mt-2 font-bold\">\n Mongoose Studio\n </div>\n <div v-if=\"loading\" class=\"mt-2\">\n <img src=\"images/loader.gif\" class=\"inline w-16 h-16\">\n </div>\n <div class=\"mt-2 text-gray-700\" v-if=\"!loading\">\n {{workspaceName}}\n </div>\n <div class=\"mt-4\" v-if=\"!loading\">\n <async-button\n
|
|
4440
|
+
module.exports = "<div class=\"w-full h-full flex items-center justify-center\">\n <div class=\"text-center\">\n <div class=\"rounded-full bg-gray-100 p-6 inline-block\">\n <img src=\"images/logo.svg\" class=\"w-48 h-48\">\n </div>\n <div class=\"text-lg mt-2 font-bold\">\n Mongoose Studio\n </div>\n <div v-if=\"loading\" class=\"mt-2\">\n <img src=\"images/loader.gif\" class=\"inline w-16 h-16\">\n </div>\n <div class=\"mt-2 text-gray-700\" v-if=\"!loading\">\n {{workspaceName}}\n </div>\n <div class=\"mt-4 flex gap-4 justify-center\" v-if=\"!loading\">\n <div>\n <async-button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n <svg viewBox=\"0 0 98 98\" class=\"inline mr-1\" height=\"1.5em\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z\" fill=\"#fff\"/></svg>\n Login With GitHub\n </async-button>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"loginWithGoogle\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n <svg class=\"inline\" xmlns=\"http://www.w3.org/2000/svg\" height=\"1.5em\" viewBox=\"0 0 512 512\"><path fill=\"#fff\" d=\"M386 400c45-42 65-112 53-179H260v74h102c-4 24-18 44-38 57z\"/><path fill=\"#fff\" d=\"M90 341a192 192 0 0 0 296 59l-62-48c-53 35-141 22-171-60z\"/><path fill=\"#fff\" d=\"M153 292c-8-25-8-48 0-73l-63-49c-23 46-30 111 0 171z\"/><path fill=\"#fff\" d=\"M153 219c22-69 116-109 179-50l55-54c-78-75-230-72-297 55z\"/></svg>\n Login With Google\n </async-button>\n </div>\n </div>\n <div class=\"mt-4\" v-if=\"state.authError\">\n <div class=\"rounded-md bg-red-50 p-4\">\n <div class=\"flex\">\n <div class=\"shrink-0\">\n <svg class=\"size-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">{{state.authError}}</h3>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
|
|
4414
4441
|
|
|
4415
4442
|
/***/ }),
|
|
4416
4443
|
|
|
@@ -4421,7 +4448,7 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
|
|
|
4421
4448
|
/***/ ((module) => {
|
|
4422
4449
|
|
|
4423
4450
|
"use strict";
|
|
4424
|
-
module.exports = "<div class=\"p-1\">\n <form class=\"space-y-4\">\n <div class=\"text-lg font-bold\">\n New Invitation\n </div>\n\n <div>\n <label for=\"githubUsername\" class=\"block text-sm/6 font-medium text-gray-900\">GitHub Username</label>\n <div class=\"mt-2\">\n <input type=\"githubUsername\" name=\"githubUsername\" id=\"githubUsername\" v-model=\"githubUsername\" class=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6\" placeholder=\"johnsmith12\">\n </div>\n </div>\n\n <div>\n <label for=\"email\" class=\"block text-sm/6 font-medium text-gray-900\">Email (Optional)</label>\n <div class=\"mt-2\">\n <input type=\"email\" name=\"email\" id=\"email\" v-model=\"email\" class=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6\" placeholder=\"you@example.com\">\n </div>\n </div>\n\n <div>\n <label for=\"location\" class=\"block text-sm/6 font-medium text-gray-900\">Role</label>\n <div class=\"mt-2 grid grid-cols-1\">\n <select id=\"role\" name=\"role\" v-model=\"role\" class=\"col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6\">\n <option value=\"admin\">Admin</option>\n <option value=\"member\">Member</option>\n <option value=\"readonly\">Read-only</option>\n </select>\n <svg class=\"pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\">\n <path fill-rule=\"evenodd\" d=\"M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n </div>\n\n <async-button\n type=\"submit\"\n @click=\"inviteToWorkspace\"\n class=\"inline-flex justify-center rounded-md border border-transparent bg-forest-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-forest-green-500 focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2\">\n Submit\n </async-button>\n </form>\n</div>\n";
|
|
4451
|
+
module.exports = "<div class=\"p-1\">\n <form class=\"space-y-4\">\n <div class=\"text-lg font-bold\">\n New Invitation\n </div>\n\n <div>\n <label for=\"githubUsername\" class=\"block text-sm/6 font-medium text-gray-900\">GitHub Username</label>\n <div class=\"mt-2\">\n <input type=\"githubUsername\" name=\"githubUsername\" id=\"githubUsername\" v-model=\"githubUsername\" class=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6\" placeholder=\"johnsmith12\">\n </div>\n </div>\n\n <div>\n <label for=\"email\" class=\"block text-sm/6 font-medium text-gray-900\">Email (Optional)</label>\n <div class=\"mt-2\">\n <input type=\"email\" name=\"email\" id=\"email\" v-model=\"email\" class=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6\" placeholder=\"you@example.com\">\n </div>\n </div>\n\n <div>\n <label for=\"location\" class=\"block text-sm/6 font-medium text-gray-900\">Role</label>\n <div class=\"mt-2 grid grid-cols-1\">\n <select id=\"role\" :disabled=\"tier == null\" name=\"role\" v-model=\"role\" class=\"col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6\">\n <option value=\"admin\">Admin</option>\n <option value=\"member\">Member</option>\n <option value=\"readonly\">Read-only</option>\n <option value=\"dashboards\">Dashboards Only</option>\n </select>\n <svg class=\"pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\">\n <path fill-rule=\"evenodd\" d=\"M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div v-if=\"tier == null\" class=\"text-sm text-gray-700\">\n You can only invite \"Dashboards Only\" users until you set up a subscription.\n </div>\n </div>\n\n <async-button\n type=\"submit\"\n @click=\"inviteToWorkspace\"\n class=\"inline-flex justify-center rounded-md border border-transparent bg-forest-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-forest-green-500 focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2\">\n Submit\n </async-button>\n </form>\n</div>\n";
|
|
4425
4452
|
|
|
4426
4453
|
/***/ }),
|
|
4427
4454
|
|
|
@@ -4432,7 +4459,7 @@ module.exports = "<div class=\"p-1\">\n <form class=\"space-y-4\">\n <div cl
|
|
|
4432
4459
|
/***/ ((module) => {
|
|
4433
4460
|
|
|
4434
4461
|
"use strict";
|
|
4435
|
-
module.exports = "<div class=\"mx-auto max-w-5xl py-6 px-2 flex flex-col gap-8\">\n <div>\n <div class=\"text-xl font-bold\">\n Subscription Details\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <div v-else-if=\"workspace && workspace.subscriptionTier\" class=\"mt-4 flex justify-between items-center\">\n <div>\n <span class=\"font-bold\">Tier:</span> {{workspace.subscriptionTier ?? 'No subscription'}}\n </div>\n <div>\n <async-button\n type=\"submit\"\n @click=\"getWorkspaceCustomerPortalLink\"\n class=\"inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2\">\n View in Stripe\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 ml-1\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </async-button>\n </div>\n </div>\n <div v-else-if=\"workspace && !workspace.subscriptionTier\" class=\"mt-4 flex justify-between items-center\">\n <div>\n <span class=\"font-bold\">No active subscription</span>\n <div class=\"text-sm text-gray-700\">\n You won't be able to invite your team until you activate a subscription\n </div>\n </div>\n <div>\n <a\n :href=\"paymentLink\"\n target=\"_blank\"\n class=\"inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-ultramarine-500 focus:ring-offset-2\">\n Subscribe With Stripe\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 ml-1\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </a>\n </div>\n </div>\n </div>\n <div>\n <div class=\"text-xl font-bold\">\n Current Members\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <ul v-else role=\"list\" class=\"divide-y divide-gray-100\">\n <li class=\"flex justify-between gap-x-6 py-5\" v-for=\"user in users\">\n <div class=\"flex min-w-0 gap-x-4\">\n <img class=\"size-12 flex-none rounded-full bg-gray-50\" :src=\"user.picture ?? 'images/logo.svg'\" alt=\"\">\n <div class=\"min-w-0 flex-auto\">\n <p class=\"text-sm/6 font-semibold text-gray-900\">\n {{user.name || user.githubUsername}}\n <span v-if=\"user.isFreeUser\" class=\"ml-1 inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20\">Free</span>\n </p>\n <p class=\"mt-1 truncate text-xs/5 text-gray-500\">{{user.email ?? 'No Email'}}</p>\n </div>\n </div>\n <div class=\"hidden shrink-0 sm:flex sm:flex-col sm:items-end\">\n <p class=\"text-sm/6 text-gray-900 capitalize\">{{getRolesForUser(user).join(', ')}}</p>\n <div class=\"flex gap-3\">\n <p class=\"mt-1 text-xs/5 text-gray-500 cursor-pointer\">\n Edit\n </p>\n <button\n class=\"mt-1 text-xs/5 text-valencia-500 cursor-pointer disabled:cursor-not-allowed disabled:text-gray-300\"\n :disabled=\"getRolesForUser(user).includes('owner')\"\n @click=\"showRemoveModal = user\">\n Remove\n </button>\n </div>\n </div>\n </li>\n </ul>\n </div>\n <div>\n <div class=\"flex items-center justify-between\">\n <div class=\"text-xl font-bold\">\n Invitations\n </div>\n <div class=\"mt-4 sm:ml-16 sm:mt-0 sm:flex-none\">\n <button\n type=\"button\"\n @click=\"showNewInvitationModal = true\"\n :disabled=\"status === 'loading'
|
|
4462
|
+
module.exports = "<div class=\"mx-auto max-w-5xl py-6 px-2 flex flex-col gap-8\">\n <div>\n <div class=\"text-xl font-bold\">\n Subscription Details\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <div v-else-if=\"workspace && workspace.subscriptionTier\" class=\"mt-4 flex justify-between items-center\">\n <div>\n <span class=\"font-bold\">Tier:</span> {{workspace.subscriptionTier ?? 'No subscription'}}\n </div>\n <div>\n <async-button\n type=\"submit\"\n @click=\"getWorkspaceCustomerPortalLink\"\n class=\"inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2\">\n View in Stripe\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 ml-1\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </async-button>\n </div>\n </div>\n <div v-else-if=\"workspace && !workspace.subscriptionTier\" class=\"mt-4 flex justify-between items-center\">\n <div>\n <span class=\"font-bold\">No active subscription</span>\n <div class=\"text-sm text-gray-700\">\n You won't be able to invite your team until you activate a subscription\n </div>\n </div>\n <div>\n <a\n :href=\"paymentLink\"\n target=\"_blank\"\n class=\"inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-ultramarine-500 focus:ring-offset-2\">\n Subscribe With Stripe\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 ml-1\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </a>\n </div>\n </div>\n </div>\n <div>\n <div class=\"text-xl font-bold\">\n Current Members\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <ul v-else role=\"list\" class=\"divide-y divide-gray-100\">\n <li class=\"flex justify-between gap-x-6 py-5\" v-for=\"user in users\">\n <div class=\"flex min-w-0 gap-x-4\">\n <img class=\"size-12 flex-none rounded-full bg-gray-50\" :src=\"user.picture ?? 'images/logo.svg'\" alt=\"\">\n <div class=\"min-w-0 flex-auto\">\n <p class=\"text-sm/6 font-semibold text-gray-900\">\n {{user.name || user.githubUsername}}\n <span v-if=\"user.isFreeUser\" class=\"ml-1 inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20\">Free</span>\n </p>\n <p class=\"mt-1 truncate text-xs/5 text-gray-500\">{{user.email ?? 'No Email'}}</p>\n </div>\n </div>\n <div class=\"hidden shrink-0 sm:flex sm:flex-col sm:items-end\">\n <p class=\"text-sm/6 text-gray-900 capitalize\">{{getRolesForUser(user).join(', ')}}</p>\n <div class=\"flex gap-3\">\n <p class=\"mt-1 text-xs/5 text-gray-500 cursor-pointer\">\n Edit\n </p>\n <button\n class=\"mt-1 text-xs/5 text-valencia-500 cursor-pointer disabled:cursor-not-allowed disabled:text-gray-300\"\n :disabled=\"getRolesForUser(user).includes('owner')\"\n @click=\"showRemoveModal = user\">\n Remove\n </button>\n </div>\n </div>\n </li>\n </ul>\n </div>\n <div>\n <div class=\"flex items-center justify-between\">\n <div class=\"text-xl font-bold\">\n Invitations\n </div>\n <div class=\"mt-4 sm:ml-16 sm:mt-0 sm:flex-none\">\n <button\n type=\"button\"\n @click=\"showNewInvitationModal = true\"\n :disabled=\"status === 'loading'\"\n :tier=\"workspace?.subscriptionTier\"\n class=\"block rounded-md bg-ultramarine-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 disabled:bg-gray-500 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n New Invitation\n <svg class=\"inline w-4 h-4 ml-1\" v-if=\"workspace && !workspace.subscriptionTier\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M12 1.5a5.25 5.25 0 00-5.25 5.25v3a3 3 0 00-3 3v6.75a3 3 0 003 3h10.5a3 3 0 003-3v-6.75a3 3 0 00-3-3v-3c0-2.9-2.35-5.25-5.25-5.25zm3.75 8.25v-3a3.75 3.75 0 10-7.5 0v3h7.5z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <div v-else-if=\"invitations?.length > 0\" class=\"mt-8 flow-root\" v-if=\"invitations?.length > 0\">\n <div class=\"-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\">\n <div class=\"inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8\">\n <table class=\"min-w-full divide-y divide-gray-300\">\n <thead>\n <tr>\n <th scope=\"col\" class=\"py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0\">GitHub Username</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Email</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Status</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Role</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-gray-200 bg-white\">\n <tr v-for=\"invitation in invitations\">\n <td class=\"whitespace-nowrap py-5 pl-4 pr-3 text-sm sm:pl-0\">\n {{invitation.githubUsername}}\n </td>\n <td class=\"whitespace-nowrap px-3 py-5 text-sm text-gray-500\">\n {{invitation.email}}\n </td>\n <td class=\"whitespace-nowrap px-3 py-5 text-sm text-gray-500\">\n <span class=\"inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-600/20\">\n Pending\n </span>\n </td>\n <td class=\"whitespace-nowrap px-3 py-5 text-sm text-gray-500\">\n {{invitation.roles.join(', ')}}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n <div v-else-if=\"invitations?.length === 0\" class=\"mt-4\">\n <div class=\"text-center\">\n <svg class=\"mx-auto size-12 text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path vector-effect=\"non-scaling-stroke\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z\" />\n </svg>\n <h3 class=\"mt-2 text-sm font-semibold text-gray-900\">No invitations</h3>\n <p class=\"mt-1 text-sm text-gray-500\">You have no outstanding invitations</p>\n </div>\n </div>\n </div>\n\n <modal v-if=\"showNewInvitationModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showNewInvitationModal = false\">×</div>\n <new-invitation @close=\"showNewInvitationModal = false\" @invitationCreated=\"invitations.push($event.invitation)\"></new-invitation>\n </template>\n </modal>\n\n <modal v-if=\"showRemoveModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showRemoveModal = false\">×</div>\n <div>\n Are you sure you want to remove user <span class=\"font-bold\">{{showRemoveModal.githubUsername}}</span> from this workspace?\n </div>\n <div class=\"mt-6 grid grid-cols-2 gap-4\">\n <async-button\n @click=\"removeFromWorkspace(showConfirmDeleteModal)\"\n class=\"border-0 mt-0 flex w-full items-center justify-center gap-3 rounded-md bg-valencia-500 hover:bg-valencia-400 px-3 py-1.5 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-400\">\n <span class=\"text-sm font-semibold leading-6\">Yes, Remove</span>\n </async-button>\n\n <span @click=\"showRemoveModal = null\" class=\"cursor-pointer flex w-full items-center justify-center gap-3 rounded-md bg-slate-500 hover:bg-slate-400 px-3 py-1.5 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-400\">\n <span class=\"text-sm font-semibold leading-6\">Cancel</span>\n </span>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4436
4463
|
|
|
4437
4464
|
/***/ }),
|
|
4438
4465
|
|
package/frontend/src/index.js
CHANGED
|
@@ -67,13 +67,15 @@ app.component('app-component', {
|
|
|
67
67
|
window.state = this;
|
|
68
68
|
|
|
69
69
|
if (mothership.hasAPIKey) {
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
const code =
|
|
70
|
+
const hashParams = new URLSearchParams(window.location.hash.replace(/^#?\/?\??/, '') || '');
|
|
71
|
+
if (hashParams.has('code')) {
|
|
72
|
+
const code = hashParams.get('code');
|
|
73
|
+
const provider = hashParams.get('provider');
|
|
73
74
|
try {
|
|
74
|
-
const { accessToken, user, roles } = await mothership.github(code);
|
|
75
|
+
const { accessToken, user, roles } = provider === 'github' ? await mothership.github(code) : await mothership.google(code);
|
|
75
76
|
if (roles == null) {
|
|
76
77
|
this.authError = 'You are not authorized to access this workspace';
|
|
78
|
+
this.status = 'loaded';
|
|
77
79
|
return;
|
|
78
80
|
}
|
|
79
81
|
this.user = user;
|
|
@@ -162,12 +162,12 @@
|
|
|
162
162
|
<div class="text-sm font-mono">{{ JSON.stringify(index.key) }}</div>
|
|
163
163
|
</div>
|
|
164
164
|
<div>
|
|
165
|
-
<button
|
|
166
|
-
type="
|
|
167
|
-
@click="dropIndex()"
|
|
165
|
+
<async-button
|
|
166
|
+
type="button"
|
|
167
|
+
@click="dropIndex(index.name)"
|
|
168
168
|
class="rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed">
|
|
169
169
|
Drop
|
|
170
|
-
</button>
|
|
170
|
+
</async-button>
|
|
171
171
|
</div>
|
|
172
172
|
</div>
|
|
173
173
|
</div>
|
|
@@ -183,9 +183,10 @@
|
|
|
183
183
|
</div>
|
|
184
184
|
</div>
|
|
185
185
|
<div class="mt-4 flex gap-2">
|
|
186
|
-
<button type="
|
|
187
|
-
<button type="
|
|
188
|
-
<button type="
|
|
186
|
+
<button type="button" @click="filterDocuments()" class="rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600">Filter Selection</button>
|
|
187
|
+
<button type="button" @click="selectAll()" class="rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600">Select All</button>
|
|
188
|
+
<button type="button" @click="deselectAll()" class="rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">Deselect All</button>
|
|
189
|
+
<button type="button" @click="resetDocuments()" class="rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" >Cancel</button>
|
|
189
190
|
</div>
|
|
190
191
|
</template>
|
|
191
192
|
</modal>
|
|
@@ -93,6 +93,10 @@ module.exports = app => app.component('models', {
|
|
|
93
93
|
this.status = 'loaded';
|
|
94
94
|
},
|
|
95
95
|
methods: {
|
|
96
|
+
async dropIndex(name) {
|
|
97
|
+
const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, index: name });
|
|
98
|
+
this.mongoDBIndexes = mongoDBIndexes;
|
|
99
|
+
},
|
|
96
100
|
initFilter(ev) {
|
|
97
101
|
if (!this.searchText) {
|
|
98
102
|
this.searchText = '{}';
|
|
@@ -191,7 +195,7 @@ module.exports = app => app.component('models', {
|
|
|
191
195
|
},
|
|
192
196
|
async openIndexModal() {
|
|
193
197
|
this.shouldShowIndexModal = true;
|
|
194
|
-
const { mongoDBIndexes, schemaIndexes } = await api.Model.getIndexes({ model: this.currentModel })
|
|
198
|
+
const { mongoDBIndexes, schemaIndexes } = await api.Model.getIndexes({ model: this.currentModel });
|
|
195
199
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
196
200
|
this.schemaIndexes = schemaIndexes;
|
|
197
201
|
},
|
|
@@ -295,6 +299,9 @@ module.exports = app => app.component('models', {
|
|
|
295
299
|
deselectAll() {
|
|
296
300
|
this.selectedPaths = [];
|
|
297
301
|
},
|
|
302
|
+
selectAll() {
|
|
303
|
+
this.selectedPaths = [...this.schemaPaths];
|
|
304
|
+
},
|
|
298
305
|
isSelected(path) {
|
|
299
306
|
return this.selectedPaths.find(x => x.path == path);
|
|
300
307
|
},
|
|
@@ -22,6 +22,10 @@ exports.githubLogin = function githubLogin() {
|
|
|
22
22
|
return client.post('/githubLogin', { state: window.location.href }).then(res => res.data);
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
exports.googleLogin = function googleLogin() {
|
|
26
|
+
return client.post('/googleLogin', { state: window.location.href }).then(res => res.data);
|
|
27
|
+
};
|
|
28
|
+
|
|
25
29
|
exports.getWorkspaceTeam = function getWorkspaceTeam() {
|
|
26
30
|
return client.post('/getWorkspaceTeam', { workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id }).then(res => res.data);
|
|
27
31
|
};
|
|
@@ -34,6 +38,10 @@ exports.github = function github(code) {
|
|
|
34
38
|
return client.post('/github', { code, workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id }).then(res => res.data);
|
|
35
39
|
};
|
|
36
40
|
|
|
41
|
+
exports.google = function google(code) {
|
|
42
|
+
return client.post('/google', { code, workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id }).then(res => res.data);
|
|
43
|
+
};
|
|
44
|
+
|
|
37
45
|
exports.inviteToWorkspace = function inviteToWorkspace(params) {
|
|
38
46
|
return client.post('/inviteToWorkspace', { workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id, ...params }).then(res => res.data);
|
|
39
47
|
};
|
|
@@ -12,13 +12,25 @@
|
|
|
12
12
|
<div class="mt-2 text-gray-700" v-if="!loading">
|
|
13
13
|
{{workspaceName}}
|
|
14
14
|
</div>
|
|
15
|
-
<div class="mt-4" v-if="!loading">
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
<div class="mt-4 flex gap-4 justify-center" v-if="!loading">
|
|
16
|
+
<div>
|
|
17
|
+
<async-button
|
|
18
|
+
type="button"
|
|
19
|
+
@click="loginWithGithub"
|
|
20
|
+
class="rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
|
|
21
|
+
<svg viewBox="0 0 98 98" class="inline mr-1" height="1.5em" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>
|
|
22
|
+
Login With GitHub
|
|
23
|
+
</async-button>
|
|
24
|
+
</div>
|
|
25
|
+
<div>
|
|
26
|
+
<async-button
|
|
27
|
+
type="button"
|
|
28
|
+
@click="loginWithGoogle"
|
|
29
|
+
class="rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
|
|
30
|
+
<svg class="inline" xmlns="http://www.w3.org/2000/svg" height="1.5em" viewBox="0 0 512 512"><path fill="#fff" d="M386 400c45-42 65-112 53-179H260v74h102c-4 24-18 44-38 57z"/><path fill="#fff" d="M90 341a192 192 0 0 0 296 59l-62-48c-53 35-141 22-171-60z"/><path fill="#fff" d="M153 292c-8-25-8-48 0-73l-63-49c-23 46-30 111 0 171z"/><path fill="#fff" d="M153 219c22-69 116-109 179-50l55-54c-78-75-230-72-297 55z"/></svg>
|
|
31
|
+
Login With Google
|
|
32
|
+
</async-button>
|
|
33
|
+
</div>
|
|
22
34
|
</div>
|
|
23
35
|
<div class="mt-4" v-if="state.authError">
|
|
24
36
|
<div class="rounded-md bg-red-50 p-4">
|
|
@@ -17,6 +17,10 @@ module.exports = app => app.component('splash', {
|
|
|
17
17
|
async loginWithGithub() {
|
|
18
18
|
const { url } = await mothership.githubLogin();
|
|
19
19
|
window.location.href = url;
|
|
20
|
+
},
|
|
21
|
+
async loginWithGoogle() {
|
|
22
|
+
const { url } = await mothership.googleLogin();
|
|
23
|
+
window.location.href = url;
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
});
|
|
@@ -21,15 +21,19 @@
|
|
|
21
21
|
<div>
|
|
22
22
|
<label for="location" class="block text-sm/6 font-medium text-gray-900">Role</label>
|
|
23
23
|
<div class="mt-2 grid grid-cols-1">
|
|
24
|
-
<select id="role" name="role" v-model="role" class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
|
24
|
+
<select id="role" :disabled="tier == null" name="role" v-model="role" class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6">
|
|
25
25
|
<option value="admin">Admin</option>
|
|
26
26
|
<option value="member">Member</option>
|
|
27
27
|
<option value="readonly">Read-only</option>
|
|
28
|
+
<option value="dashboards">Dashboards Only</option>
|
|
28
29
|
</select>
|
|
29
30
|
<svg class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" data-slot="icon">
|
|
30
31
|
<path fill-rule="evenodd" d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
|
31
32
|
</svg>
|
|
32
33
|
</div>
|
|
34
|
+
<div v-if="tier == null" class="text-sm text-gray-700">
|
|
35
|
+
You can only invite "Dashboards Only" users until you set up a subscription.
|
|
36
|
+
</div>
|
|
33
37
|
</div>
|
|
34
38
|
|
|
35
39
|
<async-button
|
|
@@ -5,12 +5,18 @@ const template = require('./new-invitation.html');
|
|
|
5
5
|
|
|
6
6
|
module.exports = app => app.component('new-invitation', {
|
|
7
7
|
template,
|
|
8
|
+
props: ['tier'],
|
|
8
9
|
emits: ['close', 'invitationCreated'],
|
|
9
10
|
data: () => ({
|
|
10
11
|
githubUsername: '',
|
|
11
12
|
email: '',
|
|
12
13
|
role: null
|
|
13
14
|
}),
|
|
15
|
+
mounted() {
|
|
16
|
+
if (this.tier == null) {
|
|
17
|
+
this.role = 'dashboards';
|
|
18
|
+
}
|
|
19
|
+
},
|
|
14
20
|
methods: {
|
|
15
21
|
async inviteToWorkspace() {
|
|
16
22
|
const { invitation } = await mothership.inviteToWorkspace({ githubUsername: this.githubUsername, email: this.email, roles: [this.role] });
|
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
<button
|
|
88
88
|
type="button"
|
|
89
89
|
@click="showNewInvitationModal = true"
|
|
90
|
-
:disabled="status === 'loading'
|
|
90
|
+
:disabled="status === 'loading'"
|
|
91
|
+
:tier="workspace?.subscriptionTier"
|
|
91
92
|
class="block rounded-md bg-ultramarine-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 disabled:bg-gray-500 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
|
|
92
93
|
New Invitation
|
|
93
94
|
<svg class="inline w-4 h-4 ml-1" v-if="workspace && !workspace.subscriptionTier" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.90",
|
|
4
4
|
"description": "A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.",
|
|
5
5
|
"homepage": "https://studio.mongoosejs.io/",
|
|
6
6
|
"repository": {
|