Browse Source

chore: sync with develop

pull/1979/head
Wing-Kam Wong 3 years ago
parent
commit
b7347a5afd
  1. 9
      .all-contributorsrc
  2. 3
      README.md
  3. 0
      packages/nc-gui/components/ApiOverlay.vue
  4. 6
      packages/nc-gui/components/AuthTab.vue
  5. 4
      packages/nc-gui/components/ChangeEnv.vue
  6. 14
      packages/nc-gui/components/CreateOrEditProject.vue
  7. 0
      packages/nc-gui/components/CreateProjectComingSoon.vue
  8. 0
      packages/nc-gui/components/Discord.vue
  9. 0
      packages/nc-gui/components/DynamicStyle.js
  10. 0
      packages/nc-gui/components/Environment.vue
  11. 4
      packages/nc-gui/components/FeedbackForm.vue
  12. 0
      packages/nc-gui/components/GithubStarBtn.vue
  13. 2
      packages/nc-gui/components/GlobalAcl.vue
  14. 0
      packages/nc-gui/components/ImportantAnnouncement.vue
  15. 0
      packages/nc-gui/components/Loader.vue
  16. 0
      packages/nc-gui/components/Notification.vue
  17. 78
      packages/nc-gui/components/PreviewAs.vue
  18. 0
      packages/nc-gui/components/ProjectLogs.vue
  19. 0
      packages/nc-gui/components/ProjectOutput.vue
  20. 330
      packages/nc-gui/components/ProjectTabs.vue
  21. 62
      packages/nc-gui/components/ProjectTreeView.vue
  22. 6
      packages/nc-gui/components/README.md
  23. 0
      packages/nc-gui/components/ReleaseInfo.vue
  24. 0
      packages/nc-gui/components/Screensaver.vue
  25. 42
      packages/nc-gui/components/Settings.vue
  26. 0
      packages/nc-gui/components/ShareIcons.vue
  27. 0
      packages/nc-gui/components/Snackbar.vue
  28. 0
      packages/nc-gui/components/SponsorMini.vue
  29. 0
      packages/nc-gui/components/SponsorOverlay.vue
  30. 2
      packages/nc-gui/components/XTerm.vue
  31. 0
      packages/nc-gui/components/XcDiff.vue
  32. 0
      packages/nc-gui/components/apiClient/Headers.vue
  33. 0
      packages/nc-gui/components/apiClient/Params.vue
  34. 0
      packages/nc-gui/components/apiClient/PerfTest.vue
  35. 86
      packages/nc-gui/components/auth.routes.js
  36. 0
      packages/nc-gui/components/auth/ApiTokens.vue
  37. 0
      packages/nc-gui/components/auth/AuthHooks.vue
  38. 2
      packages/nc-gui/components/auth/Roles.vue
  39. 140
      packages/nc-gui/components/auth/ShareOrInviteModal.vue
  40. 350
      packages/nc-gui/components/auth/UserManagement.vue
  41. 0
      packages/nc-gui/components/base/ShareBase.vue
  42. 67
      packages/nc-gui/components/global/NcSlider.vue
  43. 0
      packages/nc-gui/components/global/RecursiveMenu.vue
  44. 0
      packages/nc-gui/components/global/XAutoComplete.vue
  45. 0
      packages/nc-gui/components/global/XBtn.vue
  46. 0
      packages/nc-gui/components/global/XIcon.vue
  47. 0
      packages/nc-gui/components/import/DropOrSelectFile.vue
  48. 2
      packages/nc-gui/components/import/DropOrSelectFileModal.vue
  49. 304
      packages/nc-gui/components/import/ImportFromAirtable.vue
  50. 66
      packages/nc-gui/components/import/QuickImport.vue
  51. 6
      packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js
  52. 12
      packages/nc-gui/components/import/templateParsers/ExcelUrlTemplateAdapter.js
  53. 8
      packages/nc-gui/components/project/ApiClientOld.vue
  54. 8
      packages/nc-gui/components/project/ApiClientSwagger.vue
  55. 0
      packages/nc-gui/components/project/Apis.vue
  56. 12
      packages/nc-gui/components/project/AppStore.vue
  57. 8
      packages/nc-gui/components/project/AuditTab.vue
  58. 4
      packages/nc-gui/components/project/CronJobs.vue
  59. 4
      packages/nc-gui/components/project/Function.vue
  60. 0
      packages/nc-gui/components/project/GqlHandlerCodeEditor.vue
  61. 0
      packages/nc-gui/components/project/GraphqlClient.vue
  62. 0
      packages/nc-gui/components/project/GrpcClient.vue
  63. 0
      packages/nc-gui/components/project/GrpcHandlerCodeEditor.vue
  64. 4
      packages/nc-gui/components/project/Procedure.vue
  65. 4
      packages/nc-gui/components/project/ProjectSettings.vue
  66. 0
      packages/nc-gui/components/project/RestHandlerCodeEditor.vue
  67. 2
      packages/nc-gui/components/project/Seed.vue
  68. 2
      packages/nc-gui/components/project/Sequence.vue
  69. 2
      packages/nc-gui/components/project/SqlClient.vue
  70. 12
      packages/nc-gui/components/project/SqlLogAndOutput.vue
  71. 0
      packages/nc-gui/components/project/SwaggerClient.vue
  72. 4
      packages/nc-gui/components/project/Table.vue
  73. 0
      packages/nc-gui/components/project/XcInfo.vue
  74. 0
      packages/nc-gui/components/project/appStore/AppInstall.vue
  75. 10
      packages/nc-gui/components/project/appStore/FormInput.vue
  76. 0
      packages/nc-gui/components/project/appStore/inputs/Attachment.vue
  77. 0
      packages/nc-gui/components/project/appStore/inputs/CheckboxField.vue
  78. 0
      packages/nc-gui/components/project/appStore/inputs/LongTextField.vue
  79. 0
      packages/nc-gui/components/project/appStore/inputs/PasswordField.vue
  80. 0
      packages/nc-gui/components/project/appStore/inputs/TextField.vue
  81. 404
      packages/nc-gui/components/project/appStoreOld.vue
  82. 13
      packages/nc-gui/components/project/auditTab/Audit.vue
  83. 0
      packages/nc-gui/components/project/auditTab/AuditCE.vue
  84. 0
      packages/nc-gui/components/project/auditTab/Db.vue
  85. 0
      packages/nc-gui/components/project/dlgs/DlgAddRelation.vue
  86. 0
      packages/nc-gui/components/project/dlgs/DlgTriggerAddEdit.vue
  87. 0
      packages/nc-gui/components/project/functionTab/FunctionAcl.vue
  88. 2
      packages/nc-gui/components/project/functionTab/FunctionQuery.vue
  89. 0
      packages/nc-gui/components/project/procedureTab/ProcedureAcl.vue
  90. 2
      packages/nc-gui/components/project/procedureTab/ProcedureQuery.vue
  91. 24
      packages/nc-gui/components/project/projectMetadata/DisableOrEnableModels.vue
  92. 0
      packages/nc-gui/components/project/projectMetadata/sync/DisableOrEnableFunctions.vue
  93. 0
      packages/nc-gui/components/project/projectMetadata/sync/DisableOrEnableProcedures.vue
  94. 0
      packages/nc-gui/components/project/projectMetadata/sync/DisableOrEnableRelations.vue
  95. 0
      packages/nc-gui/components/project/projectMetadata/sync/DisableOrEnableViews.vue
  96. 0
      packages/nc-gui/components/project/projectMetadata/sync/MetaDiffSync.vue
  97. 0
      packages/nc-gui/components/project/projectMetadata/uiAcl/ToggleFunctionUIAcl.vue
  98. 0
      packages/nc-gui/components/project/projectMetadata/uiAcl/ToggleProcedureUIAcl.vue
  99. 0
      packages/nc-gui/components/project/projectMetadata/uiAcl/ToggleRelationsUIAcl.vue
  100. 62
      packages/nc-gui/components/project/projectMetadata/uiAcl/ToggleTableUIAcl.vue
  101. Some files were not shown because too many files have changed in this diff Show More

9
.all-contributorsrc

@ -765,6 +765,15 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "youyiio",
"name": "youyiio",
"avatar_url": "https://avatars.githubusercontent.com/u/49471274?v=4",
"profile": "https://www.youyi.io",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

3
README.md

@ -447,6 +447,9 @@ Our mission is to provide the most powerful no-code interface for databases whic
<td align="center"><a href="http://blog.mukyu.tw/"><img src="https://avatars.githubusercontent.com/u/6008539?v=4?s=100" width="100px;" alt=""/><br /><sub><b>神楽坂帕琪</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=mudream4869" title="Code">💻</a></td> <td align="center"><a href="http://blog.mukyu.tw/"><img src="https://avatars.githubusercontent.com/u/6008539?v=4?s=100" width="100px;" alt=""/><br /><sub><b>神楽坂帕琪</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=mudream4869" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/titouancreach"><img src="https://avatars.githubusercontent.com/u/3995719?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Titouan CREACH</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=titouancreach" title="Code">💻</a></td> <td align="center"><a href="https://github.com/titouancreach"><img src="https://avatars.githubusercontent.com/u/3995719?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Titouan CREACH</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=titouancreach" title="Code">💻</a></td>
</tr> </tr>
<tr>
<td align="center"><a href="https://www.youyi.io"><img src="https://avatars.githubusercontent.com/u/49471274?v=4?s=100" width="100px;" alt=""/><br /><sub><b>youyiio</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=youyiio" title="Code">💻</a></td>
</tr>
</table> </table>
<!-- markdownlint-restore --> <!-- markdownlint-restore -->

0
packages/nc-gui/components/apiOverlay.vue → packages/nc-gui/components/ApiOverlay.vue

6
packages/nc-gui/components/authTab.vue → packages/nc-gui/components/AuthTab.vue

