@mongoosejs/studio 0.0.58 → 0.0.60
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/frontend/public/app.js +124 -12
- package/frontend/public/tw.css +215 -2
- package/frontend/src/index.js +8 -3
- package/frontend/src/models/models.css +6 -3
- package/frontend/src/models/models.html +38 -37
- package/frontend/src/mothership.js +8 -0
- package/frontend/src/navbar/navbar.html +2 -2
- package/frontend/src/navbar/navbar.js +4 -1
- package/frontend/src/routes.js +6 -1
- package/frontend/src/team/new-invitation/new-invitation.html +42 -0
- package/frontend/src/team/new-invitation/new-invitation.js +21 -0
- package/frontend/src/team/team.html +86 -0
- package/frontend/src/team/team.js +25 -0
- package/package.json +1 -1
- package/test.html +57 -0
package/frontend/public/app.js
CHANGED
|
@@ -1864,10 +1864,10 @@ module.exports = app => app.component('models', {
|
|
|
1864
1864
|
|
|
1865
1865
|
const axios = __webpack_require__(/*! axios */ "./node_modules/axios/dist/browser/axios.cjs");
|
|
1866
1866
|
const client = axios.create({
|
|
1867
|
-
baseURL: ''
|
|
1867
|
+
baseURL: 'http://localhost:8888/.netlify/functions'
|
|
1868
1868
|
});
|
|
1869
1869
|
|
|
1870
|
-
client.hasAPIKey = !!'';
|
|
1870
|
+
client.hasAPIKey = !!'http://localhost:8888/.netlify/functions';
|
|
1871
1871
|
|
|
1872
1872
|
client.interceptors.request.use(req => {
|
|
1873
1873
|
const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
|
|
@@ -1882,12 +1882,20 @@ exports.githubLogin = function githubLogin() {
|
|
|
1882
1882
|
return client.post('/githubLogin', { state: window.location.href }).then(res => res.data);
|
|
1883
1883
|
};
|
|
1884
1884
|
|
|
1885
|
+
exports.getWorkspaceTeam = function getWorkspaceTeam() {
|
|
1886
|
+
return client.post('/getWorkspaceTeam', { workspaceId: {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]},{"userId":"67a91f3e5db099be8bd0f5c5","roles":["member"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-09T21:35:14.905Z","__v":1,"name":"Zevo DEV","subscriptionTier":"pro"}._id }).then(res => res.data);
|
|
1887
|
+
};
|
|
1888
|
+
|
|
1889
|
+
exports.inviteToWorkspace = function inviteToWorkspace(params) {
|
|
1890
|
+
return client.post('/inviteToWorkspace', { workspaceId: {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]},{"userId":"67a91f3e5db099be8bd0f5c5","roles":["member"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-09T21:35:14.905Z","__v":1,"name":"Zevo DEV","subscriptionTier":"pro"}._id, ...params }).then(res => res.data);
|
|
1891
|
+
};
|
|
1892
|
+
|
|
1885
1893
|
exports.github = function github(code) {
|
|
1886
|
-
return client.post('/github', { code, workspaceId: {}._id }).then(res => res.data);
|
|
1894
|
+
return client.post('/github', { code, workspaceId: {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]},{"userId":"67a91f3e5db099be8bd0f5c5","roles":["member"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-09T21:35:14.905Z","__v":1,"name":"Zevo DEV","subscriptionTier":"pro"}._id }).then(res => res.data);
|
|
1887
1895
|
};
|
|
1888
1896
|
|
|
1889
1897
|
exports.me = function me() {
|
|
1890
|
-
return client.post('/me', { workspaceId: {}._id }).then(res => res.data);
|
|
1898
|
+
return client.post('/me', { workspaceId: {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]},{"userId":"67a91f3e5db099be8bd0f5c5","roles":["member"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-09T21:35:14.905Z","__v":1,"name":"Zevo DEV","subscriptionTier":"pro"}._id }).then(res => res.data);
|
|
1891
1899
|
};
|
|
1892
1900
|
|
|
1893
1901
|
exports.hasAPIKey = client.hasAPIKey;
|
|
@@ -1914,7 +1922,7 @@ appendCSS(__webpack_require__(/*! ./navbar.css */ "./frontend/src/navbar/navbar.
|
|
|
1914
1922
|
|
|
1915
1923
|
module.exports = app => app.component('navbar', {
|
|
1916
1924
|
template: template,
|
|
1917
|
-
props: ['user'],
|
|
1925
|
+
props: ['user', 'roles'],
|
|
1918
1926
|
data: () => ({ nodeEnv: null, showFlyout: false }),
|
|
1919
1927
|
computed: {
|
|
1920
1928
|
routeName() {
|
|
@@ -1925,6 +1933,9 @@ module.exports = app => app.component('navbar', {
|
|
|
1925
1933
|
},
|
|
1926
1934
|
hasAPIKey() {
|
|
1927
1935
|
return mothership.hasAPIKey;
|
|
1936
|
+
},
|
|
1937
|
+
canViewTeam() {
|
|
1938
|
+
return this.roles?.includes('owner') || this.roles?.includes('admin');
|
|
1928
1939
|
}
|
|
1929
1940
|
},
|
|
1930
1941
|
async mounted() {
|
|
@@ -2002,9 +2013,15 @@ module.exports = [
|
|
|
2002
2013
|
path: '/dashboard/:dashboardId',
|
|
2003
2014
|
name: 'dashboard',
|
|
2004
2015
|
component: 'dashboard'
|
|
2016
|
+
},
|
|
2017
|
+
{
|
|
2018
|
+
path: '/team',
|
|
2019
|
+
name: 'team',
|
|
2020
|
+
component: 'team'
|
|
2005
2021
|
}
|
|
2006
2022
|
];
|
|
2007
2023
|
|
|
2024
|
+
|
|
2008
2025
|
/***/ }),
|
|
2009
2026
|
|
|
2010
2027
|
/***/ "./frontend/src/splash/splash.js":
|
|
@@ -2025,7 +2042,7 @@ module.exports = app => app.component('splash', {
|
|
|
2025
2042
|
data: () => ({ error: null }),
|
|
2026
2043
|
computed: {
|
|
2027
2044
|
workspaceName() {
|
|
2028
|
-
return {}.name;
|
|
2045
|
+
return {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]},{"userId":"67a91f3e5db099be8bd0f5c5","roles":["member"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-09T21:35:14.905Z","__v":1,"name":"Zevo DEV","subscriptionTier":"pro"}.name;
|
|
2029
2046
|
}
|
|
2030
2047
|
},
|
|
2031
2048
|
async mounted() {
|
|
@@ -2050,6 +2067,74 @@ module.exports = app => app.component('splash', {
|
|
|
2050
2067
|
});
|
|
2051
2068
|
|
|
2052
2069
|
|
|
2070
|
+
/***/ }),
|
|
2071
|
+
|
|
2072
|
+
/***/ "./frontend/src/team/new-invitation/new-invitation.js":
|
|
2073
|
+
/*!************************************************************!*\
|
|
2074
|
+
!*** ./frontend/src/team/new-invitation/new-invitation.js ***!
|
|
2075
|
+
\************************************************************/
|
|
2076
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2077
|
+
|
|
2078
|
+
"use strict";
|
|
2079
|
+
|
|
2080
|
+
|
|
2081
|
+
const mothership = __webpack_require__(/*! ../../mothership */ "./frontend/src/mothership.js");
|
|
2082
|
+
const template = __webpack_require__(/*! ./new-invitation.html */ "./frontend/src/team/new-invitation/new-invitation.html");
|
|
2083
|
+
|
|
2084
|
+
module.exports = app => app.component('new-invitation', {
|
|
2085
|
+
template,
|
|
2086
|
+
emits: ['close', 'invitationCreated'],
|
|
2087
|
+
data: () => ({
|
|
2088
|
+
githubUsername: '',
|
|
2089
|
+
email: '',
|
|
2090
|
+
role: null
|
|
2091
|
+
}),
|
|
2092
|
+
methods: {
|
|
2093
|
+
async inviteToWorkspace() {
|
|
2094
|
+
const { invitation } = await mothership.inviteToWorkspace({ githubUsername: this.githubUsername, email: this.email, roles: [this.role] });
|
|
2095
|
+
this.$emit('invitationCreated', { invitation });
|
|
2096
|
+
this.$emit('close');
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
});
|
|
2100
|
+
|
|
2101
|
+
|
|
2102
|
+
/***/ }),
|
|
2103
|
+
|
|
2104
|
+
/***/ "./frontend/src/team/team.js":
|
|
2105
|
+
/*!***********************************!*\
|
|
2106
|
+
!*** ./frontend/src/team/team.js ***!
|
|
2107
|
+
\***********************************/
|
|
2108
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2109
|
+
|
|
2110
|
+
"use strict";
|
|
2111
|
+
|
|
2112
|
+
|
|
2113
|
+
const mothership = __webpack_require__(/*! ../mothership */ "./frontend/src/mothership.js");
|
|
2114
|
+
const template = __webpack_require__(/*! ./team.html */ "./frontend/src/team/team.html");
|
|
2115
|
+
|
|
2116
|
+
module.exports = app => app.component('team', {
|
|
2117
|
+
template,
|
|
2118
|
+
data: () => ({
|
|
2119
|
+
workspace: null,
|
|
2120
|
+
users: null,
|
|
2121
|
+
invitations: null,
|
|
2122
|
+
showNewInvitationModal: false
|
|
2123
|
+
}),
|
|
2124
|
+
async mounted() {
|
|
2125
|
+
const { workspace, users, invitations } = await mothership.getWorkspaceTeam();
|
|
2126
|
+
this.workspace = workspace;
|
|
2127
|
+
this.users = users;
|
|
2128
|
+
this.invitations = invitations;
|
|
2129
|
+
},
|
|
2130
|
+
methods: {
|
|
2131
|
+
getRolesForUser(user) {
|
|
2132
|
+
return this.workspace.members.find(member => member.userId === user._id)?.roles ?? [];
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
|
|
2137
|
+
|
|
2053
2138
|
/***/ }),
|
|
2054
2139
|
|
|
2055
2140
|
/***/ "./node_modules/mpath/index.js":
|
|
@@ -3143,7 +3228,7 @@ module.exports = "<transition name=\"modal\">\n <div class=\"modal-mask\">\n
|
|
|
3143
3228
|
/***/ ((module) => {
|
|
3144
3229
|
|
|
3145
3230
|
"use strict";
|
|
3146
|
-
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top:
|
|
3231
|
+
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top: 42px;\n background-color: white;\n z-index: 1;\n}\n\n.models .documents table th:after {\n content: \"\";\n position: absolute;\n left: 0;\n width: 100%;\n bottom: -1px;\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n}\n\n.models .documents table tr {\n color: black;\n border-spacing: 0px 0px;\n background-color: white;\n cursor: pointer;\n}\n\n.models .documents table tr:nth-child(even) {\n background-color: #f5f5f5;\n}\n\n.models .documents table tr:hover {\n background-color: #a7b9ff;\n}\n\n.models .documents table th,\ntd {\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n text-align: left;\n padding: 0 16px;\n height: 48px;\n}\n\n.models textarea {\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0, 0, 0, 0.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n position: fixed;\n background-color: white;\n z-index: 1;\n padding: 4px;\n display: flex;\n width: calc(100vw - 12rem);\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\n align-items: center;\n}\n\n.models .search-input input {\n padding: 0.25em 0.5em;\n font-size: 1.1em;\n border: 1px solid #ddd;\n border-radius: 3px;\n width: calc(100% - 1em);\n}\n\n.models .sort-arrow {\n padding-left: 10px;\n padding-right: 10px;\n}\n\n.models .loader {\n width: 100%;\n text-align: center;\n}\n\n.models .loader img {\n height: 4em;\n}\n\n.models .documents .buttons {\n display: inline-flex;\n justify-content: space-around;\n align-items: center;\n}\n";
|
|
3147
3232
|
|
|
3148
3233
|
/***/ }),
|
|
3149
3234
|
|
|
@@ -3154,7 +3239,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
|
|
|
3154
3239
|
/***/ ((module) => {
|
|
3155
3240
|
|
|
3156
3241
|
"use strict";
|
|
3157
|
-
module.exports = "<div class=\"models\">\n <div>\n <div class=\"flex grow flex-col gap-y-5 overflow-
|
|
3242
|
+
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 class=\"w-full rounded-md p-1 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\" type=\"text\" placeholder=\"Filter or text\" v-model=\"searchText\" />\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 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=\"shouldShowCreateModal = true;\"\n type=\"button\"\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=\"shouldShowFieldModal = true\"\n type=\"button\"\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\">\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=\"$router.push('/model/' + currentModel + '/document/' + document._id)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\">\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=\"$router.push('/model/' + currentModel + '/document/' + document._id)\" :key=\"document._id\">\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 <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=\"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\" style=\"margin-bottom: 0.5em\">\n <input type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <label :for=\"'path' + index\">{{path.path}}</label>\n </div>\n <div style=\"margin-top: 1em\">\n <button type=\"submit\" @click=\"filterDocuments()\" style=\"color: black;margin-right: 0.5em\">Filter Selection</button>\n <button type=\"submit\" @click=\"deselectAll()\" class=\"gray\" style=\"margin-right: 0.5em\">Deselect All</button>\n <button type=\"submit\" @click=\"resetDocuments()\" class=\"gray\">Cancel</button>\n\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 </div>\n</div>\n";
|
|
3158
3243
|
|
|
3159
3244
|
/***/ }),
|
|
3160
3245
|
|
|
@@ -3176,7 +3261,7 @@ module.exports = ".navbar {\n width: 100%;\n background-color: #eee;\n}\n\n.ac
|
|
|
3176
3261
|
/***/ ((module) => {
|
|
3177
3262
|
|
|
3178
3263
|
"use strict";
|
|
3179
|
-
module.exports = "<div class=\"navbar\">\n <div class=\"nav-left flex items-center gap-4 h-full\">\n <router-link to=\"/\">\n <img src=\"images/logo.svg\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!nodeEnv\" class=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900\" :class=\"warnEnv ? 'bg-red-300' : 'bg-yellow-300'\">\n {{nodeEnv}}\n </div>\n </div>\n <div class=\"nav-right h-full\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"routeName === 'root' ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <a\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"routeName === 'dashboards' ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <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 Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-10 top-[
|
|
3264
|
+
module.exports = "<div class=\"navbar\">\n <div class=\"nav-left flex items-center gap-4 h-full\">\n <router-link to=\"/\">\n <img src=\"images/logo.svg\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!nodeEnv\" class=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900\" :class=\"warnEnv ? 'bg-red-300' : 'bg-yellow-300'\">\n {{nodeEnv}}\n </div>\n </div>\n <div class=\"nav-right h-full\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"routeName === 'root' ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <a\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"routeName === 'dashboards' ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <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 Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-10 top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\" role=\"menu\" aria-orientation=\"vertical\" aria-labelledby=\"user-menu-button\" tabindex=\"-1\">\n <router-link to=\"team\" v-if=\"canViewTeam\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n\n </div>\n </div>\n <div style=\"clear: both\"></div>\n</div>\n";
|
|
3180
3265
|
|
|
3181
3266
|
/***/ }),
|
|
3182
3267
|
|
|
@@ -3191,6 +3276,28 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
|
|
|
3191
3276
|
|
|
3192
3277
|
/***/ }),
|
|
3193
3278
|
|
|
3279
|
+
/***/ "./frontend/src/team/new-invitation/new-invitation.html":
|
|
3280
|
+
/*!**************************************************************!*\
|
|
3281
|
+
!*** ./frontend/src/team/new-invitation/new-invitation.html ***!
|
|
3282
|
+
\**************************************************************/
|
|
3283
|
+
/***/ ((module) => {
|
|
3284
|
+
|
|
3285
|
+
"use strict";
|
|
3286
|
+
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";
|
|
3287
|
+
|
|
3288
|
+
/***/ }),
|
|
3289
|
+
|
|
3290
|
+
/***/ "./frontend/src/team/team.html":
|
|
3291
|
+
/*!*************************************!*\
|
|
3292
|
+
!*** ./frontend/src/team/team.html ***!
|
|
3293
|
+
\*************************************/
|
|
3294
|
+
/***/ ((module) => {
|
|
3295
|
+
|
|
3296
|
+
"use strict";
|
|
3297
|
+
module.exports = "<div class=\"mx-auto max-w-5xl py-6 px-2\">\n <div class=\"text-xl font-bold\">\n Current Members\n </div>\n <ul 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\">{{user.name}}</p>\n <p class=\"mt-1 truncate text-xs/5 text-gray-500\">{{user.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 <p class=\"mt-1 text-xs/5 text-gray-500\">Last seen <time datetime=\"2023-01-23T13:23Z\">3h ago</time></p>\n </div>\n </li>\n </ul>\n <div class=\"mt-6\">\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 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 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n New Invitation\n </button>\n </div>\n </div>\n <div 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\n <div v-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</div>\n";
|
|
3298
|
+
|
|
3299
|
+
/***/ }),
|
|
3300
|
+
|
|
3194
3301
|
/***/ "./node_modules/axios/dist/browser/axios.cjs":
|
|
3195
3302
|
/*!***************************************************!*\
|
|
3196
3303
|
!*** ./node_modules/axios/dist/browser/axios.cjs ***!
|
|
@@ -11049,6 +11156,8 @@ __webpack_require__(/*! ./modal/modal */ "./frontend/src/modal/modal.js")(app);
|
|
|
11049
11156
|
__webpack_require__(/*! ./models/models */ "./frontend/src/models/models.js")(app);
|
|
11050
11157
|
__webpack_require__(/*! ./navbar/navbar */ "./frontend/src/navbar/navbar.js")(app);
|
|
11051
11158
|
__webpack_require__(/*! ./splash/splash */ "./frontend/src/splash/splash.js")(app);
|
|
11159
|
+
__webpack_require__(/*! ./team/team */ "./frontend/src/team/team.js")(app);
|
|
11160
|
+
__webpack_require__(/*! ./team/new-invitation/new-invitation */ "./frontend/src/team/new-invitation/new-invitation.js")(app);
|
|
11052
11161
|
|
|
11053
11162
|
app.component('app-component', {
|
|
11054
11163
|
template: `
|
|
@@ -11057,7 +11166,7 @@ app.component('app-component', {
|
|
|
11057
11166
|
<splash />
|
|
11058
11167
|
</div>
|
|
11059
11168
|
<div v-else-if="!hasAPIKey || user">
|
|
11060
|
-
<navbar :user="user" />
|
|
11169
|
+
<navbar :user="user" :roles="roles" />
|
|
11061
11170
|
<div class="view">
|
|
11062
11171
|
<router-view :key="$route.fullPath" />
|
|
11063
11172
|
</div>
|
|
@@ -11083,14 +11192,17 @@ app.component('app-component', {
|
|
|
11083
11192
|
if (mothership.hasAPIKey) {
|
|
11084
11193
|
const token = window.localStorage.getItem('_mongooseStudioAccessToken');
|
|
11085
11194
|
if (token) {
|
|
11086
|
-
|
|
11195
|
+
const { user, roles } = await mothership.me();
|
|
11196
|
+
this.user = user;
|
|
11197
|
+
this.roles = roles;
|
|
11087
11198
|
}
|
|
11088
11199
|
}
|
|
11089
11200
|
},
|
|
11090
11201
|
setup() {
|
|
11091
11202
|
const user = Vue.ref(null);
|
|
11203
|
+
const roles = Vue.ref(null);
|
|
11092
11204
|
|
|
11093
|
-
const state = Vue.reactive({ user });
|
|
11205
|
+
const state = Vue.reactive({ user, roles });
|
|
11094
11206
|
Vue.provide('state', state);
|
|
11095
11207
|
|
|
11096
11208
|
return state;
|
package/frontend/public/tw.css
CHANGED
|
@@ -590,6 +590,10 @@ video {
|
|
|
590
590
|
border-width: 0;
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
+
.pointer-events-none {
|
|
594
|
+
pointer-events: none;
|
|
595
|
+
}
|
|
596
|
+
|
|
593
597
|
.fixed {
|
|
594
598
|
position: fixed;
|
|
595
599
|
}
|
|
@@ -614,8 +618,8 @@ video {
|
|
|
614
618
|
right: 0px;
|
|
615
619
|
}
|
|
616
620
|
|
|
617
|
-
.top-\[
|
|
618
|
-
top:
|
|
621
|
+
.top-\[90\%\] {
|
|
622
|
+
top: 90%;
|
|
619
623
|
}
|
|
620
624
|
|
|
621
625
|
.isolate {
|
|
@@ -626,6 +630,14 @@ video {
|
|
|
626
630
|
z-index: 10;
|
|
627
631
|
}
|
|
628
632
|
|
|
633
|
+
.col-start-1 {
|
|
634
|
+
grid-column-start: 1;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.row-start-1 {
|
|
638
|
+
grid-row-start: 1;
|
|
639
|
+
}
|
|
640
|
+
|
|
629
641
|
.m-0 {
|
|
630
642
|
margin: 0px;
|
|
631
643
|
}
|
|
@@ -686,6 +698,10 @@ video {
|
|
|
686
698
|
margin-right: 0.375rem;
|
|
687
699
|
}
|
|
688
700
|
|
|
701
|
+
.mr-2 {
|
|
702
|
+
margin-right: 0.5rem;
|
|
703
|
+
}
|
|
704
|
+
|
|
689
705
|
.mt-1 {
|
|
690
706
|
margin-top: 0.25rem;
|
|
691
707
|
}
|
|
@@ -738,6 +754,19 @@ video {
|
|
|
738
754
|
display: flow-root;
|
|
739
755
|
}
|
|
740
756
|
|
|
757
|
+
.grid {
|
|
758
|
+
display: grid;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.hidden {
|
|
762
|
+
display: none;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.size-12 {
|
|
766
|
+
width: 3rem;
|
|
767
|
+
height: 3rem;
|
|
768
|
+
}
|
|
769
|
+
|
|
741
770
|
.size-5 {
|
|
742
771
|
width: 1.25rem;
|
|
743
772
|
height: 1.25rem;
|
|
@@ -768,6 +797,10 @@ video {
|
|
|
768
797
|
height: 300px;
|
|
769
798
|
}
|
|
770
799
|
|
|
800
|
+
.h-\[42px\] {
|
|
801
|
+
height: 42px;
|
|
802
|
+
}
|
|
803
|
+
|
|
771
804
|
.h-\[calc\(100vh-55px\)\] {
|
|
772
805
|
height: calc(100vh - 55px);
|
|
773
806
|
}
|
|
@@ -800,6 +833,14 @@ video {
|
|
|
800
833
|
width: 100%;
|
|
801
834
|
}
|
|
802
835
|
|
|
836
|
+
.w-72 {
|
|
837
|
+
width: 18rem;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.min-w-0 {
|
|
841
|
+
min-width: 0px;
|
|
842
|
+
}
|
|
843
|
+
|
|
803
844
|
.min-w-full {
|
|
804
845
|
min-width: 100%;
|
|
805
846
|
}
|
|
@@ -812,6 +853,14 @@ video {
|
|
|
812
853
|
flex: 1 1 0%;
|
|
813
854
|
}
|
|
814
855
|
|
|
856
|
+
.flex-auto {
|
|
857
|
+
flex: 1 1 auto;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.flex-none {
|
|
861
|
+
flex: none;
|
|
862
|
+
}
|
|
863
|
+
|
|
815
864
|
.flex-shrink-0 {
|
|
816
865
|
flex-shrink: 0;
|
|
817
866
|
}
|
|
@@ -844,6 +893,16 @@ video {
|
|
|
844
893
|
list-style-type: disc;
|
|
845
894
|
}
|
|
846
895
|
|
|
896
|
+
.appearance-none {
|
|
897
|
+
-webkit-appearance: none;
|
|
898
|
+
-moz-appearance: none;
|
|
899
|
+
appearance: none;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
.grid-cols-1 {
|
|
903
|
+
grid-template-columns: repeat(1, minmax(0, 1fr));
|
|
904
|
+
}
|
|
905
|
+
|
|
847
906
|
.flex-row {
|
|
848
907
|
flex-direction: row;
|
|
849
908
|
}
|
|
@@ -860,6 +919,10 @@ video {
|
|
|
860
919
|
justify-content: center;
|
|
861
920
|
}
|
|
862
921
|
|
|
922
|
+
.justify-between {
|
|
923
|
+
justify-content: space-between;
|
|
924
|
+
}
|
|
925
|
+
|
|
863
926
|
.gap-1 {
|
|
864
927
|
gap: 0.25rem;
|
|
865
928
|
}
|
|
@@ -876,6 +939,16 @@ video {
|
|
|
876
939
|
gap: 1rem;
|
|
877
940
|
}
|
|
878
941
|
|
|
942
|
+
.gap-x-4 {
|
|
943
|
+
-moz-column-gap: 1rem;
|
|
944
|
+
column-gap: 1rem;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.gap-x-6 {
|
|
948
|
+
-moz-column-gap: 1.5rem;
|
|
949
|
+
column-gap: 1.5rem;
|
|
950
|
+
}
|
|
951
|
+
|
|
879
952
|
.gap-y-5 {
|
|
880
953
|
row-gap: 1.25rem;
|
|
881
954
|
}
|
|
@@ -896,12 +969,23 @@ video {
|
|
|
896
969
|
margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
|
|
897
970
|
}
|
|
898
971
|
|
|
972
|
+
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
|
|
973
|
+
--tw-space-y-reverse: 0;
|
|
974
|
+
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
|
975
|
+
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
|
976
|
+
}
|
|
977
|
+
|
|
899
978
|
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
|
900
979
|
--tw-divide-y-reverse: 0;
|
|
901
980
|
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
|
902
981
|
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
|
|
903
982
|
}
|
|
904
983
|
|
|
984
|
+
.divide-gray-100 > :not([hidden]) ~ :not([hidden]) {
|
|
985
|
+
--tw-divide-opacity: 1;
|
|
986
|
+
border-color: rgb(243 244 246 / var(--tw-divide-opacity));
|
|
987
|
+
}
|
|
988
|
+
|
|
905
989
|
.divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
|
|
906
990
|
--tw-divide-opacity: 1;
|
|
907
991
|
border-color: rgb(229 231 235 / var(--tw-divide-opacity));
|
|
@@ -912,10 +996,18 @@ video {
|
|
|
912
996
|
border-color: rgb(209 213 219 / var(--tw-divide-opacity));
|
|
913
997
|
}
|
|
914
998
|
|
|
999
|
+
.self-center {
|
|
1000
|
+
align-self: center;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
915
1003
|
.self-stretch {
|
|
916
1004
|
align-self: stretch;
|
|
917
1005
|
}
|
|
918
1006
|
|
|
1007
|
+
.justify-self-end {
|
|
1008
|
+
justify-self: end;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
919
1011
|
.overflow-auto {
|
|
920
1012
|
overflow: auto;
|
|
921
1013
|
}
|
|
@@ -1041,6 +1133,11 @@ video {
|
|
|
1041
1133
|
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
|
1042
1134
|
}
|
|
1043
1135
|
|
|
1136
|
+
.bg-gray-50 {
|
|
1137
|
+
--tw-bg-opacity: 1;
|
|
1138
|
+
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1044
1141
|
.bg-gray-500 {
|
|
1045
1142
|
--tw-bg-opacity: 1;
|
|
1046
1143
|
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
|
@@ -1186,6 +1283,16 @@ video {
|
|
|
1186
1283
|
padding-bottom: 1rem;
|
|
1187
1284
|
}
|
|
1188
1285
|
|
|
1286
|
+
.py-5 {
|
|
1287
|
+
padding-top: 1.25rem;
|
|
1288
|
+
padding-bottom: 1.25rem;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
.py-6 {
|
|
1292
|
+
padding-top: 1.5rem;
|
|
1293
|
+
padding-bottom: 1.5rem;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1189
1296
|
.pb-2 {
|
|
1190
1297
|
padding-bottom: 0.5rem;
|
|
1191
1298
|
}
|
|
@@ -1222,6 +1329,10 @@ video {
|
|
|
1222
1329
|
padding-right: 1rem;
|
|
1223
1330
|
}
|
|
1224
1331
|
|
|
1332
|
+
.pr-8 {
|
|
1333
|
+
padding-right: 2rem;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1225
1336
|
.pt-1 {
|
|
1226
1337
|
padding-top: 0.25rem;
|
|
1227
1338
|
}
|
|
@@ -1261,6 +1372,11 @@ video {
|
|
|
1261
1372
|
line-height: 1.25rem;
|
|
1262
1373
|
}
|
|
1263
1374
|
|
|
1375
|
+
.text-sm\/6 {
|
|
1376
|
+
font-size: 0.875rem;
|
|
1377
|
+
line-height: 1.5rem;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1264
1380
|
.text-xl {
|
|
1265
1381
|
font-size: 1.25rem;
|
|
1266
1382
|
line-height: 1.75rem;
|
|
@@ -1271,6 +1387,11 @@ video {
|
|
|
1271
1387
|
line-height: 1rem;
|
|
1272
1388
|
}
|
|
1273
1389
|
|
|
1390
|
+
.text-xs\/5 {
|
|
1391
|
+
font-size: 0.75rem;
|
|
1392
|
+
line-height: 1.25rem;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1274
1395
|
.font-bold {
|
|
1275
1396
|
font-weight: 700;
|
|
1276
1397
|
}
|
|
@@ -1367,10 +1488,22 @@ video {
|
|
|
1367
1488
|
outline-offset: 2px;
|
|
1368
1489
|
}
|
|
1369
1490
|
|
|
1491
|
+
.outline {
|
|
1492
|
+
outline-style: solid;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1370
1495
|
.outline-0 {
|
|
1371
1496
|
outline-width: 0px;
|
|
1372
1497
|
}
|
|
1373
1498
|
|
|
1499
|
+
.outline-1 {
|
|
1500
|
+
outline-width: 1px;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
.-outline-offset-1 {
|
|
1504
|
+
outline-offset: -1px;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1374
1507
|
.outline-gray-300 {
|
|
1375
1508
|
outline-color: #d1d5db;
|
|
1376
1509
|
}
|
|
@@ -1394,6 +1527,10 @@ video {
|
|
|
1394
1527
|
--tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));
|
|
1395
1528
|
}
|
|
1396
1529
|
|
|
1530
|
+
.ring-gray-600\/20 {
|
|
1531
|
+
--tw-ring-color: rgb(75 85 99 / 0.2);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1397
1534
|
.ring-gray-900\/5 {
|
|
1398
1535
|
--tw-ring-color: rgb(17 24 39 / 0.05);
|
|
1399
1536
|
}
|
|
@@ -1445,6 +1582,11 @@ video {
|
|
|
1445
1582
|
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
|
|
1446
1583
|
}
|
|
1447
1584
|
|
|
1585
|
+
.hover\:bg-forest-green-500:hover {
|
|
1586
|
+
--tw-bg-opacity: 1;
|
|
1587
|
+
background-color: rgb(0 242 58 / var(--tw-bg-opacity));
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1448
1590
|
.hover\:bg-gray-300:hover {
|
|
1449
1591
|
--tw-bg-opacity: 1;
|
|
1450
1592
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
|
@@ -1519,6 +1661,30 @@ video {
|
|
|
1519
1661
|
outline-offset: 2px;
|
|
1520
1662
|
}
|
|
1521
1663
|
|
|
1664
|
+
.focus\:outline:focus {
|
|
1665
|
+
outline-style: solid;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
.focus\:outline-2:focus {
|
|
1669
|
+
outline-width: 2px;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
.focus\:-outline-offset-2:focus {
|
|
1673
|
+
outline-offset: -2px;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
.focus\:outline-indigo-600:focus {
|
|
1677
|
+
outline-color: #4f46e5;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
.focus\:outline-ultramarine-600:focus {
|
|
1681
|
+
outline-color: #1823ff;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
.focus\:outline-ultramarine-50:focus {
|
|
1685
|
+
outline-color: #f1f5ff;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1522
1688
|
.focus\:ring-0:focus {
|
|
1523
1689
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
1524
1690
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
@@ -1531,11 +1697,22 @@ video {
|
|
|
1531
1697
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
1532
1698
|
}
|
|
1533
1699
|
|
|
1700
|
+
.focus\:ring-1:focus {
|
|
1701
|
+
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
1702
|
+
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
1703
|
+
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1534
1706
|
.focus\:ring-blue-500:focus {
|
|
1535
1707
|
--tw-ring-opacity: 1;
|
|
1536
1708
|
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
|
|
1537
1709
|
}
|
|
1538
1710
|
|
|
1711
|
+
.focus\:ring-forest-green-500:focus {
|
|
1712
|
+
--tw-ring-opacity: 1;
|
|
1713
|
+
--tw-ring-color: rgb(0 242 58 / var(--tw-ring-opacity));
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1539
1716
|
.focus\:ring-gray-500:focus {
|
|
1540
1717
|
--tw-ring-opacity: 1;
|
|
1541
1718
|
--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity));
|
|
@@ -1556,10 +1733,24 @@ video {
|
|
|
1556
1733
|
--tw-ring-color: rgb(255 255 255 / var(--tw-ring-opacity));
|
|
1557
1734
|
}
|
|
1558
1735
|
|
|
1736
|
+
.focus\:ring-ultramarine-50:focus {
|
|
1737
|
+
--tw-ring-opacity: 1;
|
|
1738
|
+
--tw-ring-color: rgb(241 245 255 / var(--tw-ring-opacity));
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
.focus\:ring-ultramarine-200:focus {
|
|
1742
|
+
--tw-ring-opacity: 1;
|
|
1743
|
+
--tw-ring-color: rgb(206 218 255 / var(--tw-ring-opacity));
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1559
1746
|
.focus\:ring-offset-2:focus {
|
|
1560
1747
|
--tw-ring-offset-width: 2px;
|
|
1561
1748
|
}
|
|
1562
1749
|
|
|
1750
|
+
.focus\:ring-offset-0:focus {
|
|
1751
|
+
--tw-ring-offset-width: 0px;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1563
1754
|
.focus\:ring-offset-gray-800:focus {
|
|
1564
1755
|
--tw-ring-offset-color: #1f2937;
|
|
1565
1756
|
}
|
|
@@ -1622,6 +1813,11 @@ video {
|
|
|
1622
1813
|
display: flex;
|
|
1623
1814
|
}
|
|
1624
1815
|
|
|
1816
|
+
.sm\:size-4 {
|
|
1817
|
+
width: 1rem;
|
|
1818
|
+
height: 1rem;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1625
1821
|
.sm\:flex-auto {
|
|
1626
1822
|
flex: 1 1 auto;
|
|
1627
1823
|
}
|
|
@@ -1630,6 +1826,14 @@ video {
|
|
|
1630
1826
|
flex: none;
|
|
1631
1827
|
}
|
|
1632
1828
|
|
|
1829
|
+
.sm\:flex-col {
|
|
1830
|
+
flex-direction: column;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
.sm\:items-end {
|
|
1834
|
+
align-items: flex-end;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1633
1837
|
.sm\:items-center {
|
|
1634
1838
|
align-items: center;
|
|
1635
1839
|
}
|
|
@@ -1649,6 +1853,10 @@ video {
|
|
|
1649
1853
|
padding-right: 1.5rem;
|
|
1650
1854
|
}
|
|
1651
1855
|
|
|
1856
|
+
.sm\:pl-0 {
|
|
1857
|
+
padding-left: 0px;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1652
1860
|
.sm\:pl-6 {
|
|
1653
1861
|
padding-left: 1.5rem;
|
|
1654
1862
|
}
|
|
@@ -1662,6 +1870,11 @@ video {
|
|
|
1662
1870
|
line-height: 1.25rem;
|
|
1663
1871
|
}
|
|
1664
1872
|
|
|
1873
|
+
.sm\:text-sm\/6 {
|
|
1874
|
+
font-size: 0.875rem;
|
|
1875
|
+
line-height: 1.5rem;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1665
1878
|
.sm\:leading-6 {
|
|
1666
1879
|
line-height: 1.5rem;
|
|
1667
1880
|
}
|
package/frontend/src/index.js
CHANGED
|
@@ -45,6 +45,8 @@ require('./modal/modal')(app);
|
|
|
45
45
|
require('./models/models')(app);
|
|
46
46
|
require('./navbar/navbar')(app);
|
|
47
47
|
require('./splash/splash')(app);
|
|
48
|
+
require('./team/team')(app);
|
|
49
|
+
require('./team/new-invitation/new-invitation')(app);
|
|
48
50
|
|
|
49
51
|
app.component('app-component', {
|
|
50
52
|
template: `
|
|
@@ -53,7 +55,7 @@ app.component('app-component', {
|
|
|
53
55
|
<splash />
|
|
54
56
|
</div>
|
|
55
57
|
<div v-else-if="!hasAPIKey || user">
|
|
56
|
-
<navbar :user="user" />
|
|
58
|
+
<navbar :user="user" :roles="roles" />
|
|
57
59
|
<div class="view">
|
|
58
60
|
<router-view :key="$route.fullPath" />
|
|
59
61
|
</div>
|
|
@@ -79,14 +81,17 @@ app.component('app-component', {
|
|
|
79
81
|
if (mothership.hasAPIKey) {
|
|
80
82
|
const token = window.localStorage.getItem('_mongooseStudioAccessToken');
|
|
81
83
|
if (token) {
|
|
82
|
-
|
|
84
|
+
const { user, roles } = await mothership.me();
|
|
85
|
+
this.user = user;
|
|
86
|
+
this.roles = roles;
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
},
|
|
86
90
|
setup() {
|
|
87
91
|
const user = Vue.ref(null);
|
|
92
|
+
const roles = Vue.ref(null);
|
|
88
93
|
|
|
89
|
-
const state = Vue.reactive({ user });
|
|
94
|
+
const state = Vue.reactive({ user, roles });
|
|
90
95
|
Vue.provide('state', state);
|
|
91
96
|
|
|
92
97
|
return state;
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
.models .documents table th {
|
|
45
45
|
position: sticky;
|
|
46
|
-
top:
|
|
46
|
+
top: 42px;
|
|
47
47
|
background-color: white;
|
|
48
48
|
z-index: 1;
|
|
49
49
|
}
|
|
@@ -90,9 +90,12 @@ td {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
.models .documents-menu {
|
|
93
|
+
position: fixed;
|
|
94
|
+
background-color: white;
|
|
95
|
+
z-index: 1;
|
|
96
|
+
padding: 4px;
|
|
93
97
|
display: flex;
|
|
94
|
-
|
|
95
|
-
width: calc(100vw - 220px);
|
|
98
|
+
width: calc(100vw - 12rem);
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
.models .documents-menu .search-input {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="models">
|
|
2
2
|
<div>
|
|
3
|
-
<div class="flex grow flex-col gap-y-5 overflow-
|
|
3
|
+
<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">
|
|
4
4
|
<div class="flex font-bold font-xl mt-4 pl-2">
|
|
5
5
|
Models
|
|
6
6
|
</div>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<li v-for="model in models">
|
|
12
12
|
<router-link
|
|
13
13
|
:to="'/model/' + model"
|
|
14
|
-
class="block rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700"
|
|
14
|
+
class="block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700"
|
|
15
15
|
:class="model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'">
|
|
16
16
|
{{model}}
|
|
17
17
|
</router-link>
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
|
|
25
25
|
</div>
|
|
26
26
|
<div class="documents" ref="documentsList">
|
|
27
|
-
<div>
|
|
27
|
+
<div class="relative h-[42px]">
|
|
28
28
|
<div class="documents-menu">
|
|
29
29
|
<div class="flex flex-row items-center w-full gap-2">
|
|
30
30
|
<form @submit.prevent="search" class="flex-grow m-0">
|
|
31
|
-
<input class="w-full rounded-md p-1 outline-gray-300 text-lg" type="text" placeholder="Filter or text" v-model="searchText" />
|
|
31
|
+
<input class="w-full rounded-md p-1 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none" type="text" placeholder="Filter or text" v-model="searchText" />
|
|
32
32
|
</form>
|
|
33
33
|
<div>
|
|
34
34
|
<span v-if="status === 'loading'">Loading ...</span>
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
</div>
|
|
72
72
|
</div>
|
|
73
73
|
</div>
|
|
74
|
-
<div class="documents-container">
|
|
74
|
+
<div class="documents-container relative">
|
|
75
75
|
<table v-if="outputType === 'table'">
|
|
76
76
|
<thead>
|
|
77
77
|
<th v-for="path in filteredPaths">
|
|
@@ -105,37 +105,38 @@
|
|
|
105
105
|
<img src="images/loader.gif">
|
|
106
106
|
</div>
|
|
107
107
|
</div>
|
|
108
|
-
<modal v-if="shouldShowExportModal">
|
|
109
|
-
<template v-slot:body>
|
|
110
|
-
<div class="modal-exit" @click="shouldShowExportModal = false">×</div>
|
|
111
|
-
<export-query-results
|
|
112
|
-
:schemaPaths="schemaPaths"
|
|
113
|
-
:filter="filter"
|
|
114
|
-
:currentModel="currentModel"
|
|
115
|
-
@done="shouldShowExportModal = false">
|
|
116
|
-
</export-query-results>
|
|
117
|
-
</template>
|
|
118
|
-
</modal>
|
|
119
|
-
<modal v-if="shouldShowFieldModal">
|
|
120
|
-
<template v-slot:body>
|
|
121
|
-
<div class="modal-exit" @click="shouldShowFieldModal = false; selectedPaths = [...filteredPaths];">×</div>
|
|
122
|
-
<div v-for="(path, index) in schemaPaths" :key="index" style="margin-bottom: 0.5em">
|
|
123
|
-
<input type="checkbox" :id="'path.path'+index" @change="addOrRemove(path)" :value="path.path" :checked="isSelected(path.path)" />
|
|
124
|
-
<label :for="'path' + index">{{path.path}}</label>
|
|
125
|
-
</div>
|
|
126
|
-
<div style="margin-top: 1em">
|
|
127
|
-
<button type="submit" @click="filterDocuments()" style="color: black;margin-right: 0.5em">Filter Selection</button>
|
|
128
|
-
<button type="submit" @click="deselectAll()" class="gray" style="margin-right: 0.5em">Deselect All</button>
|
|
129
|
-
<button type="submit" @click="resetDocuments()" class="gray">Cancel</button>
|
|
130
|
-
|
|
131
|
-
</div>
|
|
132
|
-
</template>
|
|
133
|
-
</modal>
|
|
134
|
-
<modal v-if="shouldShowCreateModal">
|
|
135
|
-
<template v-slot:body>
|
|
136
|
-
<div class="modal-exit" @click="shouldShowCreateModal = false;">×</div>
|
|
137
|
-
<create-document :currentModel="currentModel" :paths="schemaPaths" @close="closeCreationModal"></create-document>
|
|
138
|
-
</template>
|
|
139
|
-
</modal>
|
|
140
108
|
</div>
|
|
109
|
+
|
|
110
|
+
<modal v-if="shouldShowExportModal">
|
|
111
|
+
<template v-slot:body>
|
|
112
|
+
<div class="modal-exit" @click="shouldShowExportModal = false">×</div>
|
|
113
|
+
<export-query-results
|
|
114
|
+
:schemaPaths="schemaPaths"
|
|
115
|
+
:filter="filter"
|
|
116
|
+
:currentModel="currentModel"
|
|
117
|
+
@done="shouldShowExportModal = false">
|
|
118
|
+
</export-query-results>
|
|
119
|
+
</template>
|
|
120
|
+
</modal>
|
|
121
|
+
<modal v-if="shouldShowFieldModal">
|
|
122
|
+
<template v-slot:body>
|
|
123
|
+
<div class="modal-exit" @click="shouldShowFieldModal = false; selectedPaths = [...filteredPaths];">×</div>
|
|
124
|
+
<div v-for="(path, index) in schemaPaths" :key="index" style="margin-bottom: 0.5em">
|
|
125
|
+
<input type="checkbox" :id="'path.path'+index" @change="addOrRemove(path)" :value="path.path" :checked="isSelected(path.path)" />
|
|
126
|
+
<label :for="'path' + index">{{path.path}}</label>
|
|
127
|
+
</div>
|
|
128
|
+
<div style="margin-top: 1em">
|
|
129
|
+
<button type="submit" @click="filterDocuments()" style="color: black;margin-right: 0.5em">Filter Selection</button>
|
|
130
|
+
<button type="submit" @click="deselectAll()" class="gray" style="margin-right: 0.5em">Deselect All</button>
|
|
131
|
+
<button type="submit" @click="resetDocuments()" class="gray">Cancel</button>
|
|
132
|
+
|
|
133
|
+
</div>
|
|
134
|
+
</template>
|
|
135
|
+
</modal>
|
|
136
|
+
<modal v-if="shouldShowCreateModal">
|
|
137
|
+
<template v-slot:body>
|
|
138
|
+
<div class="modal-exit" @click="shouldShowCreateModal = false;">×</div>
|
|
139
|
+
<create-document :currentModel="currentModel" :paths="schemaPaths" @close="closeCreationModal"></create-document>
|
|
140
|
+
</template>
|
|
141
|
+
</modal>
|
|
141
142
|
</div>
|
|
@@ -20,6 +20,14 @@ exports.githubLogin = function githubLogin() {
|
|
|
20
20
|
return client.post('/githubLogin', { state: window.location.href }).then(res => res.data);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
exports.getWorkspaceTeam = function getWorkspaceTeam() {
|
|
24
|
+
return client.post('/getWorkspaceTeam', { workspaceId: config__workspace._id }).then(res => res.data);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
exports.inviteToWorkspace = function inviteToWorkspace(params) {
|
|
28
|
+
return client.post('/inviteToWorkspace', { workspaceId: config__workspace._id, ...params }).then(res => res.data);
|
|
29
|
+
};
|
|
30
|
+
|
|
23
31
|
exports.github = function github(code) {
|
|
24
32
|
return client.post('/github', { code, workspaceId: config__workspace._id }).then(res => res.data);
|
|
25
33
|
};
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
</button>
|
|
36
36
|
</div>
|
|
37
37
|
|
|
38
|
-
<div v-if="showFlyout" class="absolute right-0 z-10 top-[
|
|
39
|
-
|
|
38
|
+
<div v-if="showFlyout" class="absolute right-0 z-10 top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1">
|
|
39
|
+
<router-link to="team" v-if="canViewTeam" @click="showFlyout = false" class="cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200" role="menuitem" tabindex="-1" id="user-menu-item-2">Team</router-link>
|
|
40
40
|
<span @click="logout" class="cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</span>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
@@ -10,7 +10,7 @@ appendCSS(require('./navbar.css'));
|
|
|
10
10
|
|
|
11
11
|
module.exports = app => app.component('navbar', {
|
|
12
12
|
template: template,
|
|
13
|
-
props: ['user'],
|
|
13
|
+
props: ['user', 'roles'],
|
|
14
14
|
data: () => ({ nodeEnv: null, showFlyout: false }),
|
|
15
15
|
computed: {
|
|
16
16
|
routeName() {
|
|
@@ -21,6 +21,9 @@ module.exports = app => app.component('navbar', {
|
|
|
21
21
|
},
|
|
22
22
|
hasAPIKey() {
|
|
23
23
|
return mothership.hasAPIKey;
|
|
24
|
+
},
|
|
25
|
+
canViewTeam() {
|
|
26
|
+
return this.roles?.includes('owner') || this.roles?.includes('admin');
|
|
24
27
|
}
|
|
25
28
|
},
|
|
26
29
|
async mounted() {
|
package/frontend/src/routes.js
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<div class="p-1">
|
|
2
|
+
<form class="space-y-4">
|
|
3
|
+
<div class="text-lg font-bold">
|
|
4
|
+
New Invitation
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div>
|
|
8
|
+
<label for="githubUsername" class="block text-sm/6 font-medium text-gray-900">GitHub Username</label>
|
|
9
|
+
<div class="mt-2">
|
|
10
|
+
<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">
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div>
|
|
15
|
+
<label for="email" class="block text-sm/6 font-medium text-gray-900">Email (Optional)</label>
|
|
16
|
+
<div class="mt-2">
|
|
17
|
+
<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">
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div>
|
|
22
|
+
<label for="location" class="block text-sm/6 font-medium text-gray-900">Role</label>
|
|
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">
|
|
25
|
+
<option value="admin">Admin</option>
|
|
26
|
+
<option value="member">Member</option>
|
|
27
|
+
<option value="readonly">Read-only</option>
|
|
28
|
+
</select>
|
|
29
|
+
<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
|
+
<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
|
+
</svg>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<async-button
|
|
36
|
+
type="submit"
|
|
37
|
+
@click="inviteToWorkspace"
|
|
38
|
+
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">
|
|
39
|
+
Submit
|
|
40
|
+
</async-button>
|
|
41
|
+
</form>
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const mothership = require('../../mothership');
|
|
4
|
+
const template = require('./new-invitation.html');
|
|
5
|
+
|
|
6
|
+
module.exports = app => app.component('new-invitation', {
|
|
7
|
+
template,
|
|
8
|
+
emits: ['close', 'invitationCreated'],
|
|
9
|
+
data: () => ({
|
|
10
|
+
githubUsername: '',
|
|
11
|
+
email: '',
|
|
12
|
+
role: null
|
|
13
|
+
}),
|
|
14
|
+
methods: {
|
|
15
|
+
async inviteToWorkspace() {
|
|
16
|
+
const { invitation } = await mothership.inviteToWorkspace({ githubUsername: this.githubUsername, email: this.email, roles: [this.role] });
|
|
17
|
+
this.$emit('invitationCreated', { invitation });
|
|
18
|
+
this.$emit('close');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<div class="mx-auto max-w-5xl py-6 px-2">
|
|
2
|
+
<div class="text-xl font-bold">
|
|
3
|
+
Current Members
|
|
4
|
+
</div>
|
|
5
|
+
<ul role="list" class="divide-y divide-gray-100">
|
|
6
|
+
<li class="flex justify-between gap-x-6 py-5" v-for="user in users">
|
|
7
|
+
<div class="flex min-w-0 gap-x-4">
|
|
8
|
+
<img class="size-12 flex-none rounded-full bg-gray-50" :src="user.picture ?? 'images/logo.svg'" alt="">
|
|
9
|
+
<div class="min-w-0 flex-auto">
|
|
10
|
+
<p class="text-sm/6 font-semibold text-gray-900">{{user.name}}</p>
|
|
11
|
+
<p class="mt-1 truncate text-xs/5 text-gray-500">{{user.email}}</p>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="hidden shrink-0 sm:flex sm:flex-col sm:items-end">
|
|
15
|
+
<p class="text-sm/6 text-gray-900 capitalize">{{getRolesForUser(user).join(', ')}}</p>
|
|
16
|
+
<p class="mt-1 text-xs/5 text-gray-500">Last seen <time datetime="2023-01-23T13:23Z">3h ago</time></p>
|
|
17
|
+
</div>
|
|
18
|
+
</li>
|
|
19
|
+
</ul>
|
|
20
|
+
<div class="mt-6">
|
|
21
|
+
<div class="flex items-center justify-between">
|
|
22
|
+
<div class="text-xl font-bold">
|
|
23
|
+
Invitations
|
|
24
|
+
</div>
|
|
25
|
+
<div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
@click="showNewInvitationModal = true"
|
|
29
|
+
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 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
|
|
30
|
+
New Invitation
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="mt-8 flow-root" v-if="invitations?.length > 0">
|
|
35
|
+
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
|
36
|
+
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
|
37
|
+
<table class="min-w-full divide-y divide-gray-300">
|
|
38
|
+
<thead>
|
|
39
|
+
<tr>
|
|
40
|
+
<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>
|
|
41
|
+
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Email</th>
|
|
42
|
+
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Status</th>
|
|
43
|
+
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Role</th>
|
|
44
|
+
</tr>
|
|
45
|
+
</thead>
|
|
46
|
+
<tbody class="divide-y divide-gray-200 bg-white">
|
|
47
|
+
<tr v-for="invitation in invitations">
|
|
48
|
+
<td class="whitespace-nowrap py-5 pl-4 pr-3 text-sm sm:pl-0">
|
|
49
|
+
{{invitation.githubUsername}}
|
|
50
|
+
</td>
|
|
51
|
+
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
|
|
52
|
+
{{invitation.email}}
|
|
53
|
+
</td>
|
|
54
|
+
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
|
|
55
|
+
<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">
|
|
56
|
+
Pending
|
|
57
|
+
</span>
|
|
58
|
+
</td>
|
|
59
|
+
<td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
|
|
60
|
+
{{invitation.roles.join(', ')}}
|
|
61
|
+
</td>
|
|
62
|
+
</tr>
|
|
63
|
+
</tbody>
|
|
64
|
+
</table>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div v-if="invitations?.length === 0" class="mt-4">
|
|
70
|
+
<div class="text-center">
|
|
71
|
+
<svg class="mx-auto size-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
|
72
|
+
<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" />
|
|
73
|
+
</svg>
|
|
74
|
+
<h3 class="mt-2 text-sm font-semibold text-gray-900">No invitations</h3>
|
|
75
|
+
<p class="mt-1 text-sm text-gray-500">You have no outstanding invitations</p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<modal v-if="showNewInvitationModal">
|
|
81
|
+
<template v-slot:body>
|
|
82
|
+
<div class="modal-exit" @click="showNewInvitationModal = false">×</div>
|
|
83
|
+
<new-invitation @close="showNewInvitationModal = false" @invitationCreated="invitations.push($event.invitation)"></new-invitation>
|
|
84
|
+
</template>
|
|
85
|
+
</modal>
|
|
86
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const mothership = require('../mothership');
|
|
4
|
+
const template = require('./team.html');
|
|
5
|
+
|
|
6
|
+
module.exports = app => app.component('team', {
|
|
7
|
+
template,
|
|
8
|
+
data: () => ({
|
|
9
|
+
workspace: null,
|
|
10
|
+
users: null,
|
|
11
|
+
invitations: null,
|
|
12
|
+
showNewInvitationModal: false
|
|
13
|
+
}),
|
|
14
|
+
async mounted() {
|
|
15
|
+
const { workspace, users, invitations } = await mothership.getWorkspaceTeam();
|
|
16
|
+
this.workspace = workspace;
|
|
17
|
+
this.users = users;
|
|
18
|
+
this.invitations = invitations;
|
|
19
|
+
},
|
|
20
|
+
methods: {
|
|
21
|
+
getRolesForUser(user) {
|
|
22
|
+
return this.workspace.members.find(member => member.userId === user._id)?.roles ?? [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
package/package.json
CHANGED
package/test.html
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Fixed Navbars with Scrollable Table</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body class="h-screen flex flex-col">
|
|
10
|
+
|
|
11
|
+
<!-- Top-Level Navbar (Fixed) -->
|
|
12
|
+
<nav class="bg-blue-600 text-white p-4 fixed top-0 left-0 right-0 z-20">
|
|
13
|
+
Top-Level Navbar
|
|
14
|
+
</nav>
|
|
15
|
+
|
|
16
|
+
<!-- Side Navbar (Fixed) -->
|
|
17
|
+
<aside class="bg-gray-800 text-white w-64 p-4 fixed top-16 left-0 bottom-0 z-10">
|
|
18
|
+
Side Navbar
|
|
19
|
+
</aside>
|
|
20
|
+
|
|
21
|
+
<!-- Stacked Navigation Bar (Fixed Below Top Navbar) -->
|
|
22
|
+
<nav class="bg-gray-300 p-4 fixed top-16 left-64 right-0 z-20">
|
|
23
|
+
Stacked Navigation Bar
|
|
24
|
+
</nav>
|
|
25
|
+
|
|
26
|
+
<!-- Scrollable Table Container -->
|
|
27
|
+
<div class="flex-grow overflow-auto p-4 pl-64 pt-32 bg-gray-50" style="height: calc(100vh - 8rem);">
|
|
28
|
+
<div class="overflow-auto border border-gray-300" style="max-height: calc(100vh - 12rem);">
|
|
29
|
+
<table class="min-w-max border border-gray-300">
|
|
30
|
+
<thead class="bg-gray-300 sticky top-0 z-10">
|
|
31
|
+
<tr>
|
|
32
|
+
<!-- Generate many columns -->
|
|
33
|
+
<script>
|
|
34
|
+
for (let j = 0; j < 20; j++) {
|
|
35
|
+
document.write(`<th class="border p-2 whitespace-nowrap">Header ${j + 1}</th>`);
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
</tr>
|
|
39
|
+
</thead>
|
|
40
|
+
<tbody>
|
|
41
|
+
<!-- Generate many rows -->
|
|
42
|
+
<script>
|
|
43
|
+
for (let i = 0; i < 100; i++) {
|
|
44
|
+
document.write(`<tr class="border">`);
|
|
45
|
+
for (let j = 0; j < 20; j++) {
|
|
46
|
+
document.write(`<td class="border p-2 whitespace-nowrap">Row ${i + 1} Col ${j + 1}</td>`);
|
|
47
|
+
}
|
|
48
|
+
document.write(`</tr>`);
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
</tbody>
|
|
52
|
+
</table>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|