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. 48
      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. 64
      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. 11
      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. 6
      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": [
"code"
]
},
{
"login": "youyiio",
"name": "youyiio",
"avatar_url": "https://avatars.githubusercontent.com/u/49471274?v=4",
"profile": "https://www.youyi.io",
"contributions": [
"code"
]
}
],
"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="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>
<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>
<!-- 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>
<script>
import UserManagement from '@/components/auth/userManagement'
import Roles from '@/components/auth/roles'
import ApiTokens from '@/components/auth/apiTokens'
import UserManagement from '~/components/auth/UserManagement'
import Roles from '~/components/auth/Roles'
import ApiTokens from '~/components/auth/ApiTokens'
export default {
name: 'AuthTab',

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

@ -28,7 +28,7 @@
<!-- Close -->
{{ $t('general.close') }}
</v-btn>
<v-btn color="primary" small :disabled="progressbar" @click="changeEnv">
<v-btn color="primary" small :disabled="progressbar" @click="ChangeEnv">
Change
</v-btn>
</v-card-actions>
@ -93,7 +93,7 @@ export default {
await this.$store.dispatch('users/ActSignOut')
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')
}
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 XBtn from './global/xBtn'
import dlgOk from './utils/dlgOk.vue'
import textDlgSubmitCancel from './utils/dlgTextSubmitCancel'
import XBtn from './global/XBtn'
import dlgOk from './utils/DlgOk.vue'
import textDlgSubmitCancel from './utils/DlgTextSubmitCancel'
import MonacoJsonObjectEditor from '@/components/monaco/MonacoJsonObjectEditor'
import ApiOverlay from '@/components/apiOverlay'
import ApiOverlay from '~/components/ApiOverlay'
import colors from '@/mixins/colors'
import DlgOkNew from '@/components/utils/dlgOkNew'
import DlgOkNew from '~/components/utils/DlgOkNew'
import readFile from '@/helpers/fileReader'
const {
@ -1428,8 +1428,8 @@ export default {
break
}
xcConfig.type = this.$store.state.project.projectInfo
? this.$store.state.project.projectInfo.type
xcConfig.type = this.$store.state.project.appInfo
? this.$store.state.project.appInfo.type
: 'docker'
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: {
feedbackFormHidden: {
get() {
return this.$store.state.windows.feedbackFormHidden
return this.$store.state.settings.feedbackFormHidden
},
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>
/* eslint-disable */
import AclTsFileDbChild from '@/components/project/tableTabs/aclTsFileDbChild'
import AclTsFileDbChild from '~/components/project/tableTabs/AclTsFileDbChild'
export default {
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"
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
<v-icon small> mdi-menu-down </v-icon>
<v-icon small>
mdi-menu-down
</v-icon>
</v-btn>
</template>
<v-list dense>
@ -33,8 +37,7 @@
<span
class="caption text-capitalize"
:class="{ 'x-active--text': role.title === previewAs }"
>{{ role.title }}</span
>
>{{ role.title }}</span>
</v-list-item-title>
</v-list-item>
</template>
@ -42,7 +45,9 @@
<template v-if="previewAs">
<!-- <v-divider></v-divider>-->
<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 -->
<span class="caption nc-preview-reset">{{
$t("activity.resetReview")
@ -97,8 +102,7 @@
</v-radio-group>
<v-divider vertical class="mr-2" />
<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>
</v-menu>
@ -107,69 +111,69 @@
<script>
export default {
name: "PreviewAs",
name: 'PreviewAs',
data: () => ({
roleIcon: {
owner: "mdi-account-star",
creator: "mdi-account-hard-hat",
editor: "mdi-account-edit",
viewer: "mdi-eye-outline",
commenter: "mdi-comment-account-outline",
owner: 'mdi-account-star',
creator: 'mdi-account-hard-hat',
editor: 'mdi-account-edit',
viewer: 'mdi-eye-outline',
commenter: 'mdi-comment-account-outline'
},
rolesList: [
{ title: "editor" },
{ title: "commenter" },
{ title: "viewer" },
{ title: 'editor' },
{ title: 'commenter' },
{ title: 'viewer' }
],
position: {
x: 9999,
y: 9999,
},
y: 9999
}
}),
computed: {
previewAs: {
get() {
return this.$store.state.users.previewAs;
return this.$store.state.users.previewAs
},
set(previewAs) {
this.$store.commit("users/MutPreviewAs", previewAs);
},
},
this.$store.commit('users/MutPreviewAs', previewAs)
}
}
},
mounted() {
this.position = {
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() {
window.removeEventListener("mousemove", this.divMove, true);
window.removeEventListener("mouseup", this.mouseUp, false);
window.removeEventListener('mousemove', this.divMove, true)
window.removeEventListener('mouseup', this.mouseUp, false)
},
methods: {
setPreviewUser(previewAs) {
this.$e("a:navdraw:preview", { role: previewAs });
this.$e('a:navdraw:preview', { role: previewAs })
if (!process.env.EE) {
this.$toast.info("Available in Enterprise edition").goAway(3000);
this.$toast.info('Available in Enterprise edition').goAway(3000)
} else {
this.previewAs = previewAs;
window.location.reload();
this.previewAs = previewAs
window.location.reload()
}
},
mouseUp() {
window.removeEventListener("mousemove", this.divMove, true);
window.removeEventListener('mousemove', this.divMove, true)
},
mouseDown(e) {
window.addEventListener("mousemove", this.divMove, true);
window.addEventListener('mousemove', this.divMove, true)
},
divMove(e) {
this.position = { y: e.clientY - 10, x: e.clientX - 18 };
},
},
};
this.position = { y: e.clientY - 10, x: e.clientX - 18 }
}
}
}
</script>
<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"
prev-icon="mdi-arrow-left-bold-box-outline"
show-arrows
:class="{ 'dark-them': $store.state.windows.darkTheme }"
:class="{ 'dark-them': $store.state.settings.darkTheme }"
>
<v-tabs-slider color="" />
@ -42,14 +42,12 @@
max-width: 140px;
text-overflow: ellipsis;
"
>{{ tab.name }}</span
>
>{{ tab.name }}</span>
<v-icon icon :small="true" @click="removeTab(index)">
mdi-close
</v-icon>
</v-tab>
<!-- <v-tabs-items v-model="activeTab">-->
<v-tabs-items :value="activeTab">
<v-tab-item
v-for="(tab, index) in tabs"
@ -65,7 +63,6 @@
:reverse-transition="false"
>
<div v-if="tab._nodes.type === 'table'" style="height: 100%">
<!-- <sqlLogAndOutput :hide="hideLogWindows">-->
<TableView
:ref="'tabs' + index"
:is-active="
@ -80,11 +77,8 @@
:hide-log-windows.sync="hideLogWindows"
:nodes="tab._nodes"
/>
<!-- </sqlLogAndOutput>-->
</div>
<div v-else-if="tab._nodes.type === 'view'" style="height: 100%">
<!-- <sqlLogAndOutput>-->
<!-- <ViewTab :ref="'tabs'+index" :nodes="tab._nodes" />-->
<TableView
:ref="'tabs' + index"
:is-active="
@ -247,22 +241,118 @@
<h1>{{ tab._nodes }}</h1>
</div>
</v-tab-item>
<!-- </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
v-if="_isUIAllowed('addTable')"
:tooltip="$t('tooltip.addTable')"
icon-class="add-btn"
:color="['white', 'grey lighten-2']"
@click="dialogCreateTableShowMethod"
>
mdi-plus-box
</x-icon>
<span class="flex-grow-1 caption font-weight-bold text-capitalize mx-2">
<!-- TODO: i18n -->
Add / Import
</span>
<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
v-if="dialogCreateTableShow"
v-model="dialogCreateTableShow"
@ -271,42 +361,53 @@
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>
</template>
<script>
import { mapGetters, mapMutations } from "vuex";
import treeViewIcons from "../helpers/treeViewIcons";
import TableView from "./project/table";
import FunctionTab from "./project/function";
import ProcedureTab from "./project/procedure";
import SequenceTab from "./project/sequence";
import SeedTab from "./project/seed";
import SqlClientTab from "./project/sqlClient";
import ApisTab from "./project/apis";
import ApiClientTab from "./project/apiClientOld";
import sqlLogAndOutput from "./project/sqlLogAndOutput";
import graphqlClient from "./project/graphqlClient";
import xTerm from "./xTerm";
import ApiClientSwaggerTab from "./project/apiClientSwagger";
import XcMeta from "./project/settings/xcMeta";
import XcInfo from "./project/xcInfo";
import SwaggerClient from "@/components/project/swaggerClient";
import DlgTableCreate from "@/components/utils/dlgTableCreate";
import AppStore from "@/components/project/appStore";
import AuthTab from "@/components/authTab";
import CronJobs from "@/components/project/cronJobs";
import DisableOrEnableModels from "@/components/project/projectMetadata/disableOrEnableModels";
import ProjectSettings from "@/components/project/projectSettings";
import GrpcClient from "@/components/project/grpcClient";
import GlobalAcl from "@/components/globalAcl";
import AuditTab from "~/components/project/auditTab";
import { mapGetters, mapMutations } from 'vuex'
import treeViewIcons from '../helpers/treeViewIcons'
import TableView from './project/Table'
import FunctionTab from './project/Function'
import ProcedureTab from './project/Procedure'
import SequenceTab from './project/Sequence'
import SeedTab from './project/Seed'
import SqlClientTab from './project/SqlClient'
import ApisTab from './project/Apis'
import ApiClientTab from './project/ApiClientOld'
import sqlLogAndOutput from './project/SqlLogAndOutput'
import graphqlClient from './project/GraphqlClient'
import xTerm from './XTerm'
import ApiClientSwaggerTab from './project/ApiClientSwagger'
import XcMeta from './project/settings/XcMeta'
import XcInfo from './project/XcInfo'
import SwaggerClient from '~/components/project/SwaggerClient'
import DlgTableCreate from '~/components/utils/DlgTableCreate'
import AppStore from '~/components/project/AppStore'
import AuthTab from '~/components/AuthTab'
import CronJobs from '~/components/project/CronJobs'
import DisableOrEnableModels from '~/components/project/projectMetadata/DisableOrEnableModels'
import ProjectSettings from '~/components/project/ProjectSettings'
import GrpcClient from '~/components/project/GrpcClient'
import GlobalAcl from '~/components/GlobalAcl'
import AuditTab from '~/components/project/AuditTab'
import QuickImport from '~/components/import/QuickImport'
import ImportFromAirtable from '~/components/import/ImportFromAirtable'
export default {
components: {
ImportFromAirtable,
SwaggerClient,
// Screensaver,
DlgTableCreate,
@ -334,147 +435,163 @@ export default {
sqlLogAndOutput,
xTerm,
graphqlClient,
QuickImport
},
data() {
return {
dragOver: false,
dialogCreateTableShow: false,
test: "",
test: '',
treeViewIcons,
hideLogWindows: false,
showScreensaver: false,
};
quickImportModal: false,
quickImportType: '',
airtableImportModal: false
}
},
methods: {
dialogCreateTableShowMethod() {
this.dialogCreateTableShow = true;
this.$e("c:table:create:navbar");
this.dialogCreateTableShow = true
this.$e('c:table:create:navbar')
},
checkInactiveState() {
let position = 0;
let idleTime = 0;
let position = 0
let idleTime = 0
// 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.
document.addEventListener("mousemove", (e) => {
self.showScreensaver = false;
idleTime = 0;
clearInterval(idleInterval);
idleInterval = setInterval(timerIncrement, 1000);
});
document.addEventListener("keypress", (e) => {
self.showScreensaver = false;
idleTime = 0;
clearInterval(idleInterval);
idleInterval = setInterval(timerIncrement, 1000);
});
document.addEventListener('mousemove', (e) => {
self.showScreensaver = false
idleTime = 0
clearInterval(idleInterval)
idleInterval = setInterval(timerIncrement, 1000)
})
document.addEventListener('keypress', (e) => {
self.showScreensaver = false
idleTime = 0
clearInterval(idleInterval)
idleInterval = setInterval(timerIncrement, 1000)
})
function timerIncrement() {
idleTime = idleTime + 1;
idleTime = idleTime + 1
if (idleTime > 120) {
const title = document.title;
const title = document.title
function scrolltitle() {
document.title = title + Array(position).fill(" .").join("");
position = ++position % 3;
document.title = title + Array(position).fill(' .').join('')
position = ++position % 3
if (self.showScreensaver) {
window.setTimeout(scrolltitle, 400);
window.setTimeout(scrolltitle, 400)
} else {
document.title = title;
document.title = title
}
}
self.showScreensaver = self.$store.state.windows.screensaver;
scrolltitle();
clearInterval(idleInterval);
self.showScreensaver = self.$store.state.settings.screensaver
scrolltitle()
clearInterval(idleInterval)
}
}
},
async handleKeyDown(event) {
const activeTabEleKey = `tabs${this.activeTab}`;
let isHandled = false;
const activeTabEleKey = `tabs${this.activeTab}`
let isHandled = false
if (
this.$refs[activeTabEleKey] &&
this.$refs[activeTabEleKey][0] &&
this.$refs[activeTabEleKey][0].handleKeyDown
) {
isHandled = await this.$refs[activeTabEleKey][0].handleKeyDown(event);
isHandled = await this.$refs[activeTabEleKey][0].handleKeyDown(event)
}
if (!isHandled) {
switch (
[this._isMac ? event.metaKey : event.ctrlKey, event.key].join("_")
[this._isMac ? event.metaKey : event.ctrlKey, event.key].join('_')
) {
case "true_w":
this.removeTab(this.activeTab);
event.preventDefault();
event.stopPropagation();
break;
case 'true_w':
this.removeTab(this.activeTab)
event.preventDefault()
event.stopPropagation()
break
}
}
},
...mapMutations({
setActiveTab: "tabs/active",
removeTab: "tabs/remove",
updateActiveTabx: "tabs/activeTabCtx",
setActiveTab: 'tabs/active',
removeTab: 'tabs/remove',
updateActiveTabx: 'tabs/activeTabCtx'
}),
tabActivated(tab) {},
onImportFromExcelOrCSV(quickImportType) {
this.quickImportModal = true
this.quickImportType = quickImportType
},
onAirtableImport() {
this.airtableImportModal = true
}
},
computed: {
...mapGetters({ tabs: "tabs/list", activeTabCtx: "tabs/activeTabCtx" }),
...mapGetters({ tabs: 'tabs/list', activeTabCtx: 'tabs/activeTabCtx' }),
pid() {
return this.$route.params.project_id;
return this.$route.params.project_id
},
activeTab: {
set(tab) {
if (!tab) {
return this.$router.push({
query: {},
});
query: {}
})
}
const [type, dbalias, name] = tab.split("||");
const [type, dbalias, name] = tab.split('||')
this.$router.push({
query: {
...this.$route.query,
type,
dbalias,
name,
},
});
name
}
})
},
get() {
return [
this.$route.query.type,
this.$route.query.dbalias,
this.$route.query.name,
].join("||");
},
},
this.$route.query.name
].join('||')
}
}
},
beforeCreated() {},
watch: {},
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
*/
},
mounted() {},
mounted() {
if (this.$route && this.$route.query && this.$route.query.excelUrl) {
this.quickImportModal = true
}
},
beforeDestroy() {},
destroyed() {
document.removeEventListener("keydown", this.handleKeyDown);
document.removeEventListener('keydown', this.handleKeyDown)
},
directives: {},
validate({ params }) {
return true;
return true
},
head() {
return {};
return {}
},
props: {},
};
props: {}
}
</script>
<style scoped>
@ -544,6 +661,14 @@ export default {
font-weight: bold;
}
.addOrImport {
min-width: 200px;
}
.addOrImport .v-list-item {
min-height: 35px;
}
/deep/ .add-btn {
margin-left: 5px;
}
@ -562,6 +687,7 @@ export default {
*
* @author Naveen MR <oof1lab@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
*

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

@ -678,8 +678,6 @@
</v-list>
</div>
<v-divider />
<div
v-t="['e:api-docs']"
@ -692,7 +690,6 @@
{{ $t('title.apiDocs') }}
</div>
<template v-if="_isUIAllowed('settings')">
<div class="pl-5 pr-3 d-flex align-center pb-2">
<settings-modal>
@ -712,7 +709,6 @@
</div>
</template>
<!-- <v-divider/>-->
<!-- <extras class="pl-1"/>-->
</div>
@ -769,12 +765,12 @@
:heading="selectedNodeForDelete.heading"
type="error"
/>
<excel-import
ref="excelImport"
v-model="excelImportDialog"
<quick-import
ref="quickImport"
v-model="quickImportDialog"
hide-label
import-to-project
@success="onExcelImport"
@success="onQuickImport"
/>
</div>
</template>
@ -788,20 +784,20 @@ import rightClickOptions from "../helpers/rightClickOptions";
import rightClickOptionsSub from "../helpers/rightClickOptionsSub";
import icons from "../helpers/treeViewIcons";
import textDlgSubmitCancel from "./utils/dlgTextSubmitCancel";
import dlgLabelSubmitCancel from "./utils/dlgLabelSubmitCancel";
import textDlgSubmitCancel from "./utils/DlgTextSubmitCancel";
import dlgLabelSubmitCancel from "./utils/DlgLabelSubmitCancel";
import {copyTextToClipboard} from "../helpers/xutils";
import DlgTableCreate from "@/components/utils/dlgTableCreate";
import DlgViewCreate from "@/components/utils/dlgViewCreate";
import SponsorMini from "@/components/sponsorMini";
import DlgTableCreate from "~/components/utils/DlgTableCreate";
import DlgViewCreate from "~/components/utils/DlgViewCreate";
import SponsorMini from "~/components/SponsorMini";
import {validateTableName} from "~/helpers";
import ExcelImport from "~/components/import/excelImport";
import QuickImport from "~/components/import/QuickImport";
import draggable from "vuedraggable";
import GithubStarBtn from "~/components/githubStarBtn";
import SettingsModal from "~/components/settings/settingsModal";
import Language from "~/components/utils/language";
import Extras from "~/components/project/spreadsheet/components/extras";
import GithubStarBtn from "~/components/GithubStarBtn";
import SettingsModal from "~/components/settings/SettingsModal";
import Language from "~/components/utils/Language";
import Extras from "~/components/project/spreadsheet/components/Extras";
export default {
components: {
@ -810,7 +806,7 @@ export default {
SettingsModal,
GithubStarBtn,
draggable,
ExcelImport,
QuickImport,
SponsorMini,
DlgViewCreate,
DlgTableCreate,
@ -864,7 +860,7 @@ export default {
open: [],
search: null,
menuVisible: false,
excelImportDialog: false,
quickImportDialog: false,
x: 0,
y: 0,
menuItem: null,
@ -913,7 +909,7 @@ export default {
}),
computed: {
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: {
get() {
@ -1067,8 +1063,8 @@ export default {
},
changeTheme() {
this.$store.dispatch(
"windows/ActToggleDarkMode",
!this.$store.state.windows.darkTheme
"settings/ActToggleDarkMode",
!this.$store.state.settings.darkTheme
);
},
openLink(link) {
@ -1162,7 +1158,7 @@ export default {
this.miniExpanded = false;
}
},
onExcelImport() {
onQuickImport() {
if (!this.menuItem || this.menuItem.type !== "tableDir") {
this.menuItem = this.listViewArr.find((n) => n.type === "tableDir");
}
@ -1257,7 +1253,7 @@ export default {
if (item._nodes.type === "table") {
let tableIndex = +item._nodes.key.split(".").pop();
if (
!(await this.$store.dispatch("windows/ActCheckMaxTable", {
!(await this.$store.dispatch("settings/ActCheckMaxTable", {
tableIndex,
}))
) {
@ -1437,7 +1433,7 @@ export default {
await this.loadViews(this.menuItem);
this.$toast.success("Views refreshed").goAway(1000);
} else if (action === "IMPORT_EXCEL") {
this.excelImportDialog = true;
this.quickImportDialog = true;
} else if (action === "ENV_DB_FUNCTIONS_REFRESH") {
await this.loadFunctions(this.menuItem);
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
</td>
<td>
<span @contextmenu="rightClick">{{ $store.state.windows.version }}</span>
<span @contextmenu="rightClick">{{ $store.state.settings.version }}</span>
</td>
</tr>
<tr @dblclick="enableAppRefresh = true">
@ -226,7 +226,7 @@
<script>
import themes from '../helpers/themes'
import dlgLabelSubmitCancel from './utils/dlgLabelSubmitCancel'
import dlgLabelSubmitCancel from './utils/DlgLabelSubmitCancel'
export default {
components: { dlgLabelSubmitCancel },
@ -275,42 +275,42 @@ export default {
computed: {
checkForUpdate: {
get() {
return this.$store.state.windows.checkForUpdate
return this.$store.state.settings.checkForUpdate
},
set(value) {
this.$store.commit('windows/MutCheckForUpdate', value)
this.$store.commit('settings/MutCheckForUpdate', value)
}
},
autoUpdate: {
get() {
return this.$store.state.windows.downloadAndUpdateRelease
return this.$store.state.settings.downloadAndUpdateRelease
},
set(value) {
this.$store.commit('windows/MutDownloadAndUpdateRelease', value)
this.$store.commit('settings/MutDownloadAndUpdateRelease', value)
}
},
isGaEnabled: {
get() {
return this.$store.state.windows.isGaEnabled
return this.$store.state.settings.isGaEnabled
},
set(value) {
this.$store.commit('windows/MutToggleGaEnabled', value)
this.$store.commit('settings/MutToggleGaEnabled', value)
}
},
isErrorReportingEnabled: {
get() {
return this.$store.state.windows.isErrorReportingEnabled
return this.$store.state.settings.isErrorReportingEnabled
},
set(value) {
this.$store.commit('windows/MutToggleErrorReportingEnabled', value)
this.$store.commit('settings/MutToggleErrorReportingEnabled', value)
}
},
isTelemetryEnabled: {
get() {
return this.$store.state.windows.isErrorReportingEnabled
return this.$store.state.settings.isErrorReportingEnabled
},
set(value) {
this.$store.commit('windows/MutToggleTelemetryEnabled', value)
this.$store.commit('settings/MutToggleTelemetryEnabled', value)
}
},
dialogShow: {
@ -323,24 +323,24 @@ export default {
},
language: {
get() {
return this.$store.state.windows.language
return this.$store.state.settings.language
},
set(val) {
this.$store.commit('windows/MutSetLanguage', val)
this.$store.commit('settings/MutSetLanguage', val)
}
}
},
watch: {},
created() {
this.customTheme = { ...this.customTheme, ...this.$store.state.windows.customTheme }
this.item = this.$store.state.windows.themeName
this.customTheme = { ...this.customTheme, ...this.$store.state.settings.customTheme }
this.item = this.$store.state.settings.themeName
this.$store.watch(
state => state.windows.customTheme,
state => state.settings.customTheme,
(theme) => {
this.customTheme = { ...this.customTheme, ...theme }
})
this.$store.watch(state => state.windows.themeName,
this.$store.watch(state => state.settings.themeName,
(theme) => {
this.$nextTick(() => {
if (this.item !== theme) { this.item = theme }
@ -396,11 +396,11 @@ export default {
},
async changeTheme(t, theme = 'Custom') {
this.item = theme
if (theme === 'Custom') { await this.$store.dispatch('windows/ActSetTheme', { theme: { ...t }, custom: true }) }
await this.$store.dispatch('windows/ActSetTheme', { theme: { ...t }, themeName: theme })
if (theme === 'Custom') { await this.$store.dispatch('settings/ActSetTheme', { theme: { ...t }, custom: true }) }
await this.$store.dispatch('settings/ActSetTheme', { theme: { ...t }, themeName: theme })
},
toggleDarkTheme() {
this.$store.commit('windows/MutToggleDarkMode')
this.$store.commit('settings/MutToggleDarkMode')
}
},
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 { WebLinksAddon } from 'xterm-addon-web-links'
import { mapGetters } from 'vuex'
import XIcon from './global/xIcon'
import XIcon from './global/XIcon'
export default {
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>
<script>
import DlgLabelSubmitCancel from '@/components/utils/dlgLabelSubmitCancel'
import DlgLabelSubmitCancel from '~/components/utils/DlgLabelSubmitCancel'
import colors from '@/mixins/colors'
export default {

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

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

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

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

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

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

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

@ -1,20 +1,22 @@
import ExcelTemplateAdapter from '~/components/import/templateParsers/ExcelTemplateAdapter'
export default class ExcelUrlTemplateAdapter extends ExcelTemplateAdapter {
constructor(url, $store, parserConfig) {
constructor(url, $store, parserConfig, $api) {
const name = url.split('/').pop()
super(name, null, parserConfig)
this.url = url
this.$api = $api
this.$store = $store
}
async init() {
const res = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'handleAxiosCall',
[{
const data = await this.$api.utils.axiosRequestMake({
apiMeta: {
url: this.url,
responseType: 'arraybuffer'
}]])
this.excelData = res.data
}
})
this.excelData = data.data
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 { Splitpanes, Pane } from 'splitpanes'
import params from '../apiClient/params'
import headers from '../apiClient/headers'
import params from '../apiClient/Params'
import headers from '../apiClient/Headers'
import { MonacoJsonEditor } from '../monaco/index'
import environment from '../environment'
import PerfTest from '../apiClient/perfTest'
import environment from '../Environment'
import PerfTest from '../apiClient/PerfTest'
// const {app, dialog, path, fs, shell, FileCollection} = require("electron").remote.require(
// "./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 { Splitpanes, Pane } from 'splitpanes'
import params from '../apiClient/params'
import headers from '../apiClient/headers'
import params from '../apiClient/Params'
import headers from '../apiClient/Headers'
import { MonacoJsonEditor } from '../monaco/index'
import environment from '../environment'
import PerfTest from '../apiClient/perfTest'
import environment from '../Environment'
import PerfTest from '../apiClient/PerfTest'
// const {app, dialog, path, fs, shell, XcApis} = require("electron").remote.require(
// "./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-if="installPlugin && pluginInstallOverlay"
:dark="$store.state.windows.darkTheme"
:light="!$store.state.windows.darkTheme"
:dark="$store.state.settings.darkTheme"
:light="!$store.state.settings.darkTheme"
>
<app-install
:id="installPlugin.id"
@ -38,8 +38,8 @@
<v-dialog min-width="400px" max-width="700px" min-height="300">
<v-card
v-if="resetPluginRef"
:dark="$store.state.windows.darkTheme"
:light="!$store.state.windows.darkTheme"
:dark="$store.state.settings.darkTheme"
:light="!$store.state.settings.darkTheme"
>
<v-card-text>
Please confirm to reset {{ resetPluginRef.title }}
@ -191,8 +191,8 @@
</template>
<script>
import AppInstall from '@/components/project/appStore/appInstall'
import DlgOkNew from '@/components/utils/dlgOkNew'
import AppInstall from '~/components/project/appStore/AppInstall'
import DlgOkNew from '~/components/utils/DlgOkNew'
export default {
name: 'AppStore',

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

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

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

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

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

@ -17,8 +17,8 @@
<script>
import FunctionQuery from './functionTab/functionQuery'
import FunctionAcl from './functionTab/functionAcl'
import FunctionQuery from './functionTab/FunctionQuery'
import FunctionAcl from './functionTab/FunctionAcl'
export default {
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>
import ProcedureQuery from './procedureTab/procedureQuery'
import ProcedureAcl from './procedureTab/procedureAcl'
import ProcedureQuery from './procedureTab/ProcedureQuery'
import ProcedureAcl from './procedureTab/ProcedureAcl'
export default {
components: { ProcedureAcl, ProcedureQuery },

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

@ -75,8 +75,8 @@
</template>
<script>
import Appearance from '@/components/project/settings/appearance'
import CreateOrEditProject from '@/components/createOrEditProject'
import Appearance from '~/components/project/settings/Appearance'
import CreateOrEditProject from '~/components/CreateOrEditProject'
export default {
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>
import { mapGetters } from 'vuex'
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel'
import dlgLabelSubmitCancel from '../utils/DlgLabelSubmitCancel'
export default {
components: {

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

@ -148,7 +148,7 @@
<script>
import { mapGetters, mapActions } from 'vuex'
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel'
import dlgLabelSubmitCancel from '../utils/DlgLabelSubmitCancel'
export default {
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 { Splitpanes, Pane } from 'splitpanes'
import sqlRightClickOptions from '../../helpers/sqlRightClickOptions'
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel.vue'
import dlgLabelSubmitCancel from '../utils/DlgLabelSubmitCancel.vue'
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 />
</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 />
</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">-->
<!-- <ProjectLogs/>-->
<!-- </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">-->
<!-- <ProjectLogs/>-->
<!-- </pane>-->
@ -23,7 +23,7 @@
import { Splitpanes, Pane } from 'splitpanes'
import 'splitpanes/dist/splitpanes.css'
// import ProjectLogs from '~/components/projectLogs'
import ProjectOutput from '~/components/projectOutput'
import ProjectOutput from '~/components/ProjectOutput'
export default {
name: 'SqlLogAndOutput',
@ -42,13 +42,13 @@ export default {
}
},
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.mainPanelSize = 100
})
}
this.$store.watch(
state => !state.windows.outputWindow && !state.windows.logWindow,
state => !state.settings.outputWindow && !state.settings.logWindow,
(newState) => {
if (newState) {
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>
import { mapActions } from 'vuex'
import { UITypes } from 'nocodb-sdk'
import dlgLabelSubmitCancel from '../utils/dlgLabelSubmitCancel'
import dlgLabelSubmitCancel from '../utils/DlgLabelSubmitCancel'
import { isMetaTable } from '@/helpers/xutils'
import RowsXcDataTable from '@/components/project/spreadsheet/rowsXcDataTable'
import RowsXcDataTable from '~/components/project/spreadsheet/RowsXcDataTable'
export default {
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>
<script>
import TextField from '@/components/project/appStore/inputs/textField'
import Attachment from '@/components/project/appStore/inputs/attachment'
import PasswordField from '@/components/project/appStore/inputs/passwordField'
import TextAreaCell from '@/components/project/spreadsheet/components/editableCell/textAreaCell'
import CheckboxField from '@/components/project/appStore/inputs/checkboxField'
import TextField from '~/components/project/appStore/inputs/TextField'
import Attachment from '~/components/project/appStore/inputs/Attachment'
import PasswordField from '~/components/project/appStore/inputs/PasswordField'
import TextAreaCell from '~/components/project/spreadsheet/components/editableCell/TextAreaCell'
import CheckboxField from '~/components/project/appStore/inputs/CheckboxField'
export default {
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/>.
*
*/
-->

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

@ -81,12 +81,7 @@
</template>
<script>
import dayjs from 'dayjs'
const relativeTime = require('dayjs/plugin/relativeTime')
const utc = require('dayjs/plugin/utc')
dayjs.extend(utc)
dayjs.extend(relativeTime)
import { calculateDiff } from '~/helpers'
export default {
name: 'Audit',
@ -116,9 +111,7 @@ export default {
this.audits = list
this.count = pageInfo.totalRows
},
calculateDiff(date) {
return dayjs.utc(date).fromNow()
}
calculateDiff
}
}
</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 MonacoEditor from '../../monaco/Monaco'
import dlgLabelSubmitCancel from '../../utils/dlgLabelSubmitCancel'
import dlgLabelSubmitCancel from '../../utils/DlgLabelSubmitCancel'
export default {
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 MonacoEditor from '../../monaco/Monaco'
import dlgLabelSubmitCancel from '../../utils/dlgLabelSubmitCancel'
import dlgLabelSubmitCancel from '../../utils/DlgLabelSubmitCancel'
export default {
components: { MonacoEditor, dlgLabelSubmitCancel },

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

@ -57,10 +57,10 @@
<script>
import { mapGetters } from 'vuex'
import XcMeta from '../settings/xcMeta'
import XcMeta from '../settings/XcMeta'
import { isMetaTable } from '@/helpers/xutils'
import metaDiffSync from '~/components/project/projectMetadata/sync/metaDiffSync'
import ToggleTableUiAcl from '@/components/project/projectMetadata/uiAcl/toggleTableUIAcl'
import metaDiffSync from '~/components/project/projectMetadata/sync/MetaDiffSync'
import ToggleTableUiAcl from '~/components/project/projectMetadata/uiAcl/ToggleTableUIAcl'
export default {
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
>
<template #prepend-inner>
<v-icon small> search </v-icon>
<v-icon small>
search
</v-icon>
</template>
</v-text-field>
<v-spacer />
@ -127,16 +129,12 @@
</div>
</template>
<span v-if="table.disabled[role]"
>Click to make '{{ table.table_name }}' visible for
Role:{{ role }} in UI dashboard</span
>
<span v-else
>Click to hide '{{ table.table_name }}' for Role:{{
<span v-if="table.disabled[role]">Click to make '{{ table.table_name }}' visible for
Role:{{ role }} in UI dashboard</span>
<span v-else>Click to hide '{{ table.table_name }}' for Role:{{
role
}}
in UI dashboard</span
>
in UI dashboard</span>
</v-tooltip>
</td>
</tr>
@ -151,32 +149,32 @@
</template>
<script>
import { mapGetters } from "vuex";
import viewIcons from "~/helpers/viewIcons";
import { mapGetters } from 'vuex'
import viewIcons from '~/helpers/viewIcons'
export default {
name: "ToggleTableUiAcl",
name: 'ToggleTableUiAcl',
components: {},
props: ["nodes", "db"],
props: ['nodes', 'db'],
data: () => ({
viewIcons,
models: null,
updating: false,
dbsTab: 0,
filter: "",
tables: null,
filter: '',
tables: null
}),
async mounted() {
await this.loadTableList();
await this.loadTableList()
},
methods: {
async loadTableList() {
this.tables = await this.$api.project.modelVisibilityList(
this.db.project_id,
{
includeM2M: this.$store.state.windows.includeM2M || "",
includeM2M: this.$store.state.settings.includeM2M || ''
}
);
)
// this.tables = (await this.$store.dispatch('sqlMgr/ActSqlOp', [{
// dbAlias: this.db.meta.dbAlias,
// env: this.$store.getters['project/GtrEnv']
@ -188,32 +186,32 @@ export default {
try {
await this.$api.project.modelVisibilitySet(
this.db.project_id,
this.tables.filter((t) => t.edited)
);
this.tables.filter(t => t.edited)
)
this.$toast
.success("Updated UI ACL for tables successfully")
.goAway(3000);
.success('Updated UI ACL for tables successfully')
.goAway(3000)
} 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: {
...mapGetters({
dbAliasList: "project/GtrDbAliasList",
dbAliasList: 'project/GtrDbAliasList'
}),
edited() {
return (
this.tables && this.tables.length && this.tables.some((t) => t.edited)
);
this.tables && this.tables.length && this.tables.some(t => t.edited)
)
},
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>
<style scoped lang="scss">

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

Loading…
Cancel
Save