@ -29,9 +29,9 @@
</template> </template>
<script> <script>
import UserManagement from '@/components/auth/userManagement' import UserManagement from '~/components/auth/UserManagement'
import Roles from '@/components/auth/roles' import Roles from '~/components/auth/Roles'
import ApiTokens from '@/components/auth/apiTokens' import ApiTokens from '~/components/auth/ApiTokens'
export default { export default {
name: 'AuthTab', name: 'AuthTab',

4
packages/nc-gui/components/changeEnv.vue → packages/nc-gui/components/ChangeEnv.vue

@ -28,7 +28,7 @@
<!-- Close --> <!-- Close -->
{{ $t('general.close') }} {{ $t('general.close') }}
</v-btn> </v-btn>
<v-btn color="primary" small :disabled="progressbar" @click="changeEnv"> <v-btn color="primary" small :disabled="progressbar" @click="ChangeEnv">
Change Change
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -93,7 +93,7 @@ export default {
await this.$store.dispatch('users/ActSignOut') await this.$store.dispatch('users/ActSignOut')
await this.$store.dispatch('project/ActLoadProjectInfo') await this.$store.dispatch('project/ActLoadProjectInfo')
if (this.$store.state.project.projectInfo.projectHasAdmin === false) { if (this.$store.state.project.appInfo.projectHasAdmin === false) {
return this.$router.push('/start') return this.$router.push('/start')
} }
location.reload() location.reload()

14
packages/nc-gui/components/createOrEditProject.vue → packages/nc-gui/components/CreateOrEditProject.vue

@ -860,13 +860,13 @@ import Vue from 'vue'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import XBtn from './global/xBtn' import XBtn from './global/XBtn'
import dlgOk from './utils/dlgOk.vue' import dlgOk from './utils/DlgOk.vue'
import textDlgSubmitCancel from './utils/dlgTextSubmitCancel' import textDlgSubmitCancel from './utils/DlgTextSubmitCancel'
import MonacoJsonObjectEditor from '@/components/monaco/MonacoJsonObjectEditor' import MonacoJsonObjectEditor from '@/components/monaco/MonacoJsonObjectEditor'
import ApiOverlay from '@/components/apiOverlay' import ApiOverlay from '~/components/ApiOverlay'
import colors from '@/mixins/colors' import colors from '@/mixins/colors'
import DlgOkNew from '@/components/utils/dlgOkNew' import DlgOkNew from '~/components/utils/DlgOkNew'
import readFile from '@/helpers/fileReader' import readFile from '@/helpers/fileReader'
const { const {
@ -1428,8 +1428,8 @@ export default {
break break
} }
xcConfig.type = this.$store.state.project.projectInfo xcConfig.type = this.$store.state.project.appInfo
? this.$store.state.project.projectInfo.type ? this.$store.state.project.appInfo.type
: 'docker' : 'docker'
if ( if (

0
packages/nc-gui/components/createProjectComingSoon.vue → packages/nc-gui/components/CreateProjectComingSoon.vue

0
packages/nc-gui/components/discord.vue → packages/nc-gui/components/Discord.vue

0
packages/nc-gui/components/dynamicStyle.js → packages/nc-gui/components/DynamicStyle.js

0
packages/nc-gui/components/environment.vue → packages/nc-gui/components/Environment.vue

4
packages/nc-gui/components/feedbackForm.vue → packages/nc-gui/components/FeedbackForm.vue

@ -23,10 +23,10 @@ export default {
computed: { computed: {
feedbackFormHidden: { feedbackFormHidden: {
get() { get() {
return this.$store.state.windows.feedbackFormHidden return this.$store.state.settings.feedbackFormHidden
}, },
set(val) { set(val) {
this.$store.commit('windows/MutFeedbackFormHidden', val) this.$store.commit('settings/MutFeedbackFormHidden', val)
} }
} }
} }

0
packages/nc-gui/components/githubStarBtn.vue → packages/nc-gui/components/GithubStarBtn.vue

2
packages/nc-gui/components/globalAcl.vue → packages/nc-gui/components/GlobalAcl.vue

@ -231,7 +231,7 @@
<script> <script>
/* eslint-disable */ /* eslint-disable */
import AclTsFileDbChild from '@/components/project/tableTabs/aclTsFileDbChild' import AclTsFileDbChild from '~/components/project/tableTabs/AclTsFileDbChild'
export default { export default {
name: 'GlobalAcl', name: 'GlobalAcl',

0
packages/nc-gui/components/importantAnnouncement.vue → packages/nc-gui/components/ImportantAnnouncement.vue

0
packages/nc-gui/components/loader.vue → packages/nc-gui/components/Loader.vue

0
packages/nc-gui/components/notification.vue → packages/nc-gui/components/Notification.vue

78
packages/nc-gui/components/previewAs.vue → packages/nc-gui/components/PreviewAs.vue

@ -10,9 +10,13 @@
class="white--text nc-btn-preview" class="white--text nc-btn-preview"
v-on="on" v-on="on"
> >
<v-icon small class="mr-1"> mdi-play-circle </v-icon> <v-icon small class="mr-1">
mdi-play-circle
</v-icon>
Preview Preview
<v-icon small> mdi-menu-down </v-icon> <v-icon small>
mdi-menu-down
</v-icon>
</v-btn> </v-btn>
</template> </template>
<v-list dense> <v-list dense>
@ -33,8 +37,7 @@
<span <span
class="caption text-capitalize" class="caption text-capitalize"
:class="{ 'x-active--text': role.title === previewAs }" :class="{ 'x-active--text': role.title === previewAs }"
>{{ role.title }}</span >{{ role.title }}</span>
>
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
</template> </template>
@ -42,7 +45,9 @@
<template v-if="previewAs"> <template v-if="previewAs">
<!-- <v-divider></v-divider>--> <!-- <v-divider></v-divider>-->
<v-list-item @click="setPreviewUser(null)"> <v-list-item @click="setPreviewUser(null)">
<v-icon small class="mr-1"> mdi-close </v-icon> <v-icon small class="mr-1">
mdi-close
</v-icon>
<!-- Reset Preview --> <!-- Reset Preview -->
<span class="caption nc-preview-reset">{{ <span class="caption nc-preview-reset">{{
$t("activity.resetReview") $t("activity.resetReview")
@ -97,8 +102,7 @@
</v-radio-group> </v-radio-group>
<v-divider vertical class="mr-2" /> <v-divider vertical class="mr-2" />
<span class="pointer" @click="setPreviewUser(null)"> <span class="pointer" @click="setPreviewUser(null)">
<v-icon small color="white">mdi-exit-to-app</v-icon> Exit</span <v-icon small color="white">mdi-exit-to-app</v-icon> Exit</span>
>
</div> </div>
</div> </div>
</v-menu> </v-menu>
@ -107,69 +111,69 @@
<script> <script>
export default { export default {
name: "PreviewAs", name: 'PreviewAs',
data: () => ({ data: () => ({
roleIcon: { roleIcon: {
owner: "mdi-account-star", owner: 'mdi-account-star',
creator: "mdi-account-hard-hat", creator: 'mdi-account-hard-hat',
editor: "mdi-account-edit", editor: 'mdi-account-edit',
viewer: "mdi-eye-outline", viewer: 'mdi-eye-outline',
commenter: "mdi-comment-account-outline", commenter: 'mdi-comment-account-outline'
}, },
rolesList: [ rolesList: [
{ title: "editor" }, { title: 'editor' },
{ title: "commenter" }, { title: 'commenter' },
{ title: "viewer" }, { title: 'viewer' }
], ],
position: { position: {
x: 9999, x: 9999,
y: 9999, y: 9999
}, }
}), }),
computed: { computed: {
previewAs: { previewAs: {
get() { get() {
return this.$store.state.users.previewAs; return this.$store.state.users.previewAs
}, },
set(previewAs) { set(previewAs) {
this.$store.commit("users/MutPreviewAs", previewAs); this.$store.commit('users/MutPreviewAs', previewAs)
}, }
}, }
}, },
mounted() { mounted() {
this.position = { this.position = {
y: window.innerHeight - 100, y: window.innerHeight - 100,
x: window.innerWidth / 2 - 250, x: window.innerWidth / 2 - 250
}; }
window.addEventListener("mouseup", this.mouseUp, false); window.addEventListener('mouseup', this.mouseUp, false)
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener("mousemove", this.divMove, true); window.removeEventListener('mousemove', this.divMove, true)
window.removeEventListener("mouseup", this.mouseUp, false); window.removeEventListener('mouseup', this.mouseUp, false)
}, },
methods: { methods: {
setPreviewUser(previewAs) { setPreviewUser(previewAs) {
this.$e("a:navdraw:preview", { role: previewAs }); this.$e('a:navdraw:preview', { role: previewAs })
if (!process.env.EE) { if (!process.env.EE) {
this.$toast.info("Available in Enterprise edition").goAway(3000); this.$toast.info('Available in Enterprise edition').goAway(3000)
} else { } else {
this.previewAs = previewAs; this.previewAs = previewAs
window.location.reload(); window.location.reload()
} }
}, },
mouseUp() { mouseUp() {
window.removeEventListener("mousemove", this.divMove, true); window.removeEventListener('mousemove', this.divMove, true)
}, },
mouseDown(e) { mouseDown(e) {
window.addEventListener("mousemove", this.divMove, true); window.addEventListener('mousemove', this.divMove, true)
}, },
divMove(e) { divMove(e) {
this.position = { y: e.clientY - 10, x: e.clientX - 18 }; this.position = { y: e.clientY - 10, x: e.clientX - 18 }
}, }
}, }
}; }
</script> </script>
<style scoped></style> <style scoped></style>

0
packages/nc-gui/components/projectLogs.vue → packages/nc-gui/components/ProjectLogs.vue

0
packages/nc-gui/components/projectOutput.vue → packages/nc-gui/components/ProjectOutput.vue

330
packages/nc-gui/components/projectTabs.vue → packages/nc-gui/components/ProjectTabs.vue

@ -15,7 +15,7 @@
next-icon="mdi-arrow-right-bold-box-outline" next-icon="mdi-arrow-right-bold-box-outline"
prev-icon="mdi-arrow-left-bold-box-outline" prev-icon="mdi-arrow-left-bold-box-outline"
show-arrows show-arrows
:class="{ 'dark-them': $store.state.windows.darkTheme }" :class="{ 'dark-them': $store.state.settings.darkTheme }"
> >
<v-tabs-slider color="" /> <v-tabs-slider color="" />
@ -42,14 +42,12 @@
max-width: 140px; max-width: 140px;
text-overflow: ellipsis; text-overflow: ellipsis;
" "
>{{ tab.name }}</span >{{ tab.name }}</span>
>
<v-icon icon :small="true" @click="removeTab(index)"> <v-icon icon :small="true" @click="removeTab(index)">
mdi-close mdi-close
</v-icon> </v-icon>
</v-tab> </v-tab>
<!-- <v-tabs-items v-model="activeTab">-->
<v-tabs-items :value="activeTab"> <v-tabs-items :value="activeTab">
<v-tab-item <v-tab-item
v-for="(tab, index) in tabs" v-for="(tab, index) in tabs"
@ -65,7 +63,6 @@
:reverse-transition="false" :reverse-transition="false"
> >
<div v-if="tab._nodes.type === 'table'" style="height: 100%"> <div v-if="tab._nodes.type === 'table'" style="height: 100%">
<!-- <sqlLogAndOutput :hide="hideLogWindows">-->
<TableView <TableView
:ref="'tabs' + index" :ref="'tabs' + index"
:is-active=" :is-active="
@ -80,11 +77,8 @@
:hide-log-windows.sync="hideLogWindows" :hide-log-windows.sync="hideLogWindows"
:nodes="tab._nodes" :nodes="tab._nodes"
/> />
<!-- </sqlLogAndOutput>-->
</div> </div>
<div v-else-if="tab._nodes.type === 'view'" style="height: 100%"> <div v-else-if="tab._nodes.type === 'view'" style="height: 100%">
<!-- <sqlLogAndOutput>-->
<!-- <ViewTab :ref="'tabs'+index" :nodes="tab._nodes" />-->
<TableView <TableView
:ref="'tabs' + index" :ref="'tabs' + index"
:is-active=" :is-active="
@ -247,22 +241,118 @@
<h1>{{ tab._nodes }}</h1> <h1>{{ tab._nodes }}</h1>
</div> </div>
</v-tab-item> </v-tab-item>
<!-- </v-tabs-items>-->
</v-tabs-items> </v-tabs-items>
<!-- tooltip: Add new table --> <!-- Add / Import -->
<v-menu v-if="_isUIAllowed('addOrImport')" offset-y>
<template #activator="{ on }">
<v-btn
color="primary"
style="height: 100%; padding: 5px;"
v-on="on"
>
<x-icon <x-icon
v-if="_isUIAllowed('addTable')"
:tooltip="$t('tooltip.addTable')"
icon-class="add-btn" icon-class="add-btn"
:color="['white', 'grey lighten-2']" :color="['white', 'grey lighten-2']"
@click="dialogCreateTableShowMethod"
> >
mdi-plus-box mdi-plus-box
</x-icon> </x-icon>
<span class="flex-grow-1 caption font-weight-bold text-capitalize mx-2">
<!-- TODO: i18n -->
Add / Import
</span>
<v-spacer /> <v-spacer />
</v-tabs> </v-btn>
</template>
<v-list class="addOrImport">
<v-list-item
v-if="_isUIAllowed('addTable')"
v-t="['a:table:import-from-excel']"
@click="dialogCreateTableShowMethod"
>
<v-list-item-title>
<v-icon small>
mdi-table
</v-icon>
<span class="caption">
<!-- Add new table -->
{{ $t('tooltip.addTable') }}
</span>
</v-list-item-title>
</v-list-item>
<v-divider class="my-1" />
<v-subheader class="caption" style="height:35px">
QUICK IMPORT FROM
</v-subheader>
<v-list-item
v-if="_isUIAllowed('airtableImport')"
v-t="['a:actions:import-airtable']"
@click="airtableImportModal = true"
>
<v-list-item-title>
<v-icon small>
mdi-table-large
</v-icon>
<span class="caption">
<!-- TODO: i18n -->
Airtable
</span>
</v-list-item-title>
</v-list-item>
<v-list-item
v-if="_isUIAllowed('csvQuickImport')"
v-t="['a:actions:import-csv']"
@click="onImportFromExcelOrCSV('csv')"
>
<v-list-item-title>
<v-icon small>
mdi-file-document-outline
</v-icon>
<span class="caption">
<!-- TODO: i18n -->
CSV file
</span>
</v-list-item-title>
</v-list-item>
<v-list-item
v-if="_isUIAllowed('excelQuickImport')"
v-t="['a:actions:import-excel']"
@click="onImportFromExcelOrCSV('excel')"
>
<v-list-item-title>
<v-icon small>
mdi-file-excel
</v-icon>
<span class="caption">
<!-- TODO: i18n -->
Microsoft Excel
</span>
</v-list-item-title>
</v-list-item>
<v-divider class="my-1" />
<v-list-item
v-if="_isUIAllowed('importRequest')"
v-t="['e:datasource:import-request']"
href="https://github.com/nocodb/nocodb/issues/2052"
target="_blank"
>
<v-list-item-title>
<v-icon small>
mdi-open-in-new
</v-icon>
<span class="caption">
<!-- TODO: i18n -->
Request a data source you need ?
</span>
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-tabs>
<!-- Create Empty Table -->
<dlg-table-create <dlg-table-create
v-if="dialogCreateTableShow" v-if="dialogCreateTableShow"
v-model="dialogCreateTableShow" v-model="dialogCreateTableShow"
@ -271,42 +361,53 @@
dialogCreateTableShow = false; dialogCreateTableShow = false;
" "
/> />
<!-- Import From Excel / CSV -->
<quick-import
ref="quickImport"
v-model="quickImportModal"
:quick-import-type="quickImportType"
hide-label
@closeModal="quickImportModal = false"
/>
<!-- <screensaver v-if="showScreensaver && !($store.state.project.projectInfo && $store.state.project.projectInfo.ncMin)" class="screensaver" />--> <import-from-airtable v-if="airtableImportModal" v-model="airtableImportModal" />
</v-container> </v-container>
</template> </template>
<script> <script>
import { mapGetters, mapMutations } from "vuex"; import { mapGetters, mapMutations } from 'vuex'
import treeViewIcons from "../helpers/treeViewIcons"; import treeViewIcons from '../helpers/treeViewIcons'
import TableView from "./project/table"; import TableView from './project/Table'
import FunctionTab from "./project/function"; import FunctionTab from './project/Function'
import ProcedureTab from "./project/procedure"; import ProcedureTab from './project/Procedure'
import SequenceTab from "./project/sequence"; import SequenceTab from './project/Sequence'
import SeedTab from "./project/seed"; import SeedTab from './project/Seed'
import SqlClientTab from "./project/sqlClient"; import SqlClientTab from './project/SqlClient'
import ApisTab from "./project/apis"; import ApisTab from './project/Apis'
import ApiClientTab from "./project/apiClientOld"; import ApiClientTab from './project/ApiClientOld'
import sqlLogAndOutput from "./project/sqlLogAndOutput"; import sqlLogAndOutput from './project/SqlLogAndOutput'
import graphqlClient from "./project/graphqlClient"; import graphqlClient from './project/GraphqlClient'
import xTerm from "./xTerm"; import xTerm from './XTerm'
import ApiClientSwaggerTab from "./project/apiClientSwagger"; import ApiClientSwaggerTab from './project/ApiClientSwagger'
import XcMeta from "./project/settings/xcMeta"; import XcMeta from './project/settings/XcMeta'
import XcInfo from "./project/xcInfo"; import XcInfo from './project/XcInfo'
import SwaggerClient from "@/components/project/swaggerClient"; import SwaggerClient from '~/components/project/SwaggerClient'
import DlgTableCreate from "@/components/utils/dlgTableCreate"; import DlgTableCreate from '~/components/utils/DlgTableCreate'
import AppStore from "@/components/project/appStore"; import AppStore from '~/components/project/AppStore'
import AuthTab from "@/components/authTab"; import AuthTab from '~/components/AuthTab'
import CronJobs from "@/components/project/cronJobs"; import CronJobs from '~/components/project/CronJobs'
import DisableOrEnableModels from "@/components/project/projectMetadata/disableOrEnableModels"; import DisableOrEnableModels from '~/components/project/projectMetadata/DisableOrEnableModels'
import ProjectSettings from "@/components/project/projectSettings"; import ProjectSettings from '~/components/project/ProjectSettings'
import GrpcClient from "@/components/project/grpcClient"; import GrpcClient from '~/components/project/GrpcClient'
import GlobalAcl from "@/components/globalAcl"; import GlobalAcl from '~/components/GlobalAcl'
import AuditTab from "~/components/project/auditTab"; import AuditTab from '~/components/project/AuditTab'
import QuickImport from '~/components/import/QuickImport'
import ImportFromAirtable from '~/components/import/ImportFromAirtable'
export default { export default {
components: { components: {
ImportFromAirtable,
SwaggerClient, SwaggerClient,
// Screensaver, // Screensaver,
DlgTableCreate, DlgTableCreate,
@ -334,147 +435,163 @@ export default {
sqlLogAndOutput, sqlLogAndOutput,
xTerm, xTerm,
graphqlClient, graphqlClient,
QuickImport
}, },
data() { data() {
return { return {
dragOver: false,
dialogCreateTableShow: false, dialogCreateTableShow: false,
test: "", test: '',
treeViewIcons, treeViewIcons,
hideLogWindows: false, hideLogWindows: false,
showScreensaver: false, showScreensaver: false,
}; quickImportModal: false,
quickImportType: '',
airtableImportModal: false
}
}, },
methods: { methods: {
dialogCreateTableShowMethod() { dialogCreateTableShowMethod() {
this.dialogCreateTableShow = true; this.dialogCreateTableShow = true
this.$e("c:table:create:navbar"); this.$e('c:table:create:navbar')
}, },
checkInactiveState() { checkInactiveState() {
let position = 0; let position = 0
let idleTime = 0; let idleTime = 0
// Increment the idle time counter every minute. // Increment the idle time counter every minute.
let idleInterval = setInterval(timerIncrement, 1000); let idleInterval = setInterval(timerIncrement, 1000)
const self = this; const self = this
// Zero the idle timer on mouse movement. // Zero the idle timer on mouse movement.
document.addEventListener("mousemove", (e) => { document.addEventListener('mousemove', (e) => {
self.showScreensaver = false; self.showScreensaver = false
idleTime = 0; idleTime = 0
clearInterval(idleInterval); clearInterval(idleInterval)
idleInterval = setInterval(timerIncrement, 1000); idleInterval = setInterval(timerIncrement, 1000)
}); })
document.addEventListener("keypress", (e) => { document.addEventListener('keypress', (e) => {
self.showScreensaver = false; self.showScreensaver = false
idleTime = 0; idleTime = 0
clearInterval(idleInterval); clearInterval(idleInterval)
idleInterval = setInterval(timerIncrement, 1000); idleInterval = setInterval(timerIncrement, 1000)
}); })
function timerIncrement() { function timerIncrement() {
idleTime = idleTime + 1; idleTime = idleTime + 1
if (idleTime > 120) { if (idleTime > 120) {
const title = document.title; const title = document.title
function scrolltitle() { function scrolltitle() {
document.title = title + Array(position).fill(" .").join(""); document.title = title + Array(position).fill(' .').join('')
position = ++position % 3; position = ++position % 3
if (self.showScreensaver) { if (self.showScreensaver) {
window.setTimeout(scrolltitle, 400); window.setTimeout(scrolltitle, 400)
} else { } else {
document.title = title; document.title = title
} }
} }
self.showScreensaver = self.$store.state.windows.screensaver; self.showScreensaver = self.$store.state.settings.screensaver
scrolltitle(); scrolltitle()
clearInterval(idleInterval); clearInterval(idleInterval)
} }
} }
}, },
async handleKeyDown(event) { async handleKeyDown(event) {
const activeTabEleKey = `tabs${this.activeTab}`; const activeTabEleKey = `tabs${this.activeTab}`
let isHandled = false; let isHandled = false
if ( if (
this.$refs[activeTabEleKey] && this.$refs[activeTabEleKey] &&
this.$refs[activeTabEleKey][0] && this.$refs[activeTabEleKey][0] &&
this.$refs[activeTabEleKey][0].handleKeyDown this.$refs[activeTabEleKey][0].handleKeyDown
) { ) {
isHandled = await this.$refs[activeTabEleKey][0].handleKeyDown(event); isHandled = await this.$refs[activeTabEleKey][0].handleKeyDown(event)
} }
if (!isHandled) { if (!isHandled) {
switch ( switch (
[this._isMac ? event.metaKey : event.ctrlKey, event.key].join("_") [this._isMac ? event.metaKey : event.ctrlKey, event.key].join('_')
) { ) {
case "true_w": case 'true_w':
this.removeTab(this.activeTab); this.removeTab(this.activeTab)
event.preventDefault(); event.preventDefault()
event.stopPropagation(); event.stopPropagation()
break; break
} }
} }
}, },
...mapMutations({ ...mapMutations({
setActiveTab: "tabs/active", setActiveTab: 'tabs/active',
removeTab: "tabs/remove", removeTab: 'tabs/remove',
updateActiveTabx: "tabs/activeTabCtx", updateActiveTabx: 'tabs/activeTabCtx'
}), }),
tabActivated(tab) {}, tabActivated(tab) {},
onImportFromExcelOrCSV(quickImportType) {
this.quickImportModal = true
this.quickImportType = quickImportType
},
onAirtableImport() {
this.airtableImportModal = true
}
}, },
computed: { computed: {
...mapGetters({ tabs: "tabs/list", activeTabCtx: "tabs/activeTabCtx" }), ...mapGetters({ tabs: 'tabs/list', activeTabCtx: 'tabs/activeTabCtx' }),
pid() { pid() {
return this.$route.params.project_id; return this.$route.params.project_id
}, },
activeTab: { activeTab: {
set(tab) { set(tab) {
if (!tab) { if (!tab) {
return this.$router.push({ return this.$router.push({
query: {}, query: {}
}); })
} }
const [type, dbalias, name] = tab.split("||"); const [type, dbalias, name] = tab.split('||')
this.$router.push({ this.$router.push({
query: { query: {
...this.$route.query, ...this.$route.query,
type, type,
dbalias, dbalias,
name, name
}, }
}); })
}, },
get() { get() {
return [ return [
this.$route.query.type, this.$route.query.type,
this.$route.query.dbalias, this.$route.query.dbalias,
this.$route.query.name, this.$route.query.name
].join("||"); ].join('||')
}, }
}, }
}, },
beforeCreated() {}, beforeCreated() {},
watch: {}, watch: {},
created() { created() {
document.addEventListener("keydown", this.handleKeyDown); document.addEventListener('keydown', this.handleKeyDown)
/** /**
* Listening for tab change so that we can hide/show projectlogs based on tab * Listening for tab change so that we can hide/show projectlogs based on tab
*/ */
}, },
mounted() {}, mounted() {
if (this.$route && this.$route.query && this.$route.query.excelUrl) {
this.quickImportModal = true
}
},
beforeDestroy() {}, beforeDestroy() {},
destroyed() { destroyed() {
document.removeEventListener("keydown", this.handleKeyDown); document.removeEventListener('keydown', this.handleKeyDown)
}, },
directives: {}, directives: {},
validate({ params }) { validate({ params }) {
return true; return true
}, },
head() { head() {
return {}; return {}
}, },
props: {}, props: {}
}; }
</script> </script>
<style scoped> <style scoped>
@ -544,6 +661,14 @@ export default {
font-weight: bold; font-weight: bold;
} }
.addOrImport {
min-width: 200px;
}
.addOrImport .v-list-item {
min-height: 35px;
}
/deep/ .add-btn { /deep/ .add-btn {
margin-left: 5px; margin-left: 5px;
} }
@ -562,6 +687,7 @@ export default {
* *
* @author Naveen MR <oof1lab@gmail.com> * @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com> * @author Pranav C Balan <pranavxc@gmail.com>
* @author Wing-Kam Wong <wingkwong.code@gmail.com>
* *
* @license GNU AGPL version 3 or any later version * @license GNU AGPL version 3 or any later version
* *

62
packages/nc-gui/components/ProjectTreeView.vue

@ -17,7 +17,7 @@
> >
{{ $store.getters["project/GtrProjectName"] }} {{ $store.getters["project/GtrProjectName"] }}
</h3> </h3>
<github-star-btn v-else/> <github-star-btn v-else />
</div> </div>
<v-navigation-drawer <v-navigation-drawer
ref="drawer" ref="drawer"
@ -230,7 +230,7 @@
</template> </template>
</v-list-item-title> </v-list-item-title>
<v-spacer/> <v-spacer />
<v-tooltip bottom> <v-tooltip bottom>
<template #activator="{ on }"> <template #activator="{ on }">
@ -348,7 +348,7 @@
<span v-else class="caption">{{ child.name }}</span> <span v-else class="caption">{{ child.name }}</span>
</v-list-item-title> </v-list-item-title>
<template v-if="child.type === 'table'"> <template v-if="child.type === 'table'">
<v-spacer/> <v-spacer />
<div class="action d-flex" @click.stop> <div class="action d-flex" @click.stop>
<v-menu> <v-menu>
<template #activator="{ on }"> <template #activator="{ on }">
@ -483,7 +483,7 @@
/> />
</div> </div>
<div class="pr-3 advance-menu d-none" :class="{ 'pl-3': !mini }"> <div class="pr-3 advance-menu d-none" :class="{ 'pl-3': !mini }">
<v-divider v-if="_isUIAllowed('treeViewProjectSettings')"/> <v-divider v-if="_isUIAllowed('treeViewProjectSettings')" />
<v-list <v-list
v-if="_isUIAllowed('treeViewProjectSettings')" v-if="_isUIAllowed('treeViewProjectSettings')"
@ -621,7 +621,7 @@
</v-tooltip> </v-tooltip>
</template> </template>
</v-list> </v-list>
<v-divider/> <v-divider />
<v-list v-if="_isUIAllowed('previewAs') || previewAs" dense> <v-list v-if="_isUIAllowed('previewAs') || previewAs" dense>
<v-list-item> <v-list-item>
@ -678,9 +678,7 @@
</v-list> </v-list>
</div> </div>
<v-divider />
<v-divider/>
<div <div
v-t="['e:api-docs']" v-t="['e:api-docs']"
class="caption pointer nc-docs pb-2 pl-5 pr-3 pt-2 d-flex align-center" class="caption pointer nc-docs pb-2 pl-5 pr-3 pt-2 d-flex align-center"
@ -692,7 +690,6 @@
{{ $t('title.apiDocs') }} {{ $t('title.apiDocs') }}
</div> </div>
<template v-if="_isUIAllowed('settings')"> <template v-if="_isUIAllowed('settings')">
<div class="pl-5 pr-3 d-flex align-center pb-2"> <div class="pl-5 pr-3 d-flex align-center pb-2">
<settings-modal> <settings-modal>
@ -712,9 +709,8 @@
</div> </div>
</template> </template>
<!-- <v-divider/>--> <!-- <v-divider/>-->
<!-- <extras class="pl-1"/>--> <!-- <extras class="pl-1"/>-->
</div> </div>
</v-navigation-drawer> </v-navigation-drawer>
@ -769,12 +765,12 @@
:heading="selectedNodeForDelete.heading" :heading="selectedNodeForDelete.heading"
type="error" type="error"
/> />
<excel-import <quick-import
ref="excelImport" ref="quickImport"
v-model="excelImportDialog" v-model="quickImportDialog"
hide-label hide-label
import-to-project import-to-project
@success="onExcelImport" @success="onQuickImport"
/> />
</div> </div>
</template> </template>
@ -788,20 +784,20 @@ import rightClickOptions from "../helpers/rightClickOptions";
import rightClickOptionsSub from "../helpers/rightClickOptionsSub"; import rightClickOptionsSub from "../helpers/rightClickOptionsSub";
import icons from "../helpers/treeViewIcons"; import icons from "../helpers/treeViewIcons";
import textDlgSubmitCancel from "./utils/dlgTextSubmitCancel"; import textDlgSubmitCancel from "./utils/DlgTextSubmitCancel";
import dlgLabelSubmitCancel from "./utils/dlgLabelSubmitCancel"; import dlgLabelSubmitCancel from "./utils/DlgLabelSubmitCancel";
import {copyTextToClipboard} from "../helpers/xutils"; import {copyTextToClipboard} from "../helpers/xutils";
import DlgTableCreate from "@/components/utils/dlgTableCreate"; import DlgTableCreate from "~/components/utils/DlgTableCreate";
import DlgViewCreate from "@/components/utils/dlgViewCreate"; import DlgViewCreate from "~/components/utils/DlgViewCreate";
import SponsorMini from "@/components/sponsorMini"; import SponsorMini from "~/components/SponsorMini";
import {validateTableName} from "~/helpers"; import {validateTableName} from "~/helpers";
import ExcelImport from "~/components/import/excelImport"; import QuickImport from "~/components/import/QuickImport";
import draggable from "vuedraggable"; import draggable from "vuedraggable";
import GithubStarBtn from "~/components/githubStarBtn"; import GithubStarBtn from "~/components/GithubStarBtn";
import SettingsModal from "~/components/settings/settingsModal"; import SettingsModal from "~/components/settings/SettingsModal";
import Language from "~/components/utils/language"; import Language from "~/components/utils/Language";
import Extras from "~/components/project/spreadsheet/components/extras"; import Extras from "~/components/project/spreadsheet/components/Extras";
export default { export default {
components: { components: {
@ -810,7 +806,7 @@ export default {
SettingsModal, SettingsModal,
GithubStarBtn, GithubStarBtn,
draggable, draggable,
ExcelImport, QuickImport,
SponsorMini, SponsorMini,
DlgViewCreate, DlgViewCreate,
DlgTableCreate, DlgTableCreate,
@ -864,7 +860,7 @@ export default {
open: [], open: [],
search: null, search: null,
menuVisible: false, menuVisible: false,
excelImportDialog: false, quickImportDialog: false,
x: 0, x: 0,
y: 0, y: 0,
menuItem: null, menuItem: null,
@ -913,7 +909,7 @@ export default {
}), }),
computed: { computed: {
apiLink() { apiLink() {
return new URL(`/api/v1/db/meta/projects/${this.projectId}/swagger`, this.$store.state.project.projectInfo && this.$store.state.project.projectInfo.ncSiteUrl) return new URL(`/api/v1/db/meta/projects/${this.projectId}/swagger`, this.$store.state.project.appInfo && this.$store.state.project.appInfo.ncSiteUrl)
}, },
previewAs: { previewAs: {
get() { get() {
@ -1067,8 +1063,8 @@ export default {
}, },
changeTheme() { changeTheme() {
this.$store.dispatch( this.$store.dispatch(
"windows/ActToggleDarkMode", "settings/ActToggleDarkMode",
!this.$store.state.windows.darkTheme !this.$store.state.settings.darkTheme
); );
}, },
openLink(link) { openLink(link) {
@ -1162,7 +1158,7 @@ export default {
this.miniExpanded = false; this.miniExpanded = false;
} }
}, },
onExcelImport() { onQuickImport() {
if (!this.menuItem || this.menuItem.type !== "tableDir") { if (!this.menuItem || this.menuItem.type !== "tableDir") {
this.menuItem = this.listViewArr.find((n) => n.type === "tableDir"); this.menuItem = this.listViewArr.find((n) => n.type === "tableDir");
} }
@ -1257,7 +1253,7 @@ export default {
if (item._nodes.type === "table") { if (item._nodes.type === "table") {
let tableIndex = +item._nodes.key.split(".").pop(); let tableIndex = +item._nodes.key.split(".").pop();
if ( if (
!(await this.$store.dispatch("windows/ActCheckMaxTable", { !(await this.$store.dispatch("settings/ActCheckMaxTable", {
tableIndex, tableIndex,
})) }))
) { ) {
@ -1437,7 +1433,7 @@ export default {
await this.loadViews(this.menuItem); await this.loadViews(this.menuItem);
this.$toast.success("Views refreshed").goAway(1000); this.$toast.success("Views refreshed").goAway(1000);
} else if (action === "IMPORT_EXCEL") { } else if (action === "IMPORT_EXCEL") {
this.excelImportDialog = true; this.quickImportDialog = true;
} else if (action === "ENV_DB_FUNCTIONS_REFRESH") { } else if (action === "ENV_DB_FUNCTIONS_REFRESH") {
await this.loadFunctions(this.menuItem); await this.loadFunctions(this.menuItem);
this.$toast.success("Functions refreshed").goAway(1000); this.$toast.success("Functions refreshed").goAway(1000);

6
packages/nc-gui/components/README.md

@ -1,6 +0,0 @@
# COMPONENTS
The components directory contains your Vue.js Components.
Nuxt.js doesn't supercharge these components.
**This directory is not required, you can delete it if you don't want to use it.**

0
packages/nc-gui/components/releaseInfo.vue → packages/nc-gui/components/ReleaseInfo.vue

0
packages/nc-gui/components/screensaver.vue → packages/nc-gui/components/Screensaver.vue

42
packages/nc-gui/components/settings.vue → packages/nc-gui/components/Settings.vue

@ -161,7 +161,7 @@
Version Version
</td> </td>
<td> <td>
<span @contextmenu="rightClick">{{ $store.state.windows.version }}</span> <span @contextmenu="rightClick">{{ $store.state.settings.version }}</span>
</td> </td>
</tr> </tr>
<tr @dblclick="enableAppRefresh = true"> <tr @dblclick="enableAppRefresh = true">
@ -226,7 +226,7 @@
<script> <script>
import themes from '../helpers/themes' import themes from '../helpers/themes'
import dlgLabelSubmitCancel from './utils/dlgLabelSubmitCancel' import dlgLabelSubmitCancel from './utils/DlgLabelSubmitCancel'
export default { export default {
components: { dlgLabelSubmitCancel }, components: { dlgLabelSubmitCancel },
@ -275,42 +275,42 @@ export default {
computed: { computed: {
checkForUpdate: { checkForUpdate: {
get() { get() {
return this.$store.state.windows.checkForUpdate return this.$store.state.settings.checkForUpdate
}, },
set(value) { set(value) {
this.$store.commit('windows/MutCheckForUpdate', value) this.$store.commit('settings/MutCheckForUpdate', value)
} }
}, },
autoUpdate: { autoUpdate: {
get() { get() {
return this.$store.state.windows.downloadAndUpdateRelease return this.$store.state.settings.downloadAndUpdateRelease
}, },
set(value) { set(value) {
this.$store.commit('windows/MutDownloadAndUpdateRelease', value) this.$store.commit('settings/MutDownloadAndUpdateRelease', value)
} }
}, },
isGaEnabled: { isGaEnabled: {
get() { get() {
return this.$store.state.windows.isGaEnabled return this.$store.state.settings.isGaEnabled
}, },
set(value) { set(value) {
this.$store.commit('windows/MutToggleGaEnabled', value) this.$store.commit('settings/MutToggleGaEnabled', value)
} }
}, },
isErrorReportingEnabled: { isErrorReportingEnabled: {
get() { get() {
return this.$store.state.windows.isErrorReportingEnabled return this.$store.state.settings.isErrorReportingEnabled
}, },
set(value) { set(value) {
this.$store.commit('windows/MutToggleErrorReportingEnabled', value) this.$store.commit('settings/MutToggleErrorReportingEnabled', value)
} }
}, },
isTelemetryEnabled: { isTelemetryEnabled: {
get() { get() {
return this.$store.state.windows.isErrorReportingEnabled return this.$store.state.settings.isErrorReportingEnabled
}, },
set(value) { set(value) {
this.$store.commit('windows/MutToggleTelemetryEnabled', value) this.$store.commit('settings/MutToggleTelemetryEnabled', value)
} }
}, },
dialogShow: { dialogShow: {
@ -323,24 +323,24 @@ export default {
}, },
language: { language: {
get() { get() {
return this.$store.state.windows.language return this.$store.state.settings.language
}, },
set(val) { set(val) {
this.$store.commit('windows/MutSetLanguage', val) this.$store.commit('settings/MutSetLanguage', val)
} }
} }
}, },
watch: {}, watch: {},
created() { created() {
this.customTheme = { ...this.customTheme, ...this.$store.state.windows.customTheme } this.customTheme = { ...this.customTheme, ...this.$store.state.settings.customTheme }
this.item = this.$store.state.windows.themeName this.item = this.$store.state.settings.themeName
this.$store.watch( this.$store.watch(
state => state.windows.customTheme, state => state.settings.customTheme,
(theme) => { (theme) => {
this.customTheme = { ...this.customTheme, ...theme } this.customTheme = { ...this.customTheme, ...theme }
}) })
this.$store.watch(state => state.windows.themeName, this.$store.watch(state => state.settings.themeName,
(theme) => { (theme) => {
this.$nextTick(() => { this.$nextTick(() => {
if (this.item !== theme) { this.item = theme } if (this.item !== theme) { this.item = theme }
@ -396,11 +396,11 @@ export default {
}, },
async changeTheme(t, theme = 'Custom') { async changeTheme(t, theme = 'Custom') {
this.item = theme this.item = theme
if (theme === 'Custom') { await this.$store.dispatch('windows/ActSetTheme', { theme: { ...t }, custom: true }) } if (theme === 'Custom') { await this.$store.dispatch('settings/ActSetTheme', { theme: { ...t }, custom: true }) }
await this.$store.dispatch('windows/ActSetTheme', { theme: { ...t }, themeName: theme }) await this.$store.dispatch('settings/ActSetTheme', { theme: { ...t }, themeName: theme })
}, },
toggleDarkTheme() { toggleDarkTheme() {
this.$store.commit('windows/MutToggleDarkMode') this.$store.commit('settings/MutToggleDarkMode')
} }
}, },
beforeCreated() { beforeCreated() {

0
packages/nc-gui/components/share-icons.vue → packages/nc-gui/components/ShareIcons.vue

0
packages/nc-gui/components/snackbar.vue → packages/nc-gui/components/Snackbar.vue

0
packages/nc-gui/components/sponsorMini.vue → packages/nc-gui/components/SponsorMini.vue

0
packages/nc-gui/components/sponsorOverlay.vue → packages/nc-gui/components/SponsorOverlay.vue

2
packages/nc-gui/components/xTerm.vue → packages/nc-gui/components/XTerm.vue

@ -178,7 +178,7 @@ import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit' import { FitAddon } from 'xterm-addon-fit'
import { WebLinksAddon } from 'xterm-addon-web-links' import { WebLinksAddon } from 'xterm-addon-web-links'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import XIcon from './global/xIcon' import XIcon from './global/XIcon'
export default { export default {
name: 'XTerm', name: 'XTerm',

0
packages/nc-gui/components/xcDiff.vue → packages/nc-gui/components/XcDiff.vue

0
packages/nc-gui/components/apiClient/headers.vue → packages/nc-gui/components/apiClient/Headers.vue

0
packages/nc-gui/components/apiClient/params.vue → packages/nc-gui/components/apiClient/Params.vue

0
packages/nc-gui/components/apiClient/perfTest.vue → packages/nc-gui/components/apiClient/PerfTest.vue

86
packages/nc-gui/components/auth.routes.js

@ -1,86 +0,0 @@
const authorizedRoutes = {}
authorizedRoutes['/user/settings'] = true
authorizedRoutes['/user/settings/accounts'] = true
authorizedRoutes['/user/settings/password'] = true
authorizedRoutes['/referral'] = true
authorizedRoutes['/realestate/profits'] = true
authorizedRoutes['/realestate/bygeo'] = true
authorizedRoutes['/payment/buy'] = true
authorizedRoutes['/pricing'] = false
authorizedRoutes['/user'] = true
authorizedRoutes['/user/authentication'] = true
authorizedRoutes['/user/password'] = true
authorizedRoutes['/error/400'] = false
authorizedRoutes['/realestate/capitalgains'] = true
authorizedRoutes['/payment/train'] = true
authorizedRoutes['/user/settings'] = true
authorizedRoutes['/info/contact'] = false
authorizedRoutes['/profits'] = false
authorizedRoutes['/user/admin'] = true
authorizedRoutes['/error/403'] = false
authorizedRoutes['/error/404'] = false
authorizedRoutes['/info/hiring'] = false
authorizedRoutes['/user/settings/accounts'] = true
authorizedRoutes['/user/password/reset'] = false
authorizedRoutes['/user/settings/profile'] = true
authorizedRoutes['/user/settings/picture'] = true
authorizedRoutes['/user/password/forgot'] = false
authorizedRoutes['/user/authentication/signup'] = false
authorizedRoutes['/user/settings/password'] = true
authorizedRoutes['/user/admin/user-edit'] = true
authorizedRoutes['/user/authentication/signin'] = false
authorizedRoutes['/user/password/reset/success'] = true
authorizedRoutes['/user/password/reset/invalid'] = true
authorizedRoutes['/user/password/reset/form'] = true
// // authorizedRoutes['/user/admin/user/_userId'] = true;
// // authorizedRoutes['/'] = false;
// authorizedRoutes['/realestate/profits'] = true;
// let freeRoutes = {};
// freeRoutes['/'] = true;
// freeRoutes['/pricing'] = true;
// freeRoutes['user/authentication/signin'] = true;
// freeRoutes['user/authentication/signup'] = true;
exports.allowed = function(store, path) {
// console.log('store.getters.GtrUser',store.getters.GtrUser);
// console.log('path',path);
// && authorizedRoutes[path]
if (store.getters.GtrUser === null && path in authorizedRoutes && authorizedRoutes[path]) {
return false
} else {
return true
}
}
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

0
packages/nc-gui/components/auth/apiTokens.vue → packages/nc-gui/components/auth/ApiTokens.vue

0
packages/nc-gui/components/auth/authHooks.vue → packages/nc-gui/components/auth/AuthHooks.vue

2
packages/nc-gui/components/auth/roles.vue → packages/nc-gui/components/auth/Roles.vue

@ -184,7 +184,7 @@
</template> </template>
<script> <script>
import DlgLabelSubmitCancel from '@/components/utils/dlgLabelSubmitCancel' import DlgLabelSubmitCancel from '~/components/utils/DlgLabelSubmitCancel'
import colors from '@/mixins/colors' import colors from '@/mixins/colors'
export default { export default {

140
packages/nc-gui/components/auth/shareOrInviteModal.vue → packages/nc-gui/components/auth/ShareOrInviteModal.vue

@ -14,9 +14,15 @@
<v-card-text> <v-card-text>
<div> <div>
<v-icon small> mdi-account-outline </v-icon> <v-icon small>
<template v-if="invite_token"> Copy Invite Token </template> mdi-account-outline
<template v-else-if="selectedUser.id"> Edit User </template> </v-icon>
<template v-if="invite_token">
Copy Invite Token
</template>
<template v-else-if="selectedUser.id">
Edit User
</template>
<template v-else> <template v-else>
<!-- Invite Team --> <!-- Invite Team -->
{{ $t("activity.inviteTeam") }} {{ $t("activity.inviteTeam") }}
@ -35,7 +41,9 @@
" "
> >
<template #append> <template #append>
<v-icon color="green" class="ml-2"> mdi-content-copy </v-icon> <v-icon color="green" class="ml-2">
mdi-content-copy
</v-icon>
</template> </template>
<div class="ellipsis d-100"> <div class="ellipsis d-100">
{{ inviteUrl }} {{ inviteUrl }}
@ -151,99 +159,99 @@
</template> </template>
<script> <script>
import { isEmail } from "~/helpers"; import { isEmail } from '~/helpers'
import { enumColor } from "~/components/project/spreadsheet/helpers/colors"; import { enumColor } from '~/components/project/spreadsheet/helpers/colors'
import ShareBase from "~/components/base/shareBase"; import ShareBase from '~/components/base/ShareBase'
export default { export default {
name: "ShareOrInviteModal", name: 'ShareOrInviteModal',
components: { ShareBase }, components: { ShareBase },
props: { props: {
value: Boolean, value: Boolean
}, },
data: () => ({ data: () => ({
roles: ["creator", "editor", "commenter", "viewer"], roles: ['creator', 'editor', 'commenter', 'viewer'],
selectedUser: {}, selectedUser: {},
invite_token: null, invite_token: null,
valid: null, valid: null,
emailRules: [ emailRules: [
(v) => !!v || "E-mail is required", v => !!v || 'E-mail is required',
(v) => { (v) => {
const invalidEmails = (v || "") const invalidEmails = (v || '')
.split(/\s*,\s*/) .split(/\s*,\s*/)
.filter((e) => !isEmail(e)); .filter(e => !isEmail(e))
return ( return (
!invalidEmails.length || !invalidEmails.length ||
`"${invalidEmails.join(", ")}" - invalid email` `"${invalidEmails.join(', ')}" - invalid email`
); )
}, }
], ],
roleRules: [ roleRules: [
(v) => !!v || "User Role is required", v => !!v || 'User Role is required',
(v) => v =>
["creator", "editor", "commenter", "viewer"].includes(v) || ['creator', 'editor', 'commenter', 'viewer'].includes(v) ||
"invalid user role", 'invalid user role'
], ],
userList: [], userList: [],
roleDescriptions: {}, roleDescriptions: {},
deleteUserType: "", deleteUserType: ''
}), }),
computed: { computed: {
userEditDialog: { userEditDialog: {
get() { get() {
return this.value; return this.value
}, },
set(v) { set(v) {
this.$emit("input", v); this.$emit('input', v)
}, }
}, },
inviteUrl() { inviteUrl() {
return this.invite_token return this.invite_token
? `${location.origin}${location.pathname}#/user/authentication/signup/${this.invite_token.invite_token}` ? `${location.origin}${location.pathname}#/user/authentication/signup/${this.invite_token.invite_token}`
: null; : null
}, },
rolesColors() { rolesColors() {
const colors = this.$store.state.windows.darkTheme const colors = this.$store.state.settings.darkTheme
? enumColor.dark ? enumColor.dark
: enumColor.light; : enumColor.light
return ['owner'].concat(this.roles).reduce((o, r, i) => { return ['owner'].concat(this.roles).reduce((o, r, i) => {
o[r] = colors[i % colors.length]; o[r] = colors[i % colors.length]
return o; return o
}, {}); }, {})
}, },
selectedRoles: { selectedRoles: {
get() { get() {
return ( return (
this.selectedUser && this.selectedUser.roles this.selectedUser && this.selectedUser.roles
? this.selectedUser.roles.split(",") ? this.selectedUser.roles.split(',')
: [] : []
).sort( ).sort(
(a, b) => this.roleNames.indexOf(a) - this.roleNames.indexOf(a) (a, b) => this.roleNames.indexOf(a) - this.roleNames.indexOf(a)
)[0]; )[0]
}, },
set(roles) { set(roles) {
if (this.selectedUser) { if (this.selectedUser) {
this.selectedUser.roles = roles; // .filter(Boolean).join(',') this.selectedUser.roles = roles // .filter(Boolean).join(',')
}
}
} }
},
},
}, },
methods: { methods: {
async saveUser() { async saveUser() {
this.validate = true; this.validate = true
await this.$nextTick(); await this.$nextTick()
if (this.loading || !this.$refs.form.validate() || !this.selectedUser) { if (this.loading || !this.$refs.form.validate() || !this.selectedUser) {
return; return
} }
this.$e("a:user:invite", { role: this.selectedUser.roles }); this.$e('a:user:invite', { role: this.selectedUser.roles })
if (!this.edited) { if (!this.edited) {
this.userEditDialog = false; this.userEditDialog = false
} }
try { try {
let data; let data
if (this.selectedUser.id) { if (this.selectedUser.id) {
await this.$api.auth.projectUserUpdate( await this.$api.auth.projectUserUpdate(
this.$route.params.project_id, this.$route.params.project_id,
@ -252,56 +260,56 @@ export default {
roles: this.selectedUser.roles, roles: this.selectedUser.roles,
email: this.selectedUser.email, email: this.selectedUser.email,
project_id: this.$route.params.project_id, project_id: this.$route.params.project_id,
projectName: this.$store.getters["project/GtrProjectName"], projectName: this.$store.getters['project/GtrProjectName']
} }
); )
} else { } else {
data = await this.$api.auth.projectUserAdd( data = await this.$api.auth.projectUserAdd(
this.$route.params.project_id, this.$route.params.project_id,
{ {
...this.selectedUser, ...this.selectedUser,
project_id: this.$route.params.project_id, project_id: this.$route.params.project_id,
projectName: this.$store.getters["project/GtrProjectName"], projectName: this.$store.getters['project/GtrProjectName']
} }
); )
} }
this.$toast this.$toast
.success("Successfully updated the user details") .success('Successfully updated the user details')
.goAway(3000); .goAway(3000)
this.$emit("saved"); this.$emit('saved')
if (data && data.invite_token) { if (data && data.invite_token) {
this.invite_token = data; this.invite_token = data
// todo: bring anim // todo: bring anim
// this.simpleAnim() // this.simpleAnim()
return; return
} }
} catch (e) { } catch (e) {
this.$toast this.$toast
.error(await this._extractSdkResponseErrorMsg(e)) .error(await this._extractSdkResponseErrorMsg(e))
.goAway(3000); .goAway(3000)
} }
this.userEditDialog = false; this.userEditDialog = false
}, },
clickInviteMore() { clickInviteMore() {
this.$e("c:user:invite-more"); this.$e('c:user:invite-more')
this.invite_token = null; this.invite_token = null
this.selectedUser = { roles: "editor" }; this.selectedUser = { roles: 'editor' }
}, },
clipboard(str) { clipboard(str) {
const el = document.createElement("textarea"); const el = document.createElement('textarea')
el.addEventListener("focusin", (e) => e.stopPropagation()); el.addEventListener('focusin', e => e.stopPropagation())
el.value = str; el.value = str
document.body.appendChild(el); document.body.appendChild(el)
el.select(); el.select()
document.execCommand("copy"); document.execCommand('copy')
document.body.removeChild(el); document.body.removeChild(el)
this.$e("c:user:copy-url"); this.$e('c:user:copy-url')
}, }
}, }
}; }
</script> </script>
<style scoped></style> <style scoped></style>

350
packages/nc-gui/components/auth/userManagement.vue → packages/nc-gui/components/auth/UserManagement.vue

@ -13,7 +13,9 @@
@keypress.enter="loadUsers" @keypress.enter="loadUsers"
> >
<template #prepend-inner> <template #prepend-inner>
<v-icon small class="mt-1"> search </v-icon> <v-icon small class="mt-1">
search
</v-icon>
</template> </template>
</v-text-field> </v-text-field>
<v-spacer /> <v-spacer />
@ -29,7 +31,9 @@
@click="clickReload" @click="clickReload"
@click.prevent @click.prevent
> >
<v-icon small left> refresh </v-icon> <v-icon small left>
refresh
</v-icon>
<!-- Reload --> <!-- Reload -->
{{ $t("general.reload") }} {{ $t("general.reload") }}
</x-btn> </x-btn>
@ -45,7 +49,9 @@
:disabled="loading" :disabled="loading"
@click="addUser" @click="addUser"
> >
<v-icon small left> mdi-plus </v-icon> <v-icon small left>
mdi-plus
</v-icon>
<!-- New User --> <!-- New User -->
{{ $t("activity.newUser") }} {{ $t("activity.newUser") }}
</x-btn> </x-btn>
@ -79,12 +85,16 @@
<tr class="text-left"> <tr class="text-left">
<!-- <th>#</th>--> <!-- <th>#</th>-->
<th class="font-weight-regular caption"> <th class="font-weight-regular caption">
<v-icon small> mdi-email-outline </v-icon> <v-icon small>
mdi-email-outline
</v-icon>
<!-- Email --> <!-- Email -->
{{ $t("labels.email") }} {{ $t("labels.email") }}
</th> </th>
<th class="font-weight-regular caption"> <th class="font-weight-regular caption">
<v-icon small> mdi-drama-masks </v-icon> <v-icon small>
mdi-drama-masks
</v-icon>
<!-- Roles --> <!-- Roles -->
{{ $t("objects.roles") }} {{ $t("objects.roles") }}
</th> </th>
@ -223,7 +233,9 @@
<tbody> <tbody>
<tr v-for="user in users" :key="user.email"> <tr v-for="user in users" :key="user.email">
<td> <td>
<v-icon x-large> mdi-account-outline </v-icon> <v-icon x-large>
mdi-account-outline
</v-icon>
</td> </td>
<td class="px-1 py-1" align="top"> <td class="px-1 py-1" align="top">
<v-text-field <v-text-field
@ -239,13 +251,19 @@
<set-list-checkbox-cell v-model="user.roles" :values="roles" /> <set-list-checkbox-cell v-model="user.roles" :values="roles" />
</td> </td>
<td align="middle"> <td align="middle">
<v-icon large> mdi-close </v-icon> <v-icon large>
<v-icon large> mdi-save </v-icon> mdi-close
</v-icon>
<v-icon large>
mdi-save
</v-icon>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<v-icon x-large> mdi-account-outline </v-icon> <v-icon x-large>
mdi-account-outline
</v-icon>
</td> </td>
<td class="px-1 py-1" align="top"> <td class="px-1 py-1" align="top">
<v-text-field solo class="elevation-0" hide-details dense /> <v-text-field solo class="elevation-0" hide-details dense />
@ -254,7 +272,9 @@
<set-list-checkbox-cell :values="roles" /> <set-list-checkbox-cell :values="roles" />
</td> </td>
<td align="middle"> <td align="middle">
<v-icon large> mdi-account-plus </v-icon> <v-icon large>
mdi-account-plus
</v-icon>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -285,9 +305,15 @@
<v-card-text> <v-card-text>
<div> <div>
<v-icon small> mdi-account-outline </v-icon> <v-icon small>
<template v-if="invite_token"> Copy Invite Token </template> mdi-account-outline
<template v-else-if="selectedUser.id"> Edit User </template> </v-icon>
<template v-if="invite_token">
Copy Invite Token
</template>
<template v-else-if="selectedUser.id">
Edit User
</template>
<template v-else> <template v-else>
<!-- Invite Team --> <!-- Invite Team -->
{{ $t("activity.inviteTeam") }} {{ $t("activity.inviteTeam") }}
@ -306,7 +332,9 @@
" "
> >
<template #append> <template #append>
<v-icon color="green" class="ml-2"> mdi-content-copy </v-icon> <v-icon color="green" class="ml-2">
mdi-content-copy
</v-icon>
</template> </template>
<div class="ellipsis d-100"> <div class="ellipsis d-100">
{{ inviteUrl }} {{ inviteUrl }}
@ -314,10 +342,8 @@
</v-alert> </v-alert>
<p class="caption grey--text mt-3"> <p class="caption grey--text mt-3">
<!-- Looks like you have not configured mailer yet! <br> Please copy above invite link and send it to --> <!-- Looks like you have not configured mailer yet! <br> Please copy above invite link and send it to -->
<pre>{{ $t('msg.info.userInviteNoSMTP') }} {{ invite_token && (invite_token.email || invite_token.emails && invite_token.emails.join(', ')) }}.</pre> <pre>{{ $t('msg.info.userInviteNoSMTP') }} {{ invite_token && (invite_token.email || invite_token.emails && invite_token.emails.join(', ')) }}.</pre>
</p> </p>
<div class="text-right"> <div class="text-right">
@ -425,22 +451,22 @@
</template> </template>
<script> <script>
import FeedbackForm from "@/components/feedbackForm"; import FeedbackForm from '~/components/FeedbackForm'
import SetListCheckboxCell from "@/components/project/spreadsheet/components/editableCell/setListCheckboxCell"; import SetListCheckboxCell from '~/components/project/spreadsheet/components/editableCell/SetListCheckboxCell'
import { enumColor } from "@/components/project/spreadsheet/helpers/colors"; import { enumColor } from '@/components/project/spreadsheet/helpers/colors'
import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel"; import DlgLabelSubmitCancel from '~/components/utils/DlgLabelSubmitCancel'
import { isEmail } from "@/helpers"; import { isEmail } from '@/helpers'
import ShareBase from "~/components/base/shareBase"; import ShareBase from '~/components/base/ShareBase'
import XBtn from "~/components/global/xBtn"; import XBtn from '~/components/global/XBtn'
export default { export default {
name: "UserManagement", name: 'UserManagement',
components: { components: {
XBtn, XBtn,
ShareBase, ShareBase,
FeedbackForm, FeedbackForm,
DlgLabelSubmitCancel, DlgLabelSubmitCancel,
SetListCheckboxCell, SetListCheckboxCell
}, },
data: () => ({ data: () => ({
validate: false, validate: false,
@ -455,206 +481,204 @@ export default {
loading: false, loading: false,
selectedUser: null, selectedUser: null,
roles: [], roles: [],
query: "", query: '',
deleteId: null, deleteId: null,
edited: false, edited: false,
valid: null, valid: null,
emailRules: [ emailRules: [
(v) => !!v || "E-mail is required", v => !!v || 'E-mail is required',
(v) => { (v) => {
const invalidEmails = (v || "") const invalidEmails = (v || '')
.split(/\s*,\s*/) .split(/\s*,\s*/)
.filter((e) => !isEmail(e)); .filter(e => !isEmail(e))
return ( return (
!invalidEmails.length || !invalidEmails.length ||
`"${invalidEmails.join(", ")}" - invalid email` `"${invalidEmails.join(', ')}" - invalid email`
); )
}, }
], ],
roleRules: [ roleRules: [
(v) => !!v || "User Role is required", v => !!v || 'User Role is required',
(v) => v =>
["creator", "editor", "commenter", "viewer"].includes(v) || ['creator', 'editor', 'commenter', 'viewer'].includes(v) ||
"invalid user role", 'invalid user role'
], ],
userList: [], userList: [],
roleDescriptions: {}, roleDescriptions: {},
deleteUserType: "", // [DELETE_FROM_PROJECT, DELETE_FROM_NOCODB] deleteUserType: '' // [DELETE_FROM_PROJECT, DELETE_FROM_NOCODB]
}), }),
computed: { computed: {
roleNames() { roleNames() {
return this.roles.map((r) => r.title); return this.roles.map(r => r.title)
}, },
inviteUrl() { inviteUrl() {
return this.invite_token return this.invite_token
? `${location.origin}${location.pathname}#/user/authentication/signup/${this.invite_token.invite_token}` ? `${location.origin}${location.pathname}#/user/authentication/signup/${this.invite_token.invite_token}`
: null; : null
}, },
rolesColors() { rolesColors() {
const colors = this.$store.state.windows.darkTheme const colors = this.$store.state.settings.darkTheme
? enumColor.dark ? enumColor.dark
: enumColor.light; : enumColor.light
return ['owner'].concat(this.roles).reduce((o, r, i) => { return ['owner'].concat(this.roles).reduce((o, r, i) => {
o[r] = colors[i % colors.length]; o[r] = colors[i % colors.length]
return o; return o
}, {}); }, {})
}, },
selectedRoles: { selectedRoles: {
get() { get() {
return ( return (
this.selectedUser && this.selectedUser.roles this.selectedUser && this.selectedUser.roles
? this.selectedUser.roles.split(",") ? this.selectedUser.roles.split(',')
: [] : []
).sort( ).sort(
(a, b) => this.roleNames.indexOf(a) - this.roleNames.indexOf(a) (a, b) => this.roleNames.indexOf(a) - this.roleNames.indexOf(a)
)[0]; )[0]
}, },
set(roles) { set(roles) {
if (this.selectedUser) { if (this.selectedUser) {
this.selectedUser.roles = roles; // .filter(Boolean).join(',') this.selectedUser.roles = roles // .filter(Boolean).join(',')
}
} }
},
}, },
selectedUserIndex: { selectedUserIndex: {
get() { get() {
return this.users return this.users
? this.users.findIndex((u) => u.email === this.selectedUser.email) ? this.users.findIndex(u => u.email === this.selectedUser.email)
: -1; : -1
}, },
set(i) { set(i) {
this.selectedUser = this.users[i]; this.selectedUser = this.users[i]
}, }
}, },
dialogMessage() { dialogMessage() {
let msg = "Do you want to remove the user"; let msg = 'Do you want to remove the user'
if (this.deleteUserType === "DELETE_FROM_PROJECT") { if (this.deleteUserType === 'DELETE_FROM_PROJECT') {
msg += " from Project"; msg += ' from Project'
} else if (this.deleteUserType === "DELETE_FROM_NOCODB") { } else if (this.deleteUserType === 'DELETE_FROM_NOCODB') {
msg += " from NocoDB"; msg += ' from NocoDB'
} }
msg += "?"; msg += '?'
return msg; return msg
}, }
}, },
watch: { watch: {
options: { options: {
async handler() { async handler() {
await this.loadUsers(); await this.loadUsers()
}, },
deep: true, deep: true
}, },
userEditDialog(v) { userEditDialog(v) {
// if (!v) { this.validate = false } // if (!v) { this.validate = false }
if (v && this.selectedUser && !this.selectedUser.id) { if (v && this.selectedUser && !this.selectedUser.id) {
this.$nextTick(() => { this.$nextTick(() => {
setTimeout(() => { setTimeout(() => {
this.$refs.email.$el.querySelector("input").focus(); this.$refs.email.$el.querySelector('input').focus()
}, 100); }, 100)
}); })
}
} }
},
}, },
async created() { async created() {
this.$eventBus.$on("show-add-user", this.addUser); this.$eventBus.$on('show-add-user', this.addUser)
await this.loadUsers(); await this.loadUsers()
await this.loadRoles(); await this.loadRoles()
}, },
beforeDestroy() { beforeDestroy() {
this.$eventBus.$off("show-add-user", this.addUser); this.$eventBus.$off('show-add-user', this.addUser)
}, },
methods: { methods: {
clickReload() { clickReload() {
this.loadUsers(); this.loadUsers()
this.$e("a:user:reload"); this.$e('a:user:reload')
}, },
clickDeleteUser(id) { clickDeleteUser(id) {
this.$e("c:user:delete"); this.$e('c:user:delete')
this.deleteId = id; this.deleteId = id
this.deleteItem = id; this.deleteItem = id
this.showConfirmDlg = true; this.showConfirmDlg = true
this.deleteUserType = "DELETE_FROM_PROJECT"; this.deleteUserType = 'DELETE_FROM_PROJECT'
}, },
clickInviteMore() { clickInviteMore() {
this.$e("c:user:invite-more"); this.$e('c:user:invite-more')
this.invite_token = null; this.invite_token = null
this.selectedUser = { roles: "editor" }; this.selectedUser = { roles: 'editor' }
}, },
getRole(roles) { getRole(roles) {
return (roles ? roles.split(",") : []).sort( return (roles ? roles.split(',') : []).sort(
(a, b) => this.roleNames.indexOf(a) - this.roleNames.indexOf(a) (a, b) => this.roleNames.indexOf(a) - this.roleNames.indexOf(a)
)[0]; )[0]
}, },
simpleAnim() { simpleAnim() {
const count = 30; const count = 30
const defaults = { const defaults = {
origin: { y: 0.7 }, origin: { y: 0.7 },
zIndex: 9999999, zIndex: 9999999
}; }
function fire(particleRatio, opts) { function fire(particleRatio, opts) {
window.confetti( window.confetti(
Object.assign({}, defaults, opts, { Object.assign({}, defaults, opts, {
particleCount: Math.floor(count * particleRatio), particleCount: Math.floor(count * particleRatio)
}) })
); )
} }
fire(0.25, { fire(0.25, {
spread: 26, spread: 26,
startVelocity: 55, startVelocity: 55
}); })
fire(0.2, { fire(0.2, {
spread: 60, spread: 60
}); })
fire(0.35, { fire(0.35, {
spread: 100, spread: 100,
decay: 0.91, decay: 0.91,
scalar: 0.8, scalar: 0.8
}); })
fire(0.1, { fire(0.1, {
spread: 120, spread: 120,
startVelocity: 25, startVelocity: 25,
decay: 0.92, decay: 0.92,
scalar: 1.2, scalar: 1.2
}); })
fire(0.1, { fire(0.1, {
spread: 120, spread: 120,
startVelocity: 45, startVelocity: 45
}); })
}, },
getInviteUrl(token) { getInviteUrl(token) {
return token return token
? `${location.origin}${location.pathname}#/user/authentication/signup/${token}` ? `${location.origin}${location.pathname}#/user/authentication/signup/${token}`
: null; : null
}, },
clipboard(str) { clipboard(str) {
const el = document.createElement("textarea"); const el = document.createElement('textarea')
el.addEventListener("focusin", (e) => e.stopPropagation()); el.addEventListener('focusin', e => e.stopPropagation())
el.value = str; el.value = str
document.body.appendChild(el); document.body.appendChild(el)
el.select(); el.select()
document.execCommand("copy"); document.execCommand('copy')
document.body.removeChild(el); document.body.removeChild(el)
this.$e("c:user:copy-url"); this.$e('c:user:copy-url')
}, },
async resendInvite(id) { async resendInvite(id) {
try { try {
await this.$api.auth.projectUserResendInvite(this.$route.params.project_id, id) await this.$api.auth.projectUserResendInvite(this.$route.params.project_id, id)
this.$toast.success('Invite email sent successfully').goAway(3000) this.$toast.success('Invite email sent successfully').goAway(3000)
await this.loadUsers() await this.loadUsers()
} catch (e) { } catch (e) {
this.$toast.error(e.response.data.msg).goAway(3000); this.$toast.error(e.response.data.msg).goAway(3000)
} }
this.$e("a:user:resend-invite"); this.$e('a:user:resend-invite')
}, },
async loadUsers() { async loadUsers() {
try { try {
const { page = 1, itemsPerPage = 20 } = this.options; const { page = 1, itemsPerPage = 20 } = this.options
// const data = (await this.$axios.get('/admin', { // const data = (await this.$axios.get('/admin', {
// headers: { // headers: {
// 'xc-auth': this.$store.state.users.token // 'xc-auth': this.$store.state.users.token
@ -673,23 +697,23 @@ export default {
query: { query: {
limit: itemsPerPage, limit: itemsPerPage,
offset: (page - 1) * itemsPerPage, offset: (page - 1) * itemsPerPage,
query: this.query, query: this.query
},
} }
); }
)
this.count = userData.users.pageInfo.totalRows; this.count = userData.users.pageInfo.totalRows
this.users = userData.users.list; this.users = userData.users.list
if (!this.selectedUser && this.users && this.users[0]) { if (!this.selectedUser && this.users && this.users[0]) {
this.selectedUserIndex = 0; this.selectedUserIndex = 0
} }
} catch (e) { } catch (e) {
console.log(e); console.log(e)
} }
}, },
async loadRoles() { async loadRoles() {
try { try {
this.roles = ["creator", "editor", "commenter", "viewer"]; this.roles = ['creator', 'editor', 'commenter', 'viewer']
// todo: // todo:
// (await this.$axios.get('/admin/roles', { // (await this.$axios.get('/admin/roles', {
@ -704,7 +728,7 @@ export default {
// return role.title // return role.title
// }).filter(role => role !== 'guest') // }).filter(role => role !== 'guest')
} catch (e) { } catch (e) {
console.log(e); console.log(e)
} }
}, },
async deleteUser(id, type) { async deleteUser(id, type) {
@ -712,66 +736,66 @@ export default {
await this.$api.auth.projectUserRemove( await this.$api.auth.projectUserRemove(
this.$route.params.project_id, this.$route.params.project_id,
id id
); )
this.$toast this.$toast
.success( .success(
`Successfully removed the user from ${ `Successfully removed the user from ${
type === "DELETE_FROM_PROJECT" ? "project" : "NocoDB" type === 'DELETE_FROM_PROJECT' ? 'project' : 'NocoDB'
}` }`
) )
.goAway(3000); .goAway(3000)
await this.loadUsers(); await this.loadUsers()
} catch (e) { } catch (e) {
this.$toast.error(e.response.data.msg).goAway(3000); this.$toast.error(e.response.data.msg).goAway(3000)
} }
}, },
async confirmDelete(hideDialog) { async confirmDelete(hideDialog) {
if (hideDialog) { if (hideDialog) {
this.showConfirmDlg = false; this.showConfirmDlg = false
return; return
} }
await this.deleteUser(this.deleteId, this.deleteUserType); await this.deleteUser(this.deleteId, this.deleteUserType)
this.showConfirmDlg = false; this.showConfirmDlg = false
this.$e("a:user:delete"); this.$e('a:user:delete')
}, },
addUser() { addUser() {
this.invite_token = null; this.invite_token = null
this.selectedUser = { this.selectedUser = {
roles: "editor", roles: 'editor'
}; }
this.userEditDialog = true; this.userEditDialog = true
this.$e("c:user:add"); this.$e('c:user:add')
}, },
async inviteUser(item) { async inviteUser(item) {
try { try {
await this.$api.auth.projectUserAdd( await this.$api.auth.projectUserAdd(
this.$route.params.project_id, this.$route.params.project_id,
item item
); )
this.$toast.success("Successfully added user to project").goAway(3000); this.$toast.success('Successfully added user to project').goAway(3000)
await this.loadUsers(); await this.loadUsers()
} catch (e) { } catch (e) {
this.$toast.error(e.response.data.msg).goAway(3000); this.$toast.error(e.response.data.msg).goAway(3000)
} }
this.$e("a:user:add"); this.$e('a:user:add')
}, },
async saveUser() { async saveUser() {
this.validate = true; this.validate = true
await this.$nextTick(); await this.$nextTick()
if (this.loading || !this.$refs.form.validate() || !this.selectedUser) { if (this.loading || !this.$refs.form.validate() || !this.selectedUser) {
return; return
} }
this.$e("a:user:invite", { role: this.selectedUser.roles }); this.$e('a:user:invite', { role: this.selectedUser.roles })
if (!this.edited) { if (!this.edited) {
this.userEditDialog = false; this.userEditDialog = false
} }
try { try {
let data; let data
if (this.selectedUser.id) { if (this.selectedUser.id) {
await this.$api.auth.projectUserUpdate( await this.$api.auth.projectUserUpdate(
this.$route.params.project_id, this.$route.params.project_id,
@ -780,36 +804,36 @@ export default {
roles: this.selectedUser.roles, roles: this.selectedUser.roles,
email: this.selectedUser.email, email: this.selectedUser.email,
project_id: this.$route.params.project_id, project_id: this.$route.params.project_id,
projectName: this.$store.getters["project/GtrProjectName"], projectName: this.$store.getters['project/GtrProjectName']
} }
); )
} else { } else {
data = await this.$api.auth.projectUserAdd( data = await this.$api.auth.projectUserAdd(
this.$route.params.project_id, this.$route.params.project_id,
{ {
...this.selectedUser, ...this.selectedUser,
project_id: this.$route.params.project_id, project_id: this.$route.params.project_id,
projectName: this.$store.getters["project/GtrProjectName"], projectName: this.$store.getters['project/GtrProjectName']
} }
); )
} }
this.$toast this.$toast
.success("Successfully updated the user details") .success('Successfully updated the user details')
.goAway(3000); .goAway(3000)
await this.loadUsers(); await this.loadUsers()
if (data && data.invite_token) { if (data && data.invite_token) {
this.invite_token = data; this.invite_token = data
this.simpleAnim(); this.simpleAnim()
return; return
} }
} catch (e) { } catch (e) {
this.$toast.error(e.response.data.msg).goAway(3000); this.$toast.error(e.response.data.msg).goAway(3000)
} }
await this.loadUsers(); await this.loadUsers()
}, }
}, }
}; }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

0
packages/nc-gui/components/base/shareBase.vue → packages/nc-gui/components/base/ShareBase.vue

67
packages/nc-gui/components/global/NcSlider.vue

@ -0,0 +1,67 @@
<template>
<div class="nc-container" :class="{active:modal}" @click="modal=false">
<div class="nc-content elevation-3 pa-4" @click.stop>
<slot />
</div>
</div>
</template>
<script>
export default {
name: 'NcSlider',
props: {
value: Boolean
},
computed: {
modal: {
get() {
return this.value
},
set(v) {
this.$emit('input', v)
}
}
},
mounted() {
(document.querySelector('[data-app]') || this.$root.$el).append(this.$el)
},
destroyed() {
this.$el.parentNode && this.$el.parentNode.removeChild(this.$el)
}
}
</script>
<style scoped lang="scss">
.nc-container {
position: fixed;
pointer-events: none;
width: 100vw;
height: 100vh;
z-index: 9999;
right: 0;
top: 0;
.nc-content {
background-color: var(--v-backgroundColorDefault-base);
height: 100%;
width: max(50%, 700px);
position: absolute;
bottom: 0;
top: 0;
right: min(-50%, -700px);
transition: .3s right;
}
&.active {
pointer-events: all;
& > .nc-content {
right: 0
}
}
}
</style>

0
packages/nc-gui/components/global/recursiveMenu.vue → packages/nc-gui/components/global/RecursiveMenu.vue

0
packages/nc-gui/components/global/xAutoComplete.vue → packages/nc-gui/components/global/XAutoComplete.vue

0
packages/nc-gui/components/global/xBtn.vue → packages/nc-gui/components/global/XBtn.vue

0
packages/nc-gui/components/global/xIcon.vue → packages/nc-gui/components/global/XIcon.vue

0
packages/nc-gui/components/import/dropOrSelectFile.vue → packages/nc-gui/components/import/DropOrSelectFile.vue

2
packages/nc-gui/components/import/dropOrSelectFileModal.vue → packages/nc-gui/components/import/DropOrSelectFileModal.vue

@ -45,7 +45,7 @@
<script> <script>
import DropOrSelectFile from '~/components/import/dropOrSelectFile' import DropOrSelectFile from '~/components/import/DropOrSelectFile'
export default { export default {
name: 'DropOrSelectFileModal', name: 'DropOrSelectFileModal',

304
packages/nc-gui/components/import/ImportFromAirtable.vue

@ -0,0 +1,304 @@
<template>
<v-dialog v-model="airtableModal" max-width="min(600px, 90%)">
<v-card class="nc-import-card h-100">
<v-toolbar class="elevation-0 align-center" height="68">
<h3 class="mt-2">
{{ $t('title.importFromAirtable') }}
</h3>
<div v-t="['c:airtable-import:turbo-mode']" class="ml-2 mt-3 title pointer" @click="enableTurbo">
🚀
</div>
<v-spacer />
</v-toolbar>
<v-divider />
<div class="h-100" style="width: 100%">
<div>
<v-card v-if="step === 1" class="py-6 elevation-0" height="500">
<div class="d-flex flex-column justify-center align-center pt-2 pb-6">
<span class="subtitle-1 font-weight-medium" @dblclick="$set(syncSource.details,'syncViews',true)">
Credentials
</span>
<a href="https://docs.nocodb.com" class="caption grey--text" target="_blank">Where to find this?</a>
</div>
<v-form v-model="valid">
<div v-if="syncSource" class="px-10 mt-1 mx-auto" style="max-width: 400px">
<v-text-field
v-model="syncSource.details.apiKey"
outlined
dense
label="Api Key"
class="caption"
:type="isPasswordVisible ? 'text':'password'"
:rules="[v=> !!v || 'Api Key is required']"
>
<template #append="">
<v-icon class="mt-1" small @click="isPasswordVisible = !isPasswordVisible">
{{ isPasswordVisible ? 'visibility_off' : 'visibility' }}
</v-icon>
</template>
</v-text-field>
<v-text-field
v-model="syncSourceUrlOrId"
outlined
dense
label="Shared Base ID / URL"
class="caption"
:rules="[(v) => !!v || 'Shared Base ID / URL is required']"
/>
</div>
</v-form> <v-card-actions class="justify-center pb-6">
<v-btn
v-t="['c:sync-airtable:save-and-sync']"
:disabled="!valid"
large
color="primary"
@click="saveAndSync"
>
Import
</v-btn>
</v-card-actions>
</v-card>
<v-card
v-if="step === 2"
class="pb-4 mt-4 elevation-0"
>
<v-card-title class=" justify-center">
<span class="subtitle-1 font-weight-medium">Logs</span>
</v-card-title>
<v-card
ref="log"
dark
class="mt-2 mx-4 pa-4 elevation-0 green--text"
height="500"
style="overflow-y: auto"
>
<div v-for="({msg , status}, i) in progress" :key="i">
<v-icon v-if="status==='FAILED'" color="red" size="15">
mdi-close-circle-outline
</v-icon>
<v-icon v-else color="green" size="15">
mdi-currency-usd
</v-icon>
<span class="caption nc-text">{{ msg }}</span>
</div>
<div
v-if="!progress || !progress.length || progress[progress.length-1].status !== 'COMPLETED' && progress[progress.length-1].status !== 'FAILED'"
class=""
>
<v-icon color="green" size="15">
mdi-loading mdi-spin
</v-icon>
<span class="caption nc-text">Syncing
</span>
<!-- <div class="nc-progress" />-->
</div>
</v-card>
<div
v-if="progress && progress.length && progress[progress.length-1].status === 'COMPLETED'"
class="pa-4 pt-8 text-center"
>
<v-btn large color="primary" @click="airtableModal=false">
Go to dashboard
</v-btn>
</div>
</v-card>
</div>
</div>
</v-card>
</v-dialog>
</template>
<script>
import io from 'socket.io-client'
export default {
name: 'ImportFromAirtable',
props: {
value: Boolean
},
data: () => ({
isPasswordVisible: false,
valid: false,
socket: null,
step: 1,
progress: [],
syncSource: null,
syncSourceUrlOrId: ''
}),
computed: {
airtableModal: {
set(v) {
this.$emit('input', v)
},
get() {
return this.value
}
}
},
watch: {
syncSourceUrlOrId(v) {
if (this.syncSource && this.syncSource.details) {
const m = v && v.match(/(exp|shr).{14}/g)
this.syncSource.details.shareId = m ? m[0] : null
}
}
},
created() {
this.socket = io(new URL(this.$axios.defaults.baseURL, window.location.href.split(/[?#]/)[0]).href, {
extraHeaders: { 'xc-auth': this.$store.state.users.token }
})
this.socket.on('connect_error', () => {
this.socket.disconnect()
this.socket = null
})
const socket = this.socket
socket.on('connect', function(data) {
console.log(socket.id)
console.log('socket connected', data)
})
socket.on('progress', (d) => {
this.progress.push(d)
this.$nextTick(() => {
if (this.$refs.log) {
const el = this.$refs.log.$el
el.scrollTop = el.scrollHeight
}
})
if (d.status === 'COMPLETED') {
this.$store.dispatch('project/_loadTables', {
dbKey: '0.projectJson.envs._noco.db.0',
key: '0.projectJson.envs._noco.db.0.tables',
_nodes: {
dbAlias: 'db',
env: '_noco',
type: 'tableDir'
}
}).then(() => this.$store.dispatch('tabs/loadFirstTableTab'))
}
})
this.loadSyncSrc()
},
beforeDestroy() {
if (this.socket) {
this.socket.disconnect()
}
},
methods: {
async saveAndSync() {
await this.createOrUpdate()
this.sync()
},
sync() {
this.step = 2
this.$axios.post(`/api/v1/db/meta/syncs/${this.syncSource.id}/trigger`, this.payload, {
params: {
id: this.socket.id
}
})
},
async loadSyncSrc() {
const { data: { list: srcs } } = await this.$axios.get(`/api/v1/db/meta/projects/${this.projectId}/syncs`)
if (srcs && srcs[0]) {
srcs[0].details = srcs[0].details || {}
this.syncSource = srcs[0]
this.syncSourceUrlOrId = srcs[0].details.shareId
} else {
this.syncSource = {
type: 'Airtable',
details: {
syncInterval: '15mins',
syncDirection: 'Airtable to NocoDB',
syncRetryCount: 1,
syncViews: false,
apiKey: '',
shareId: ''
}
}
}
},
async createOrUpdate() {
try {
const { id, ...payload } = this.syncSource
if (id) {
await this.$axios.patch(`/api/v1/db/meta/syncs/${id}`, payload)
} else {
this.syncSource = (await this.$axios.post(`/api/v1/db/meta/projects/${this.projectId}/syncs`, payload)).data
}
} catch (e) {
this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000)
}
},
enableTurbo() {
this.$set(this.syncSource.details, 'syncViews', true)
this.$toast.success('🚀🚀 Ludicrous mode activated! Let\'s go! 🚀🚀').goAway(3000)
}
}
}
</script>
<style scoped>
.nc-progress {
margin-left: 12px;
position: relative;
width: 5px;
height: 5px;
border-radius: 5px;
background-color: #9880ff;
color: #9880ff;
animation: dotFlashing 1s infinite linear alternate;
animation-delay: .5s;
}
.nc-progress::before, .nc-progress::after {
content: '';
display: inline-block;
position: absolute;
top: 0;
}
.nc-progress::before {
left: -7.5px;
width: 5px;
height: 5px;
border-radius: 5px;
background-color: #9880ff;
color: #9880ff;
animation: dotFlashing 1s infinite alternate;
animation-delay: 0s;
}
.nc-progress::after {
left: 7.5px;
width: 5px;
height: 5px;
border-radius: 5px;
background-color: var(--v-primary-base);
color: var(--v-primary-base);
animation: dotFlashing 1s infinite alternate;
animation-delay: 1s;
}
@keyframes dotFlashing {
0% {
background-color: var(--v-primary-base);
}
50%,
100% {
background-color: var(--v-backgroundColor-base);
}
}
</style>

66
packages/nc-gui/components/import/excelImport.vue → packages/nc-gui/components/import/QuickImport.vue

@ -43,8 +43,8 @@
{{ $t('msg.info.upload_sub') }} {{ $t('msg.info.upload_sub') }}
</p> </p>
<p class="caption grey--text"> <p v-if="quickImportType == 'excel'" class="caption grey--text">
<!-- Supported: .xls, .xlsx, .xlsm, .ods, .ots--> <!-- Supported: .xls, .xlsx, .xlsm, .ods, .ots -->
{{ $t('msg.info.excelSupport') }} {{ $t('msg.info.excelSupport') }}
</p> </p>
</div> </div>
@ -60,13 +60,13 @@
v-model="url" v-model="url"
hide-details="auto" hide-details="auto"
type="url" type="url"
:label="$t('msg.info.excelURL')" :label="quickImportType == 'excel' ? $t('msg.info.excelURL') : $t('msg.info.csvURL') "
class="caption" class="caption"
outlined outlined
dense dense
:rules="[v => !!v || $t('general.required') ]" :rules="[v => !!v || $t('general.required') ]"
/> />
<v-btn class="ml-3" color="primary" @click="loadUrl"> <v-btn v-t="['c:project:create:excel:load-url']" class="ml-3" color="primary" @click="loadUrl">
<!--Load--> <!--Load-->
{{ $t('general.load') }} {{ $t('general.load') }}
</v-btn> </v-btn>
@ -106,6 +106,7 @@
<v-tooltip bottom> <v-tooltip bottom>
<template #activator="{on}"> <template #activator="{on}">
<input <input
v-if="quickImportType == 'excel'"
ref="file" ref="file"
class="nc-excel-import-input" class="nc-excel-import-input"
type="file" type="file"
@ -113,6 +114,15 @@
accept=".xlsx, .xls, .xlsm, .ods, .ots" accept=".xlsx, .xls, .xlsm, .ods, .ots"
@change="_change($event)" @change="_change($event)"
> >
<input
v-if="quickImportType == 'csv'"
ref="file"
class="nc-excel-import-input"
type="file"
style="display: none"
accept=".csv"
@change="_change($event)"
>
<v-btn <v-btn
v-if="!hideLabel" v-if="!hideLabel"
@ -133,14 +143,19 @@
<v-dialog v-if="templateData" v-model="templateEditorModal" max-width="1000"> <v-dialog v-if="templateData" v-model="templateEditorModal" max-width="1000">
<v-card class="pa-6" min-width="500"> <v-card class="pa-6" min-width="500">
<template-editor :project-template.sync="templateData" excel-import> <template-editor :project-template.sync="templateData" excel-import :quick-import-type="quickImportType">
<template #toolbar="{valid}"> <template #toolbar="{valid}">
<!--Importing-->
<h3 class="mt-2 grey--text"> <h3 class="mt-2 grey--text">
{{ $t('activity.importExcel') }} : {{ filename }} <!--Import Excel-->
<span v-if="quickImportType === 'excel'">
{{ $t('activity.importExcel') }}
</span>
<!--Import CSV-->
<span v-if="quickImportType === 'csv'">
{{ $t('activity.importCSV') }}
</span>
: {{ filename }}
</h3> </h3>
<!-- <span class="grey&#45;&#45;text">Importing 2 sheets</span>-->
<v-spacer /> <v-spacer />
<v-spacer /> <v-spacer />
<create-project-from-template-btn <create-project-from-template-btn
@ -151,10 +166,16 @@
:valid="valid" :valid="valid"
create-gql-text="Import as GQL Project" create-gql-text="Import as GQL Project"
create-rest-text="Import as REST Project" create-rest-text="Import as REST Project"
@success="$emit('success'),templateEditorModal = false" @closeModal="$emit('closeModal'),templateEditorModal = false"
> >
<!--Import Excel--> <!--Import Excel-->
<span v-if="quickImportType === 'excel'">
{{ $t('activity.importExcel') }} {{ $t('activity.importExcel') }}
</span>
<!--Import CSV-->
<span v-if="quickImportType === 'csv'">
{{ $t('activity.importCSV') }}
</span>
</create-project-from-template-btn> </create-project-from-template-btn>
</template> </template>
</template-editor> </template-editor>
@ -166,18 +187,19 @@
<script> <script>
// import XLSX from 'xlsx' // import XLSX from 'xlsx'
import TemplateEditor from '~/components/templates/editor' import TemplateEditor from '~/components/templates/Editor'
import CreateProjectFromTemplateBtn from '~/components/templates/createProjectFromTemplateBtn' import CreateProjectFromTemplateBtn from '~/components/templates/CreateProjectFromTemplateBtn'
import ExcelUrlTemplateAdapter from '~/components/import/templateParsers/ExcelUrlTemplateAdapter' import ExcelUrlTemplateAdapter from '~/components/import/templateParsers/ExcelUrlTemplateAdapter'
import ExcelTemplateAdapter from '~/components/import/templateParsers/ExcelTemplateAdapter' import ExcelTemplateAdapter from '~/components/import/templateParsers/ExcelTemplateAdapter'
export default { export default {
name: 'ExcelImport', name: 'QuickImport',
components: { CreateProjectFromTemplateBtn, TemplateEditor }, components: { CreateProjectFromTemplateBtn, TemplateEditor },
props: { props: {
hideLabel: Boolean, hideLabel: Boolean,
value: Boolean, value: Boolean,
importToProject: Boolean importToProject: Boolean,
quickImportType: String
}, },
data() { data() {
return { return {
@ -266,7 +288,7 @@ export default {
templateGenerator = new ExcelTemplateAdapter(name, val, this.parserConfig) templateGenerator = new ExcelTemplateAdapter(name, val, this.parserConfig)
break break
case 'url': case 'url':
templateGenerator = new ExcelUrlTemplateAdapter(val, this.$store, this.parserConfig) templateGenerator = new ExcelUrlTemplateAdapter(val, this.$store, this.parserConfig, this.$api)
break break
} }
await templateGenerator.init() await templateGenerator.init()
@ -276,7 +298,9 @@ export default {
this.templateEditorModal = true this.templateEditorModal = true
} catch (e) { } catch (e) {
console.log(e) console.log(e)
this.$toast.error(e.message).goAway(3000) this.$toast
.error(await this._extractSdkResponseErrorMsg(e))
.goAway(3000)
} }
}, },
@ -296,8 +320,14 @@ export default {
return return
} }
if ((!/\.xls[xm]?$/.test(file.name)) && (!/\.o[dt]s?$/.test(file.name))) { if (this.quickImportType === 'excel') {
return this.$toast.error('Dropped file is not an accepted file type. The accepted file types are .xlsx,.xls,.xlsm!').goAway(3000) if (!/.*\.(xls|xlsx|xlsm|ods|ots)/.test(file.name)) {
return this.$toast.error('Dropped file is not an accepted file type. The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots!').goAway(3000)
}
} else if (this.quickImportType === 'csv') {
if (!/.*\.(csv)/.test(file.name)) {
return this.$toast.error('Dropped file is not an accepted file type. The accepted file type is .csv!').goAway(3000)
}
} }
this._file(file) this._file(file)
}, },

6
packages/nc-gui/components/import/templateParsers/ExcelTemplateAdapter.js

@ -42,7 +42,7 @@ export default class ExcelTemplateAdapter extends TemplateGenerator {
} }
tableNamePrefixRef[tn] = 0 tableNamePrefixRef[tn] = 0
const table = { tn, refTn: tn, columns: [] } const table = { table_name: tn, ref_table_name: tn, columns: [] }
this.data[tn] = [] this.data[tn] = []
const ws = this.wb.Sheets[sheet] const ws = this.wb.Sheets[sheet]
const range = XLSX.utils.decode_range(ws['!ref']) const range = XLSX.utils.decode_range(ws['!ref'])
@ -79,8 +79,8 @@ export default class ExcelTemplateAdapter extends TemplateGenerator {
columnNamePrefixRef[cn] = 0 columnNamePrefixRef[cn] = 0
const column = { const column = {
cn, column_name: cn,
refCn: cn ref_column_name: cn
} }
table.columns.push(column) table.columns.push(column)

12
packages/nc-gui/components/import/templateParsers/ExcelUrlTemplateAdapter.js

@ -1,20 +1,22 @@
import ExcelTemplateAdapter from '~/components/import/templateParsers/ExcelTemplateAdapter' import ExcelTemplateAdapter from '~/components/import/templateParsers/ExcelTemplateAdapter'
export default class ExcelUrlTemplateAdapter extends ExcelTemplateAdapter { export default class ExcelUrlTemplateAdapter extends ExcelTemplateAdapter {
constructor(url, $store, parserConfig) { constructor(url, $store, parserConfig, $api) {
const name = url.split('/').pop() const name = url.split('/').pop()
super(name, null, parserConfig) super(name, null, parserConfig)
this.url = url this.url = url
this.$api = $api
this.$store = $store this.$store = $store
} }
async init() { async init() {
const res = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'handleAxiosCall', const data = await this.$api.utils.axiosRequestMake({
[{ apiMeta: {
url: this.url, url: this.url,
responseType: 'arraybuffer' responseType: 'arraybuffer'
}]]) }
this.excelData = res.data })
this.excelData = data.data
await super.init() await super.init()
} }
} }

8
packages/nc-gui/components/project/apiClientOld.vue → packages/nc-gui/components/project/ApiClientOld.vue

@ -368,13 +368,13 @@ import Vue from 'vue'
import { VueTreeList, Tree, TreeNode } from 'vue-tree-list' import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'
import { Splitpanes, Pane } from 'splitpanes' import { Splitpanes, Pane } from 'splitpanes'
import params from '../apiClient/params' import params from '../apiClient/Params'
import headers from '../apiClient/headers' import headers from '../apiClient/Headers'
import { MonacoJsonEditor } from '../monaco/index' import { MonacoJsonEditor } from '../monaco/index'
import environment from '../environment' import environment from '../Environment'
import PerfTest from '../apiClient/perfTest' import PerfTest from '../apiClient/PerfTest'
// const {app, dialog, path, fs, shell, FileCollection} = require("electron").remote.require( // const {app, dialog, path, fs, shell, FileCollection} = require("electron").remote.require(
// "./libs" // "./libs"

8
packages/nc-gui/components/project/apiClientSwagger.vue → packages/nc-gui/components/project/ApiClientSwagger.vue

@ -620,13 +620,13 @@ import Vue from 'vue'
import { VueTreeList, Tree, TreeNode } from 'vue-tree-list' import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'
import { Splitpanes, Pane } from 'splitpanes' import { Splitpanes, Pane } from 'splitpanes'
import params from '../apiClient/params' import params from '../apiClient/Params'
import headers from '../apiClient/headers' import headers from '../apiClient/Headers'
import { MonacoJsonEditor } from '../monaco/index' import { MonacoJsonEditor } from '../monaco/index'
import environment from '../environment' import environment from '../Environment'
import PerfTest from '../apiClient/perfTest' import PerfTest from '../apiClient/PerfTest'
// const {app, dialog, path, fs, shell, XcApis} = require("electron").remote.require( // const {app, dialog, path, fs, shell, XcApis} = require("electron").remote.require(
// "./libs" // "./libs"

0
packages/nc-gui/components/project/apis.vue → packages/nc-gui/components/project/Apis.vue

12
packages/nc-gui/components/project/appStore.vue → packages/nc-gui/components/project/AppStore.vue

@ -13,8 +13,8 @@
> >
<v-card <v-card
v-if="installPlugin && pluginInstallOverlay" v-if="installPlugin && pluginInstallOverlay"
:dark="$store.state.windows.darkTheme" :dark="$store.state.settings.darkTheme"
:light="!$store.state.windows.darkTheme" :light="!$store.state.settings.darkTheme"
> >
<app-install <app-install
:id="installPlugin.id" :id="installPlugin.id"
@ -38,8 +38,8 @@
<v-dialog min-width="400px" max-width="700px" min-height="300"> <v-dialog min-width="400px" max-width="700px" min-height="300">
<v-card <v-card
v-if="resetPluginRef" v-if="resetPluginRef"
:dark="$store.state.windows.darkTheme" :dark="$store.state.settings.darkTheme"
:light="!$store.state.windows.darkTheme" :light="!$store.state.settings.darkTheme"
> >
<v-card-text> <v-card-text>
Please confirm to reset {{ resetPluginRef.title }} Please confirm to reset {{ resetPluginRef.title }}
@ -191,8 +191,8 @@
</template> </template>
<script> <script>
import AppInstall from '@/components/project/appStore/appInstall' import AppInstall from '~/components/project/appStore/AppInstall'
import DlgOkNew from '@/components/utils/dlgOkNew' import DlgOkNew from '~/components/utils/DlgOkNew'
export default { export default {
name: 'AppStore', name: 'AppStore',

8
packages/nc-gui/components/project/auditTab.vue → packages/nc-gui/components/project/AuditTab.vue

@ -25,10 +25,10 @@
</template> </template>
<script> <script>
import Db from '@/components/project/auditTab/db' import Db from '~/components/project/auditTab/Db'
import SqlLogAndOutput from '~/components/project/sqlLogAndOutput' import SqlLogAndOutput from '~/components/project/SqlLogAndOutput'
import Audit from '~/components/project/auditTab/audit' import Audit from '~/components/project/auditTab/Audit'
import AuditCE from '~/components/project/auditTab/auditCE' import AuditCE from '~/components/project/auditTab/AuditCE'
export default { export default {
name: 'AuditTab', name: 'AuditTab',

4
packages/nc-gui/components/project/cronJobs.vue → packages/nc-gui/components/project/CronJobs.vue

@ -37,7 +37,7 @@
<h4 <h4
class="text-center" class="text-center"
:class="{ :class="{
'grey--text text--darken-2' : !$store.state.windows.darkTheme 'grey--text text--darken-2' : !$store.state.settings.darkTheme
}" }"
> >
Cron Job List Cron Job List
@ -139,7 +139,7 @@
<h4 <h4
class="text-center text-capitalize mt-2 d-100" class="text-center text-capitalize mt-2 d-100"
:class="{ :class="{
'grey--text text--darken-2' : !$store.state.windows.darkTheme 'grey--text text--darken-2' : !$store.state.settings.darkTheme
}" }"
> >
{{ selectedItem.title }} {{ selectedItem.title }}

4
packages/nc-gui/components/project/function.vue → packages/nc-gui/components/project/Function.vue

@ -17,8 +17,8 @@
<script> <script>
import FunctionQuery from './functionTab/functionQuery' import FunctionQuery from './functionTab/FunctionQuery'
import FunctionAcl from './functionTab/functionAcl' import FunctionAcl from './functionTab/FunctionAcl'
export default { export default {
components: { FunctionAcl, FunctionQuery }, components: { FunctionAcl, FunctionQuery },

0
packages/nc-gui/components/project/gqlHandlerCodeEditor.vue → packages/nc-gui/components/project/GqlHandlerCodeEditor.vue

0
packages/nc-gui/components/project/graphqlClient.vue → packages/nc-gui/components/project/GraphqlClient.vue

0
packages/nc-gui/components/project/grpcClient.vue → packages/nc-gui/components/project/GrpcClient.vue

0
packages/nc-gui/components/project/grpcHandlerCodeEditor.vue → packages/nc-gui/components/project/GrpcHandlerCodeEditor.vue

4
packages/nc-gui/components/project/procedure.vue → packages/nc-gui/components/project/Procedure.vue

@ -18,8 +18,8 @@
<script> <script>
import ProcedureQuery from './procedureTab/procedureQuery' import ProcedureQuery from './procedureTab/ProcedureQuery'
import ProcedureAcl from './procedureTab/procedureAcl' import ProcedureAcl from './procedureTab/ProcedureAcl'
export default { export default {
components: { ProcedureAcl, ProcedureQuery }, components: { ProcedureAcl, ProcedureQuery },

4
packages/nc-gui/components/project/projectSettings.vue → packages/nc-gui/components/project/ProjectSettings.vue

@ -75,8 +75,8 @@
</template> </template>
<script> <script>
import Appearance from '@/components/project/settings/appearance' import Appearance from '~/components/project/settings/Appearance'
import CreateOrEditProject from '@/components/createOrEditProject' import CreateOrEditProject from '~/components/CreateOrEditProject'
export default { export default {
name: 'ProjectSettings', name: 'ProjectSettings',

0
packages/nc-gui/components/project/restHandlerCodeEditor.vue → packages/nc-gui/components/project/RestHandlerCodeEditor.vue

2
packages/nc-gui/components/project/seed.vue → packages/nc-gui/components/project/Seed.vue

@ -88,7 +88,7 @@
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel' import dlgLabelSubmitCancel from '../utils/DlgLabelSubmitCancel'
export default { export default {
components: { components: {

2
packages/nc-gui/components/project/sequence.vue → packages/nc-gui/components/project/Sequence.vue

@ -148,7 +148,7 @@
<script> <script>
import { mapGetters, mapActions } from 'vuex' import { mapGetters, mapActions } from 'vuex'
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel' import dlgLabelSubmitCancel from '../utils/DlgLabelSubmitCancel'
export default { export default {
components: { dlgLabelSubmitCancel }, components: { dlgLabelSubmitCancel },

2
packages/nc-gui/components/project/sqlClient.vue → packages/nc-gui/components/project/SqlClient.vue

@ -324,7 +324,7 @@ import { mapGetters } from 'vuex'
import { VueTreeList, Tree, TreeNode } from 'vue-tree-list' import { VueTreeList, Tree, TreeNode } from 'vue-tree-list'
import { Splitpanes, Pane } from 'splitpanes' import { Splitpanes, Pane } from 'splitpanes'
import sqlRightClickOptions from '../../helpers/sqlRightClickOptions' import sqlRightClickOptions from '../../helpers/sqlRightClickOptions'
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel.vue' import dlgLabelSubmitCancel from '../utils/DlgLabelSubmitCancel.vue'
import Utils from '../../helpers/Utils' import Utils from '../../helpers/Utils'

12
packages/nc-gui/components/project/sqlLogAndOutput.vue → packages/nc-gui/components/project/SqlLogAndOutput.vue

@ -4,14 +4,14 @@
<slot /> <slot />
</pane> </pane>
<pane v-if="$store.state.windows.outputWindow && !hide" :size="50" min-size="10" style="overflow: auto"> <pane v-if="$store.state.settings.outputWindow && !hide" :size="50" min-size="10" style="overflow: auto">
<ProjectOutput /> <ProjectOutput />
</pane> </pane>
<!-- <pane v-if="$store.state.windows.logWindow && !$store.state.windows.outputWindow && !hide" :size="10" min-size="10"--> <!-- <pane v-if="$store.state.settings.logWindow && !$store.state.settings.outputWindow && !hide" :size="10" min-size="10"-->
<!-- style="overflow: auto">--> <!-- style="overflow: auto">-->
<!-- <ProjectLogs/>--> <!-- <ProjectLogs/>-->
<!-- </pane>--> <!-- </pane>-->
<!-- <pane v-if="$store.state.windows.logWindow && $store.state.windows.outputWindow && !hide" :size="10" min-size="10"--> <!-- <pane v-if="$store.state.settings.logWindow && $store.state.settings.outputWindow && !hide" :size="10" min-size="10"-->
<!-- style="overflow: auto">--> <!-- style="overflow: auto">-->
<!-- <ProjectLogs/>--> <!-- <ProjectLogs/>-->
<!-- </pane>--> <!-- </pane>-->
@ -23,7 +23,7 @@
import { Splitpanes, Pane } from 'splitpanes' import { Splitpanes, Pane } from 'splitpanes'
import 'splitpanes/dist/splitpanes.css' import 'splitpanes/dist/splitpanes.css'
// import ProjectLogs from '~/components/projectLogs' // import ProjectLogs from '~/components/projectLogs'
import ProjectOutput from '~/components/projectOutput' import ProjectOutput from '~/components/ProjectOutput'
export default { export default {
name: 'SqlLogAndOutput', name: 'SqlLogAndOutput',
@ -42,13 +42,13 @@ export default {
} }
}, },
created() { created() {
if (!this.$store.state.windows.outputWindow && !this.$store.state.windows.logWindow) { if (!this.$store.state.settings.outputWindow && !this.$store.state.settings.logWindow) {
this.$nextTick(() => { this.$nextTick(() => {
this.mainPanelSize = 100 this.mainPanelSize = 100
}) })
} }
this.$store.watch( this.$store.watch(
state => !state.windows.outputWindow && !state.windows.logWindow, state => !state.settings.outputWindow && !state.settings.logWindow,
(newState) => { (newState) => {
if (newState) { if (newState) {
this.$nextTick(() => { this.$nextTick(() => {

0
packages/nc-gui/components/project/swaggerClient.vue → packages/nc-gui/components/project/SwaggerClient.vue

4
packages/nc-gui/components/project/table.vue → packages/nc-gui/components/project/Table.vue

@ -52,9 +52,9 @@
<script> <script>
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel' import dlgLabelSubmitCancel from '../utils/DlgLabelSubmitCancel'
import { isMetaTable } from '@/helpers/xutils' import { isMetaTable } from '@/helpers/xutils'
import RowsXcDataTable from '@/components/project/spreadsheet/rowsXcDataTable' import RowsXcDataTable from '~/components/project/spreadsheet/RowsXcDataTable'
export default { export default {
components: { components: {

0
packages/nc-gui/components/project/xcInfo.vue → packages/nc-gui/components/project/XcInfo.vue

0
packages/nc-gui/components/project/appStore/appInstall.vue → packages/nc-gui/components/project/appStore/AppInstall.vue

10
packages/nc-gui/components/project/appStore/FormInput.vue

@ -23,11 +23,11 @@
</template> </template>
<script> <script>
import TextField from '@/components/project/appStore/inputs/textField' import TextField from '~/components/project/appStore/inputs/TextField'
import Attachment from '@/components/project/appStore/inputs/attachment' import Attachment from '~/components/project/appStore/inputs/Attachment'
import PasswordField from '@/components/project/appStore/inputs/passwordField' import PasswordField from '~/components/project/appStore/inputs/PasswordField'
import TextAreaCell from '@/components/project/spreadsheet/components/editableCell/textAreaCell' import TextAreaCell from '~/components/project/spreadsheet/components/editableCell/TextAreaCell'
import CheckboxField from '@/components/project/appStore/inputs/checkboxField' import CheckboxField from '~/components/project/appStore/inputs/CheckboxField'
export default { export default {
name: 'FormInput', name: 'FormInput',

0
packages/nc-gui/components/project/appStore/inputs/attachment.vue → packages/nc-gui/components/project/appStore/inputs/Attachment.vue

0
packages/nc-gui/components/project/appStore/inputs/checkboxField.vue → packages/nc-gui/components/project/appStore/inputs/CheckboxField.vue

0
packages/nc-gui/components/project/appStore/inputs/longTextField.vue → packages/nc-gui/components/project/appStore/inputs/LongTextField.vue

0
packages/nc-gui/components/project/appStore/inputs/passwordField.vue → packages/nc-gui/components/project/appStore/inputs/PasswordField.vue

0
packages/nc-gui/components/project/appStore/inputs/textField.vue → packages/nc-gui/components/project/appStore/inputs/TextField.vue

404
packages/nc-gui/components/project/appStoreOld.vue

@ -1,404 +0,0 @@
<template>
<div class="d-flex h-100">
<v-navigation-drawer width="300" class="pa-1">
<v-text-field
v-model="query"
outlined
dense
hide-details
placeholder="Search apps"
append-icon="mdi-magnify"
/>
<v-list dense>
<v-list-item v-for="filter of filters" :key="filter">
<v-checkbox
v-model="selectedTags"
class="pt-0 mt-0"
:value="filter"
hide-details
dense
:label="filter"
/>
</v-list-item>
</v-list>
</v-navigation-drawer>
<v-container class="h-100 app-container">
<v-row>
<v-col v-for="(app,i) in filteredApps" :key="i" cols="6">
<v-card
class="elevation-0 app-item-card"
>
<v-btn x-small outlined class="install-btn caption text-capitalize" @click="installApp">
<v-icon x-small class="mr-1">
mdi-plus
</v-icon>
Install
</v-btn>
<div class="d-flex flex-no-wrap">
<v-avatar
class="ma-3"
size="100"
tile
>
<v-img v-if="app.img" :src="app.img" contain />
<v-img v-else src="https://cdn.vuetifyjs.com/images/cards/foster.jpg" contain />
</v-avatar>
<div class="flex-grow-1">
<v-card-title
class="title "
v-text="app.name"
/>
<v-card-subtitle class="pb-1" v-text="app.description" />
<v-card-actions>
<div class="d-flex justify-space-between d-100 align-center">
<v-rating
full-icon="mdi-star"
readonly
length="5"
size="15"
value="5"
/>
<span v-if="app.price && app.price !== 'Free'" class="subtitles">${{ app.price }} / mo</span>
<span v-else class="subtitles">Free</span>
</div>
</v-card-actions>
<!-- <v-card-actions>-->
<!-- <v-btn-->
<!-- outlined-->
<!-- rounded-->
<!-- small-->
<!-- >-->
<!-- Download-->
<!-- </v-btn>-->
<!-- </v-card-actions>-->
</div>
</div>
</v-card>
</v-col>
</v-row>
</v-container>
</div>
</template>
<script>
export default {
name: 'AppStore',
data: () => ({
query: '',
selectedTags: [],
apps: [
// {
// name: 'Graph',
// description: 'Visualize your records on a bar, line, pie, or scatter chart',
// price: '29',
// tags: []
// }, {
// name: 'Import / Export',
// description: 'Visualize your records on a bar, line, pie, or scatter chart',
// price: '39'
// }, {
// name: 'Chart',
// description: 'Visualize your records on a bar, line, pie, or scatter chart',
// price: '19'
// },
{
name: 'Freshworks',
img: require('~/assets/img/abcd/freshworks.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19',
tags: ['SaaS']
},
{
name: 'RazorPay',
img: require('~/assets/img/abcd/razorpay.svg'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19',
tags: ['SaaS']
},
{
name: 'Google Ads',
img: require('~/assets/img/abcd/adsense.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19',
tags: ['SaaS']
},
{
name: 'Facebook Ads',
img: require('~/assets/img/abcd/fbads.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19',
tags: ['SaaS']
},
{
name: 'Stripe',
img: require('~/assets/img/abcd/320.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19',
tags: ['SaaS']
}, {
name: 'Twilio',
img: require('~/assets/img/abcd/twilio.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19',
tags: ['SaaS']
}, {
name: 'SendGrid',
img: require('~/assets/img/abcd/sendgrid.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19',
tags: ['SaaS']
}, {
name: 'Basecamp',
img: require('~/assets/img/abcd/basecamp.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19',
tags: ['SaaS']
}, {
name: 'Github',
img: require('~/assets/img/abcd/github.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19',
tags: ['SaaS']
}, {
name: 'Shopify',
img: require('~/assets/img/abcd/shopify.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '19'
}, {
name: 'SAP',
img: require('~/assets/img/abcd/sap.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '49',
tags: ['ERP']
}, {
name: 'SalesForce',
img: require('~/assets/img/abcd/salesforce.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99',
tags: ['ERP']
}, {
name: 'NetSuite',
img: require('~/assets/img/abcd/netsuit.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99'
}, {
name: 'Zoho CRM',
img: require('~/assets/img/abcd/zohocrm.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99',
tags: ['ERP']
}, {
name: 'MySQL',
img: require('~/assets/img/abcd/mysql.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'Databases']
}, {
name: 'PostgreSQL',
img: require('~/assets/img/abcd/pg.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'Databases']
}, {
name: 'SQL Server',
img: require('~/assets/img/abcd/mssql.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'Databases']
}, {
name: 'MariaDB',
img: require('~/assets/img/abcd/mariaDB.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'Databases']
}, {
name: 'SQLite',
img: require('~/assets/img/temp/db/sqlite.svg'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'Databases']
}, {
name: 'Oracle DB',
img: require('~/assets/img/temp/db/oracle.png.jpg'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'Databases']
}, {
name: 'CrateDB',
img: require('~/assets/img/abcd/cratedb.jpg'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99',
tags: ['Databases']
}, {
name: 'Cassandra',
img: require('~/assets/img/abcd/cassandra.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99',
tags: ['Databases']
}, {
name: 'CouchDB',
img: require('~/assets/img/abcd/couchdb.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99',
tags: ['Databases']
}, {
name: 'ElasticSearch',
img: require('~/assets/img/abcd/elasticsearch.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99',
tags: ['Databases']
}, {
name: 'Snowflake',
img: require('~/assets/img/abcd/snowflake.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99',
tags: ['Databases']
}, {
name: 'MongoDB',
img: require('~/assets/img/abcd/mongodb.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99'
}, {
name: 'BigQuery',
img: require('~/assets/img/abcd/bigquery.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: '99'
},
{
name: 'REST API',
img: require('~/assets/img/abcd/rest.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'API']
}, {
name: 'GRAPHQL API',
img: require('~/assets/img/abcd/graphql.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'API']
}, {
name: 'gRPC API',
img: require('~/assets/img/abcd/grpc.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'API']
},
{
name: 'Swagger',
img: require('~/assets/img/abcd/swagger.png'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['Free', 'API Specification']
},
{
name: 'Postman',
img: require('~/assets/img/abcd/postman.jpg'),
description: 'Visualize your records on a bar, line, pie, or scatter chart',
price: 'Free',
tags: ['API Specification']
}
]
}),
computed: {
filters() {
return this.apps.reduce((arr, app) => arr.concat(app.tags || []), []).filter((f, i, arr) => i === arr.indexOf(f)).sort()
},
filteredApps() {
return this.apps.filter(app => (!this.query.trim() || app.name.toLowerCase().includes(this.query.trim().toLowerCase())) &&
(!this.selectedTags.length || this.selectedTags.some(t => app.tags && app.tags.includes(t)))
)
}
},
methods: {
installApp() {
this.$toast.info('Coming soon after seed funding.').goAway(5000)
}
}
}
</script>
<style scoped lang="scss">
.title {
color: var(--v-textColor-ligten2) !important;
}
.app-item-card {
transition: .4s background-color;
position: relative;
overflow-x: hidden;
.install-btn {
position: absolute;
opacity: 0;
right: -100%;
top: 10px;
transition: .4s opacity, .4s right;
}
&:hover .install-btn {
right: 10px;
opacity: 1;
}
}
.app-item-card {
transition: .4s background-color, .4s transform;
&:hover {
background: rgba(123, 126, 136, 0.1) !important;
transform: scale(1.01);
}
}
::v-deep {
.v-rating {
margin-left:6px;
.v-icon {
padding-right: 2px;
padding-left: 2px;
}
}
.v-input__control .v-input__slot .v-input--selection-controls__input {
transform: scale(.75);
}
.v-input--selection-controls .v-input__slot > .v-label{
font-size: .8rem;
}
}
.app-container {
height: 100%;
overflow-y: auto;
}
</style>
<!--
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-->

13
packages/nc-gui/components/project/auditTab/audit.vue → packages/nc-gui/components/project/auditTab/Audit.vue

@ -54,7 +54,7 @@
{{ audit.description }} {{ audit.description }}
</td> </td>
<td class="caption"> <td class="caption">
{{ audit.user == null?'Shared base':audit.user }} {{ audit.user == null ? 'Shared base' : audit.user }}
</td> </td>
<!-- <td class="caption">--> <!-- <td class="caption">-->
<!-- {{ audit.ip }}--> <!-- {{ audit.ip }}-->
@ -81,12 +81,7 @@
</template> </template>
<script> <script>
import dayjs from 'dayjs' import { calculateDiff } from '~/helpers'
const relativeTime = require('dayjs/plugin/relativeTime')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
dayjs.extend(relativeTime)
export default { export default {
name: 'Audit', name: 'Audit',
@ -116,9 +111,7 @@ export default {
this.audits = list this.audits = list
this.count = pageInfo.totalRows this.count = pageInfo.totalRows
}, },
calculateDiff(date) { calculateDiff
return dayjs.utc(date).fromNow()
}
} }
} }
</script> </script>

0
packages/nc-gui/components/project/auditTab/auditCE.vue → packages/nc-gui/components/project/auditTab/AuditCE.vue

0
packages/nc-gui/components/project/auditTab/db.vue → packages/nc-gui/components/project/auditTab/Db.vue

0
packages/nc-gui/components/project/dlgs/dlgAddRelation.vue → packages/nc-gui/components/project/dlgs/DlgAddRelation.vue

0
packages/nc-gui/components/project/dlgs/dlgTriggerAddEdit.vue → packages/nc-gui/components/project/dlgs/DlgTriggerAddEdit.vue

0
packages/nc-gui/components/project/functionTab/functionAcl.vue → packages/nc-gui/components/project/functionTab/FunctionAcl.vue

2
packages/nc-gui/components/project/functionTab/functionQuery.vue → packages/nc-gui/components/project/functionTab/FunctionQuery.vue

@ -75,7 +75,7 @@ import { mapGetters, mapActions } from 'vuex'
import { SqlUiFactory } from 'nocodb-sdk' import { SqlUiFactory } from 'nocodb-sdk'
import MonacoEditor from '../../monaco/Monaco' import MonacoEditor from '../../monaco/Monaco'
import dlgLabelSubmitCancel from '../../utils/dlgLabelSubmitCancel' import dlgLabelSubmitCancel from '../../utils/DlgLabelSubmitCancel'
export default { export default {
components: { MonacoEditor, dlgLabelSubmitCancel }, components: { MonacoEditor, dlgLabelSubmitCancel },

0
packages/nc-gui/components/project/procedureTab/procedureAcl.vue → packages/nc-gui/components/project/procedureTab/ProcedureAcl.vue

2
packages/nc-gui/components/project/procedureTab/procedureQuery.vue → packages/nc-gui/components/project/procedureTab/ProcedureQuery.vue

@ -71,7 +71,7 @@
import { mapGetters, mapActions } from 'vuex' import { mapGetters, mapActions } from 'vuex'
import MonacoEditor from '../../monaco/Monaco' import MonacoEditor from '../../monaco/Monaco'
import dlgLabelSubmitCancel from '../../utils/dlgLabelSubmitCancel' import dlgLabelSubmitCancel from '../../utils/DlgLabelSubmitCancel'
export default { export default {
components: { MonacoEditor, dlgLabelSubmitCancel }, components: { MonacoEditor, dlgLabelSubmitCancel },

24
packages/nc-gui/components/project/projectMetadata/disableOrEnableModels.vue → packages/nc-gui/components/project/projectMetadata/DisableOrEnableModels.vue

@ -1,15 +1,15 @@
<template> <template>
<div> <div>
<v-tabs v-model="dbsTab" color="x-active" height="30"> <v-tabs v-model="dbsTab" color="x-active" height="30">
<!-- <v-tab href="#xc-project-meta">--> <!-- <v-tab href="#xc-project-meta">-->
<!-- <v-icon icon x-small class="mr-2">--> <!-- <v-icon icon x-small class="mr-2">-->
<!-- mdi-file-table-box-multiple-outline--> <!-- mdi-file-table-box-multiple-outline-->
<!-- </v-icon>--> <!-- </v-icon>-->
<!-- <span class="caption text-capitalize nc-exp-imp-metadata">--> <!-- <span class="caption text-capitalize nc-exp-imp-metadata">-->
<!-- &lt;!&ndash; Export/Import Metadata &ndash;&gt;--> <!-- &lt;!&ndash; Export/Import Metadata &ndash;&gt;-->
<!-- {{ $t('title.exportImportMeta') }}--> <!-- {{ $t('title.exportImportMeta') }}-->
<!-- </span>--> <!-- </span>-->
<!-- </v-tab>--> <!-- </v-tab>-->
<v-tab-item value="xc-project-meta"> <v-tab-item value="xc-project-meta">
<div class="d-flex justify-center d-100"> <div class="d-flex justify-center d-100">
<xc-meta /> <xc-meta />
@ -57,10 +57,10 @@
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import XcMeta from '../settings/xcMeta' import XcMeta from '../settings/XcMeta'
import { isMetaTable } from '@/helpers/xutils' import { isMetaTable } from '@/helpers/xutils'
import metaDiffSync from '~/components/project/projectMetadata/sync/metaDiffSync' import metaDiffSync from '~/components/project/projectMetadata/sync/MetaDiffSync'
import ToggleTableUiAcl from '@/components/project/projectMetadata/uiAcl/toggleTableUIAcl' import ToggleTableUiAcl from '~/components/project/projectMetadata/uiAcl/ToggleTableUIAcl'
export default { export default {
name: 'DisableOrEnableModels', name: 'DisableOrEnableModels',

0
packages/nc-gui/components/project/projectMetadata/sync/disableOrEnableFunctions.vue → packages/nc-gui/components/project/projectMetadata/sync/DisableOrEnableFunctions.vue

0
packages/nc-gui/components/project/projectMetadata/sync/disableOrEnableProcedures.vue → packages/nc-gui/components/project/projectMetadata/sync/DisableOrEnableProcedures.vue

0
packages/nc-gui/components/project/projectMetadata/sync/disableOrEnableRelations.vue → packages/nc-gui/components/project/projectMetadata/sync/DisableOrEnableRelations.vue

0
packages/nc-gui/components/project/projectMetadata/sync/disableOrEnableViews.vue → packages/nc-gui/components/project/projectMetadata/sync/DisableOrEnableViews.vue

0
packages/nc-gui/components/project/projectMetadata/sync/metaDiffSync.vue → packages/nc-gui/components/project/projectMetadata/sync/MetaDiffSync.vue

0
packages/nc-gui/components/project/projectMetadata/uiAcl/toggleFunctionUIAcl.vue → packages/nc-gui/components/project/projectMetadata/uiAcl/ToggleFunctionUIAcl.vue

0
packages/nc-gui/components/project/projectMetadata/uiAcl/toggleProcedureUIAcl.vue → packages/nc-gui/components/project/projectMetadata/uiAcl/ToggleProcedureUIAcl.vue

0
packages/nc-gui/components/project/projectMetadata/uiAcl/toggleRelationsUIAcl.vue → packages/nc-gui/components/project/projectMetadata/uiAcl/ToggleRelationsUIAcl.vue

62
packages/nc-gui/components/project/projectMetadata/uiAcl/toggleTableUIAcl.vue → packages/nc-gui/components/project/projectMetadata/uiAcl/ToggleTableUIAcl.vue

@ -14,7 +14,9 @@
outlined outlined
> >
<template #prepend-inner> <template #prepend-inner>
<v-icon small> search </v-icon> <v-icon small>
search
</v-icon>
</template> </template>
</v-text-field> </v-text-field>
<v-spacer /> <v-spacer />
@ -127,16 +129,12 @@
</div> </div>
</template> </template>
<span v-if="table.disabled[role]" <span v-if="table.disabled[role]">Click to make '{{ table.table_name }}' visible for
>Click to make '{{ table.table_name }}' visible for Role:{{ role }} in UI dashboard</span>
Role:{{ role }} in UI dashboard</span <span v-else>Click to hide '{{ table.table_name }}' for Role:{{
>
<span v-else
>Click to hide '{{ table.table_name }}' for Role:{{
role role
}} }}
in UI dashboard</span in UI dashboard</span>
>
</v-tooltip> </v-tooltip>
</td> </td>
</tr> </tr>
@ -151,32 +149,32 @@
</template> </template>
<script> <script>
import { mapGetters } from "vuex"; import { mapGetters } from 'vuex'
import viewIcons from "~/helpers/viewIcons"; import viewIcons from '~/helpers/viewIcons'
export default { export default {
name: "ToggleTableUiAcl", name: 'ToggleTableUiAcl',
components: {}, components: {},
props: ["nodes", "db"], props: ['nodes', 'db'],
data: () => ({ data: () => ({
viewIcons, viewIcons,
models: null, models: null,
updating: false, updating: false,
dbsTab: 0, dbsTab: 0,
filter: "", filter: '',
tables: null, tables: null
}), }),
async mounted() { async mounted() {
await this.loadTableList(); await this.loadTableList()
}, },
methods: { methods: {
async loadTableList() { async loadTableList() {
this.tables = await this.$api.project.modelVisibilityList( this.tables = await this.$api.project.modelVisibilityList(
this.db.project_id, this.db.project_id,
{ {
includeM2M: this.$store.state.windows.includeM2M || "", includeM2M: this.$store.state.settings.includeM2M || ''
} }
); )
// this.tables = (await this.$store.dispatch('sqlMgr/ActSqlOp', [{ // this.tables = (await this.$store.dispatch('sqlMgr/ActSqlOp', [{
// dbAlias: this.db.meta.dbAlias, // dbAlias: this.db.meta.dbAlias,
// env: this.$store.getters['project/GtrEnv'] // env: this.$store.getters['project/GtrEnv']
@ -188,32 +186,32 @@ export default {
try { try {
await this.$api.project.modelVisibilitySet( await this.$api.project.modelVisibilitySet(
this.db.project_id, this.db.project_id,
this.tables.filter((t) => t.edited) this.tables.filter(t => t.edited)
); )
this.$toast this.$toast
.success("Updated UI ACL for tables successfully") .success('Updated UI ACL for tables successfully')
.goAway(3000); .goAway(3000)
} catch (e) { } catch (e) {
this.$toast.error(e.message).goAway(3000); this.$toast.error(e.message).goAway(3000)
} }
this.$e("a:proj-meta:ui-acl"); this.$e('a:proj-meta:ui-acl')
}, }
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
dbAliasList: "project/GtrDbAliasList", dbAliasList: 'project/GtrDbAliasList'
}), }),
edited() { edited() {
return ( return (
this.tables && this.tables.length && this.tables.some((t) => t.edited) this.tables && this.tables.length && this.tables.some(t => t.edited)
); )
}, },
roles() { roles() {
return ["editor", "commenter", "viewer"]; // this.tables && this.tables.length ? Object.keys(this.tables[0].disabled) : [] return ['editor', 'commenter', 'viewer'] // this.tables && this.tables.length ? Object.keys(this.tables[0].disabled) : []
}, }
}, }
}; }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save